From 62534e53eaeabe021de6fbe9ad51fa0e160a56c5 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 24 Sep 2024 14:14:43 +0300 Subject: [PATCH 001/127] snowbridge: improve destination fee handling to avoid trapping fees dust (#5563) On messages Ethereum -> Polkadot Asset Hub: whether they are a token transfer or a `Transact` for registering new token, make sure to handle unspent fees, rather than trapping them. This PR deposits them to Snowbridge's sovereign account on Asset Hub. --------- Co-authored-by: command-bot <> --- .../pallets/inbound-queue/src/test.rs | 4 +-- .../primitives/router/src/inbound/mod.rs | 34 +++++++++++++------ .../bridges/bridge-hub-rococo/src/lib.rs | 14 ++++---- .../bridge-hub-rococo/src/tests/snowbridge.rs | 18 +++++++--- prdoc/pr_5563.prdoc | 14 ++++++++ 5 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 prdoc/pr_5563.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index bd993c968df..41c38460aab 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -40,8 +40,8 @@ fn test_submit_happy_path() { .into(), nonce: 1, message_id: [ - 57, 61, 232, 3, 66, 61, 25, 190, 234, 188, 193, 174, 13, 186, 1, 64, 237, 94, 73, - 83, 14, 18, 209, 213, 78, 121, 43, 108, 251, 245, 107, 67, + 255, 125, 48, 71, 174, 185, 100, 26, 159, 43, 108, 6, 116, 218, 55, 155, 223, 143, + 141, 22, 124, 110, 241, 18, 122, 217, 130, 29, 139, 76, 97, 201, ], fee_burned: 110000000000, } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 5cff8413af6..a10884b4553 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -249,7 +249,7 @@ impl< let total_amount = fee + CreateAssetDeposit::get(); let total: Asset = (Location::parent(), total_amount).into(); - let bridge_location: Location = (Parent, Parent, GlobalConsensus(network)).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); let asset_id = Self::convert_token_address(network, token); @@ -262,8 +262,15 @@ impl< // Pay for execution. BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location }, - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin` + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), // Change origin to the bridge. UniversalOrigin(GlobalConsensus(network)), @@ -280,12 +287,10 @@ impl< .encode() .into(), }, - RefundSurplus, - // Clear the origin so that remaining assets in holding - // are claimable by the physical origin (BridgeHub) - ClearOrigin, // Forward message id to Asset Hub SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. ] .into(); @@ -340,17 +345,24 @@ impl< match dest_para_id { Some(dest_para_id) => { let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), // Perform a deposit reserve to send to destination chain. DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset.clone()].into()), + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), dest: Location::new(1, [Parachain(dest_para_id)]), xcm: vec![ // Buy execution on target. BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit asset to beneficiary. - DepositAsset { assets: Definite(asset.into()), beneficiary }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, // Forward message id to destination parachain. SetTopic(message_id.into()), ] @@ -371,6 +383,8 @@ impl< // Forward message id to Asset Hub. instructions.push(SetTopic(message_id.into())); + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). (instructions.into(), total_fees.into()) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index ac08e48ded6..9d54d431e83 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -55,9 +55,12 @@ mod imports { BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, }, penpal_emulated_chain::{ - penpal_runtime::xcm_config::{ - CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, - UniversalLocation as PenpalUniversalLocation, + penpal_runtime::{ + self, + xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, }, PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner, }, @@ -72,9 +75,8 @@ mod imports { BridgeHubRococoParaReceiver as BridgeHubRococoReceiver, BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend, PenpalAPara as PenpalA, - PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, - RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, - RococoRelaySender as RococoSender, + PenpalAParaSender as PenpalASender, RococoRelay as Rococo, + RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, }; pub const ASSET_MIN_BALANCE: u128 = 1000; 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 84328fb7c6d..d91a0c6895f 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 @@ -286,11 +286,19 @@ fn send_token_from_ethereum_to_penpal() { // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer BridgeHubRococo::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); - // Fund PenPal sender and receiver - PenpalA::fund_accounts(vec![ - (PenpalAReceiver::get(), INITIAL_FUND), - (PenpalASender::get(), INITIAL_FUND), - ]); + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); PenpalA::execute_with(|| { assert_ok!(::System::set_storage( diff --git a/prdoc/pr_5563.prdoc b/prdoc/pr_5563.prdoc new file mode 100644 index 00000000000..cbf436125bb --- /dev/null +++ b/prdoc/pr_5563.prdoc @@ -0,0 +1,14 @@ +title: "snowbridge: improve destination fee handling to avoid trapping fees dust" + +doc: + - audience: Runtime User + description: | + On Ethereum -> Polkadot Asset Hub messages, whether they are a token transfer + or a `Transact` for registering a new token, any unspent fees are deposited to + Snowbridge's sovereign account on Asset Hub, rather than trapped in AH's asset trap. + +crates: + - name: snowbridge-router-primitives + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch -- GitLab From 1790e4272b9fd8832f1ab9e3bf5aaaae9cf5ada7 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 24 Sep 2024 13:31:25 +0200 Subject: [PATCH 002/127] Fix border condition in Snowbridge free consensus Updates (#5671) # Description A fix for a border condition introduced with new feature https://github.com/paritytech/polkadot-sdk/pull/5201. A malicious relayer could spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. Original PR: https://github.com/Snowfork/polkadot-sdk/pull/172 ## Integration Downstream projects are not affected. Relayers will not be able to spam the Ethereum client with irrelevant sync committee updates for free. ## Review Notes Adds a storage item to track the last free sync committee update period, so that duplicate imports are not free. --------- Co-authored-by: Adrian Catangiu --- .../pallets/ethereum-client/src/lib.rs | 27 +- .../pallets/ethereum-client/src/mock.rs | 27 + .../pallets/ethereum-client/src/tests.rs | 63 +- .../tests/fixtures/initial-checkpoint.json | 30 +- .../sync-committee-update-period-0-newer.json | 565 ++++++++++++++++++ .../sync-committee-update-period-0-older.json | 565 ++++++++++++++++++ .../sync-committee-update-period-0.json | 565 ++++++++++++++++++ prdoc/pr_5671.prdoc | 16 + 8 files changed, 1828 insertions(+), 30 deletions(-) create mode 100755 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json create mode 100755 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json create mode 100644 bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json create mode 100644 prdoc/pr_5671.prdoc diff --git a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs index 84b1476931c..311b54b97de 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/lib.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs @@ -179,6 +179,10 @@ pub mod pallet { #[pallet::storage] pub type NextSyncCommittee = StorageValue<_, SyncCommitteePrepared, ValueQuery>; + /// The last period where the next sync committee was updated for free. + #[pallet::storage] + pub type LatestSyncCommitteeUpdatePeriod = StorageValue<_, u64, ValueQuery>; + /// The current operating mode of the pallet. #[pallet::storage] #[pallet::getter(fn operating_mode)] @@ -442,6 +446,13 @@ pub mod pallet { let latest_finalized_state = FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) .ok_or(Error::::NotBootstrapped)?; + + let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); + let actual_weight = match update.next_sync_committee_update { + None => T::WeightInfo::submit(), + Some(_) => T::WeightInfo::submit_with_sync_committee(), + }; + if let Some(next_sync_committee_update) = &update.next_sync_committee_update { let store_period = compute_period(latest_finalized_state.slot); let update_finalized_period = compute_period(update.finalized_header.slot); @@ -465,17 +476,12 @@ pub mod pallet { "💫 SyncCommitteeUpdated at period {}.", update_finalized_period ); + >::set(update_finalized_period); Self::deposit_event(Event::SyncCommitteeUpdated { period: update_finalized_period, }); }; - let pays_fee = Self::check_refundable(update, latest_finalized_state.slot); - let actual_weight = match update.next_sync_committee_update { - None => T::WeightInfo::submit(), - Some(_) => T::WeightInfo::submit_with_sync_committee(), - }; - if update.finalized_header.slot > latest_finalized_state.slot { Self::store_finalized_header(update.finalized_header, update.block_roots_root)?; } @@ -657,7 +663,14 @@ pub mod pallet { /// successful sync committee updates are free. pub(super) fn check_refundable(update: &Update, latest_slot: u64) -> Pays { // If the sync committee was successfully updated, the update may be free. - if update.next_sync_committee_update.is_some() { + let update_period = compute_period(update.finalized_header.slot); + let latest_free_update_period = LatestSyncCommitteeUpdatePeriod::::get(); + // If the next sync committee is not known and this update sets it, the update is free. + // If the sync committee update is in a period that we have not received an update for, + // the update is free. + let refundable = + !>::exists() || update_period > latest_free_update_period; + if update.next_sync_committee_update.is_some() && refundable { return Pays::No; } diff --git a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs index be456565d40..7dbabdee823 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/mock.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs @@ -59,6 +59,33 @@ pub fn load_next_finalized_header_update_fixture() -> snowbridge_beacon_primitiv load_fixture("next-finalized-header-update.json".to_string()).unwrap() } +pub fn load_sync_committee_update_period_0() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0.json".to_string()).unwrap()) +} + +pub fn load_sync_committee_update_period_0_older_fixture() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0-older.json".to_string()).unwrap()) +} + +pub fn load_sync_committee_update_period_0_newer_fixture() -> Box< + snowbridge_beacon_primitives::Update< + { config::SYNC_COMMITTEE_SIZE }, + { config::SYNC_COMMITTEE_BITS_SIZE }, + >, +> { + Box::new(load_fixture("sync-committee-update-period-0-newer.json".to_string()).unwrap()) +} + pub fn get_message_verification_payload() -> (Log, Proof) { let inbound_fixture = snowbridge_pallet_ethereum_client_fixtures::make_inbound_fixture(); (inbound_fixture.message.event_log, inbound_fixture.message.proof) diff --git a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs index 82a3b822447..de298ee711d 100644 --- a/bridges/snowbridge/pallets/ethereum-client/src/tests.rs +++ b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork +pub use crate::mock::*; use crate::{ + config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT}, functions::compute_period, mock::{ get_message_verification_payload, load_checkpoint_update_fixture, @@ -8,12 +10,9 @@ use crate::{ load_next_sync_committee_update_fixture, load_sync_committee_update_fixture, }, sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error, - FinalizedBeaconState, LatestFinalizedBlockRoot, NextSyncCommittee, SyncCommitteePrepared, + FinalizedBeaconState, LatestFinalizedBlockRoot, LatestSyncCommitteeUpdatePeriod, + NextSyncCommittee, SyncCommitteePrepared, }; - -pub use crate::mock::*; - -use crate::config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT}; use frame_support::{assert_err, assert_noop, assert_ok, pallet_prelude::Pays}; use hex_literal::hex; use snowbridge_beacon_primitives::{ @@ -374,7 +373,7 @@ fn submit_update_in_current_period() { assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); let result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), update.clone()); assert_ok!(result); - assert_eq!(result.unwrap().pays_fee, Pays::Yes); + assert_eq!(result.unwrap().pays_fee, Pays::No); let block_root: H256 = update.finalized_header.hash_tree_root().unwrap(); assert!(>::contains_key(block_root)); }); @@ -711,8 +710,56 @@ fn duplicate_sync_committee_updates_are_not_free() { // Check that if the same update is submitted, the update is not free. let second_result = EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update); - assert_err!(second_result, Error::::IrrelevantUpdate); - assert_eq!(second_result.unwrap_err().post_info.pays_fee, Pays::Yes); + assert_ok!(second_result); + assert_eq!(second_result.unwrap().pays_fee, Pays::Yes); + }); +} + +#[test] +fn sync_committee_update_for_sync_committee_already_imported_are_not_free() { + let checkpoint = Box::new(load_checkpoint_update_fixture()); + let sync_committee_update = Box::new(load_sync_committee_update_fixture()); // slot 129 + let second_sync_committee_update = load_sync_committee_update_period_0(); // slot 128 + let third_sync_committee_update = load_sync_committee_update_period_0_newer_fixture(); // slot 224 + let fourth_sync_committee_update = load_sync_committee_update_period_0_older_fixture(); // slot 96 + let fith_sync_committee_update = Box::new(load_next_sync_committee_update_fixture()); // slot 8259 + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 is free (it is not set yet). + let result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), sync_committee_update.clone()); + assert_ok!(result); + assert_eq!(result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 again is not free. + let second_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), second_sync_committee_update); + assert_eq!(second_result.unwrap().pays_fee, Pays::Yes); + assert_eq!(>::get(), 0); + + // Check that setting an update with a sync committee that has already been set, but with a + // newer finalized header, is free. + let third_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), third_sync_committee_update); + assert_eq!(third_result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 0); + + // Check that setting the next sync committee for period 0 again with an earlier slot is not + // free. + let fourth_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fourth_sync_committee_update); + assert_err!(fourth_result, Error::::IrrelevantUpdate); + assert_eq!(fourth_result.unwrap_err().post_info.pays_fee, Pays::Yes); + + // Check that setting the next sync committee for period 1 is free. + let fith_result = + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), fith_sync_committee_update); + assert_eq!(fith_result.unwrap().pays_fee, Pays::No); + assert_eq!(>::get(), 1); }); } diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json index a62d646617e..34e65d20b88 100755 --- a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json @@ -1,10 +1,10 @@ { "header": { - "slot": 864, + "slot": 64, "proposer_index": 4, - "parent_root": "0x614e7672f991ac268cd841055973f55e1e42228831a211adef207bb7329be614", - "state_root": "0x5fa8dfca3d760e4242ab46d529144627aa85348a19173b6e081172c701197a4a", - "body_root": "0x0f34c083b1803666bb1ac5e73fa71582731a2cf37d279ff0a3b0cad5a2ff371e" + "parent_root": "0x88e5b7e0dd468b334caf9281e0665184d2d712d7ffe632123ea07631b714920c", + "state_root": "0x82771f834d4d896f4969abdaf45f28f49a7437ecfca7bf2f7db7bfac5ca7224f", + "body_root": "0x8b36f34ceba40a29c9c6fa6266564c7df30ea75fecf1a85e6ec1cb4aabf4dc68" }, "current_sync_committee": { "pubkeys": [ @@ -525,18 +525,18 @@ }, "current_sync_committee_branch": [ "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", - "0xa9e90f89e7f90fd5d79a6bbcaf40ba5cfc05ab1b561ac51c84867c32248d5b1e", - "0xbd1a76b03e02402bb24a627de1980a80ab17691980271f597b844b89b497ef75", - "0x07bbcd27c7cad089023db046eda17e8209842b7d97add8b873519e84fe6480e7", - "0x94c11eeee4cb6192bf40810f23486d8c75dfbc2b6f28d988d6f74435ede243b0" + "0x058baa5628d6156e55ab99da54244be4a071978528f2eb3b19a4f4d7ab36f870", + "0x5f89984c1068b616e99589e161d2bb73b92c68b3422ef309ace434894b4503ae", + "0x4f1c230cf2bbe39502171956421fbe4f1c0a71a9691944019047b84584b371d5", + "0xbf8d5f6021db16e9b50e639e5c489eb8dc06449bf4ed17045cb949cb89a58a04" ], "validators_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", - "block_roots_root": "0xb9aab9c388c4e4fcd899b71f62c498fc73406e38e8eb14aa440e9affa06f2a10", + "block_roots_root": "0x2c453665ba6fc024116daf5246126e36085c61257cfbcce69d0bdcf89c766dc0", "block_roots_branch": [ - "0x733422bd810895dab74cbbe07c69dd440cbb51f573181ad4dddac30fcdd0f41f", - "0x9b9eca73ab01d14549c325ba1b4610bb20bf1f8ec2dbd649f9d8cc7f3cea75fa", - "0xbcc666ad0ad9f9725cbd682bc95589d35b1b53b2a615f1e6e8dd5e086336becf", - "0x3069b547a08f703a1715016e926cbd64e71f93f64fb68d98d8c8f1ab745c46e5", - "0xc2de7e1097239404e17b263cfa0473533cc41e903cb03440d633bc5c27314cb4" + "0xbd04f51e43f63b0be48034920e8f5976111b7717225abccedbc6bcb327b95d00", + "0x758319a3bad11ee10fde1036551d982583c0392f284de5cc429b67fbd74c25d5", + "0xb42179d040c2bec20fa0a2750baf225b8097b5c9e4e22af9250cc773f4259427", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x9f03be8e70f74fc6b51e6ed03c96aabb544b5c50e5cdb8c0ab5001d1249d55f0" ] -} \ No newline at end of file +} diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json new file mode 100755 index 00000000000..7139589acbc --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-newer.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 224, + "proposer_index": 0, + "parent_root": "0xecfba5f579f43f474039f6f9abce51eb5607f6295aa45e1c353fa20245ab4efb", + "state_root": "0x10b21ccac4df114a9c30eaaff57f064b692e957a52eb43a8264702da76ba81f7", + "body_root": "0x6bd1768f675673b4ae32a197f569f7d279568fd5f60d32bd6ea11ecff559fc35" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000", + "sync_committee_signature": "0xb8f4800cb32edf6d05e9cace783d663719f7750f0438b8481c89895809c5430005df25b73393133c9df595e5998d6a540449d8840f8bd16474608bb0b9daa349b76429d8d7e314f2fb6e628c4f68c5469bc8c698bb232a767a4b080b8909aa53" + }, + "signature_slot": 225, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0xaad994f17223061c45fb5ec4930b2da08512e221ca6857bde8929eda92dc115c", + "0x61145312b89c006c2d1406285a9f2f826679d20b00239f65f76d40e28abe3bca", + "0x37977cb0ebd513f5123ede3a57b228f31eb98ecaad7757cf8e405fee8224982e", + "0x8c24e3a8ddb0bad93d5dcd240f566c5d08bc381a58b94e337bed63f75104fe45" + ] + }, + "finalized_header": { + "slot": 160, + "proposer_index": 0, + "parent_root": "0x6b536af592b64a337ae033b9646c4a10fd3369be72fcdaf53ae37797df8ec581", + "state_root": "0x1ed5990e4a1188a49ee64cdeb0ee9e480f29ce4d8020a0c5407471771a76ef2d", + "body_root": "0x73fb27d7521c84855007a824231d3b2b1650cd9ee34d914625f692c36b8112ef" + }, + "finality_branch": [ + "0x0500000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0x61145312b89c006c2d1406285a9f2f826679d20b00239f65f76d40e28abe3bca", + "0x37977cb0ebd513f5123ede3a57b228f31eb98ecaad7757cf8e405fee8224982e", + "0x8c24e3a8ddb0bad93d5dcd240f566c5d08bc381a58b94e337bed63f75104fe45" + ], + "block_roots_root": "0xa626dafac4b71585a5b18d18198d7e7c0a09c43b0fb3f2e68e04304d3be94b91", + "block_roots_branch": [ + "0x1a4ced7954adc2f360994137f07d1ae456b008d5ff81f40f252da770a0cd70c9", + "0xa6d595807cef4f868a03813aceb42f07fadf37f93d5b30a3603f55c1eab0081d", + "0x50f2310554199f26d4a326c940dd6e014db55bb8f18bf3642fed22e58ddb5dd6", + "0xd8a7fed47a6e1934c5a5750a44aa70de9898c42e877fc87f0acb0e1b9d236091", + "0xad421833151ec4b8fd8269f16b4b41f15e7e0b82d561553ed5a50e5d6c5f2190" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json new file mode 100755 index 00000000000..b0eff7cac1b --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0-older.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 96, + "proposer_index": 5, + "parent_root": "0x711c0cbebb834c0cd47d74732d78bc9f4794be2d7805176a4613ebaa9546569e", + "state_root": "0xe5ee40ae4ce991c927de404f3aea3209a55f29b54ee96d146c1e9fb733e14018", + "body_root": "0x57953c9bb22c5231b07078e6a3d82bd85ccdf48f55b4bb410c20af4cf4c3b03e" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0xa8a01929a4018d7f5cf3d0511b68ae6af1e32320a263d282ff85bf56860154bd70cd9b0b0f4aa7a956d0375b9b4ba6700c723fcaaeb577acd9a0a88baf0bb418e39f97b17b1edcaeb95fa086d4c5d410addc9f29c0b6c6c14775216cdcb828db" + }, + "signature_slot": 97, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0x48118ce24b62eda9ed2d37108f94efe223e6a385d84bcec6b2a53584271ea001", + "0xd72abb2443691ce25174da082c4c60880775d67f83802afd73cc2bf0edd06f73", + "0x0de609b4a50cd2729a8f9d9b6a505b008555dc121b18fb99c148be86ae08a53e", + "0xfb86aae7b54b08642d51132227e409e5247fa9ddb24287deab442ebf5dd9146c" + ] + }, + "finalized_header": { + "slot": 64, + "proposer_index": 4, + "parent_root": "0x60e496771388130ba1dc1d5d447bd43b4a5026a5d17d20f34d5352c0a97e5585", + "state_root": "0x7007a070c06dbd1c6de2f6fb1288f6569a13a00a1ed7505a8b1ede38827dd39c", + "body_root": "0xbccefd80ea680aa944837ec75d660651f369f72724f125e871b787c3dab18ea4" + }, + "finality_branch": [ + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0xd72abb2443691ce25174da082c4c60880775d67f83802afd73cc2bf0edd06f73", + "0x0de609b4a50cd2729a8f9d9b6a505b008555dc121b18fb99c148be86ae08a53e", + "0xfb86aae7b54b08642d51132227e409e5247fa9ddb24287deab442ebf5dd9146c" + ], + "block_roots_root": "0xf70c00c84139e631f8d4a69120f5837e5d14db26aee6aa29f5a6a100b53f820b", + "block_roots_branch": [ + "0x3c2f0c8588c1501bcd371de7103ad74ae93fe72b4703a1bd00fd77acefd90c76", + "0x8ac33e1bd9a7fa543236bf6f385b6082bb6e68ec344d0bc03e620dd908df4b07", + "0x56e652a369b875c2f28e96d341ed76ca453e2f5a0ee2ca571a9ae19d92e842df", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x91eee53bd353a3e021e2c382d9502503b7f9f1198b042ff36e8abdc74fd920dc" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json new file mode 100644 index 00000000000..916deb7513c --- /dev/null +++ b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update-period-0.json @@ -0,0 +1,565 @@ +{ + "attested_header": { + "slot": 128, + "proposer_index": 1, + "parent_root": "0x2161b169bc9dda1785a8c087e6455d9648d8df8c6d5f98f75d29c1c1c9e13ceb", + "state_root": "0x044bb5ec8eabc0ba7a74646cb92e4c6bd96f5d2974e0e191d3fd05de4eb1acea", + "body_root": "0x2b52b7dbe94cd1c024431064486880f2093480498f2b8a704fec9edc34f68eb8" + }, + "sync_aggregate": { + "sync_committee_bits": "0x00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "sync_committee_signature": "0x95ceea859d98d209441120821af32fa7ceb6080cf62db7a00a0f578ac83a4a1c619104474e715d1688732e8fe5b19f2417a4f6ba957b3cd2b8c817c8d8c42fc822062385269858feb955cd010744d8357dffef00535cf2e7a1017e58b22c4423" + }, + "signature_slot": 129, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b" + ], + "aggregate_pubkey": "0x8fbd66eeec2ff69ef0b836f04b1d67d88bcd4dfd495061964ad757c77abe822a39fa1cd8ed0d4d9bc9276cea73fd745c" + }, + "next_sync_committee_branch": [ + "0x3ade38d498a062b50880a9409e1ca3a7fd4315d91eeb3bb83e56ac6bfe8d6a59", + "0x028330a337168f77730425239a3abdfe336671cf5047fd03ea84eb668a0bad9e", + "0xe2b84cae247ad985d1d089df0f668f7f29ba1db750e5f32159e002dcda2d3f5f", + "0xecf54973b62af22f2620c37c14138021e5ea274f80815a52b3ed6c6234e039da", + "0x63a9c666a4d51dbfceda9b1c9dac57019fce464fd5733e6a6598dde49cc4ea23" + ] + }, + "finalized_header": { + "slot": 64, + "proposer_index": 4, + "parent_root": "0x88e5b7e0dd468b334caf9281e0665184d2d712d7ffe632123ea07631b714920c", + "state_root": "0x82771f834d4d896f4969abdaf45f28f49a7437ecfca7bf2f7db7bfac5ca7224f", + "body_root": "0x8b36f34ceba40a29c9c6fa6266564c7df30ea75fecf1a85e6ec1cb4aabf4dc68" + }, + "finality_branch": [ + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x98e9116c6bb7f20de18800dc63e73e689d06d6a47d35b5e2b32cf093d475840d", + "0xe2b84cae247ad985d1d089df0f668f7f29ba1db750e5f32159e002dcda2d3f5f", + "0xecf54973b62af22f2620c37c14138021e5ea274f80815a52b3ed6c6234e039da", + "0x63a9c666a4d51dbfceda9b1c9dac57019fce464fd5733e6a6598dde49cc4ea23" + ], + "block_roots_root": "0x2c453665ba6fc024116daf5246126e36085c61257cfbcce69d0bdcf89c766dc0", + "block_roots_branch": [ + "0xbd04f51e43f63b0be48034920e8f5976111b7717225abccedbc6bcb327b95d00", + "0x758319a3bad11ee10fde1036551d982583c0392f284de5cc429b67fbd74c25d5", + "0xb42179d040c2bec20fa0a2750baf225b8097b5c9e4e22af9250cc773f4259427", + "0x5340ad5877c72dca689ca04bc8fedb78d67a4801d99887937edd8ccd29f87e82", + "0x9f03be8e70f74fc6b51e6ed03c96aabb544b5c50e5cdb8c0ab5001d1249d55f0" + ], + "execution_header": null, + "execution_branch": null +} \ No newline at end of file diff --git a/prdoc/pr_5671.prdoc b/prdoc/pr_5671.prdoc new file mode 100644 index 00000000000..364165ec820 --- /dev/null +++ b/prdoc/pr_5671.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: Snowbridge free consensus updates border condition fix + +doc: + - audience: Runtime Dev + description: | + A fix for a border condition introduced with the Ethereum client free consensus updates. A malicious relayer could + spam the Ethereum client with sync committee updates that have already been imported for the period. This PR adds + a storage item to track the last imported sync committee period, so that subsequent irrelevant updates are not free. + No impact for users or relayers, since the feature introducing the border condition has not been released. + +crates: + - name: snowbridge-pallet-ethereum-client + bump: patch -- GitLab From c987da33935898cd5b2f8605d548bc48727c1815 Mon Sep 17 00:00:00 2001 From: Evgeny Snitko Date: Tue, 24 Sep 2024 16:18:46 +0400 Subject: [PATCH 003/127] Preflight workflow update (#5771) - New and updated vars - New runner vars - Runners rename (see https://github.com/paritytech/ci_cd/issues/1039) --------- Co-authored-by: Oliver Tale-Yazdi --- .github/workflows/reusable-preflight.yml | 132 +++++++++++++++++---- .github/workflows/subsystem-benchmarks.yml | 2 +- .github/workflows/tests-linux-stable.yml | 2 +- .github/workflows/tests-misc.yml | 2 +- 4 files changed, 114 insertions(+), 24 deletions(-) diff --git a/.github/workflows/reusable-preflight.yml b/.github/workflows/reusable-preflight.yml index 71823a97ff2..e1799adddca 100644 --- a/.github/workflows/reusable-preflight.yml +++ b/.github/workflows/reusable-preflight.yml @@ -26,23 +26,55 @@ on: IMAGE: value: ${{ jobs.preflight.outputs.IMAGE }} description: "CI image" + + # Runners + # https://github.com/paritytech/ci_cd/wiki/GitHub#paritytech-self-hosted-runners RUNNER: value: ${{ jobs.preflight.outputs.RUNNER }} description: | - Runner name. + Main runner for resource-intensive tasks + By default we use spot machines that can be terminated at any time. + Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. + RUNNER_OLDLINUX: + value: ${{ jobs.preflight.outputs.RUNNER_OLDLINUX }} + description: | + parity-oldlinux By default we use spot machines that can be terminated at any time. Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. - OLDLINUXRUNNER: - value: ${{ jobs.preflight.outputs.OLDLINUXRUNNER }} + RUNNER_DEFAULT: + value: ${{ jobs.preflight.outputs.RUNNER_DEFAULT }} + description: "Relatively lightweight runner. When `ubuntu-latest` is not enough" + RUNNER_WEIGHTS: + value: ${{ jobs.preflight.outputs.RUNNER_WEIGHTS }} + RUNNER_BENCHMARK: + value: ${{ jobs.preflight.outputs.RUNNER_BENCHMARK }} + RUNNER_MACOS: + value: ${{ jobs.preflight.outputs.RUNNER_MACOS }} + + # Vars + SOURCE_REF_SLUG: + value: ${{ jobs.preflight.outputs.SOURCE_REF_SLUG }} + description: "Name of the current branch for `push` or source branch for `pull_request` with `/` replaced by `_`. Does not exists in merge_group" + REF_SLUG: + value: ${{ jobs.preflight.outputs.REF_SLUG }} + description: | + Name of the current revision (depending on the event) with `/` replaced by `_`, e.g: + push - master + pull_request - 49_merge + merge_group - gh-readonly-queue_master_pr-49-38d43798a986430231c828b2c762997f818ac012 - SOURCE_REF_NAME: - value: ${{ jobs.preflight.outputs.SOURCE_REF_NAME }} - description: "Name of the current branch for `push` or source branch for `pull_request`" COMMIT_SHA: value: ${{ jobs.preflight.outputs.COMMIT_SHA }} - description: "Sha of the current commit for `push` or head of the source branch for `pull_request`" + description: "Sha of the current revision" + COMMIT_SHA_SHORT: + value: ${{ jobs.preflight.outputs.COMMIT_SHA_SHORT }} + description: "Sha of the current revision, 8-symbols long" jobs: + + # + # + # preflight: runs-on: ubuntu-latest outputs: @@ -50,12 +82,21 @@ jobs: changes_currentWorkflow: ${{ steps.set_changes.outputs.currentWorkflow_any_changed }} IMAGE: ${{ steps.set_image.outputs.IMAGE }} + + # Runners + # https://github.com/paritytech/ci_cd/wiki/GitHub#paritytech-self-hosted-runners RUNNER: ${{ steps.set_runner.outputs.RUNNER }} - OLDLINUXRUNNER: ${{ steps.set_runner.outputs.OLDLINUXRUNNER }} + RUNNER_OLDLINUX: ${{ steps.set_runner.outputs.RUNNER_OLDLINUX }} + RUNNER_DEFAULT: ${{ steps.set_runner.outputs.RUNNER_DEFAULT }} + RUNNER_WEIGHTS: ${{ steps.set_runner.outputs.RUNNER_WEIGHTS }} + RUNNER_BENCHMARK: ${{ steps.set_runner.outputs.RUNNER_BENCHMARK }} + RUNNER_MACOS: ${{ steps.set_runner.outputs.RUNNER_MACOS }} - SOURCE_REF_NAME: ${{ steps.set_vars.outputs.SOURCE_REF_NAME }} - COMMIT_SHA: ${{ steps.set_vars.outputs.COMMIT_SHA }} + SOURCE_REF_SLUG: ${{ steps.set_vars.outputs.SOURCE_REF_SLUG }} + REF_SLUG: ${{ steps.set_vars.outputs.REF_SLUG }} + COMMIT_SHA: ${{ steps.set_vars.outputs.COMMIT_SHA }} + COMMIT_SHA_SHORT: ${{ steps.set_vars.outputs.COMMIT_SHA_SHORT }} steps: @@ -64,7 +105,8 @@ jobs: # # Set changes # - - id: current_file + - name: Current file + id: current_file shell: bash run: | echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT @@ -98,27 +140,40 @@ jobs: # By default we use spot machines that can be terminated at any time. # Merge queues use persistent runners to avoid kicking off from queue when the runner is terminated. # - - id: set_runner + - name: Set runner + id: set_runner shell: bash run: | + echo "RUNNER_DEFAULT=parity-default" >> $GITHUB_OUTPUT + echo "RUNNER_WEIGHTS=parity-weights" >> $GITHUB_OUTPUT + echo "RUNNER_BENCHMARK=parity-benchmark" >> $GITHUB_OUTPUT + echo "RUNNER_MACOS=parity-macos" >> $GITHUB_OUTPUT + # # Run merge queues on persistent runners if [[ $GITHUB_REF_NAME == *"gh-readonly-queue"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy-persistent" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux-persistent" >> $GITHUB_OUTPUT + echo "RUNNER=parity-large-persistent" >> $GITHUB_OUTPUT + echo "RUNNER_OLDLINUX=parity-oldlinux-persistent" >> $GITHUB_OUTPUT else - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT - echo "OLDLINUXRUNNER=oldlinux" >> $GITHUB_OUTPUT + echo "RUNNER=parity-large" >> $GITHUB_OUTPUT + echo "RUNNER_OLDLINUX=parity-oldlinux" >> $GITHUB_OUTPUT fi # # Set vars # - - id: set_vars + - name: Set vars + id: set_vars shell: bash run: | - export BRANCH_NAME=${{ github.head_ref || github.ref_name }} - echo "SOURCE_REF_NAME=${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT - echo "COMMIT_SHA=${{ github.event.pull_request.head.sha || github.sha }}" >> $GITHUB_OUTPUT + export SOURCE_REF_NAME=${{ github.head_ref || github.ref_name }} + echo "SOURCE_REF_SLUG=${SOURCE_REF_NAME//\//_}" >> $GITHUB_OUTPUT + # + export COMMIT_SHA=${{ github.sha }} + echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_OUTPUT + echo "COMMIT_SHA_SHORT=${COMMIT_SHA:0:8}" >> $GITHUB_OUTPUT + # + export REF_NAME=${{ github.ref_name }} + echo "REF_SLUG=${REF_NAME//\//_}" >> $GITHUB_OUTPUT - name: log @@ -126,6 +181,41 @@ jobs: run: | echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}" echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}" + + # + # + # + ci-versions: + needs: [preflight] + runs-on: ubuntu-latest + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - uses: actions/checkout@v4 + + - name: Info rust + run: | + rustup show + cargo --version + cargo +nightly --version + cargo clippy --version + echo "yarn version: $(yarn --version)" + echo $( substrate-contracts-node --version | awk 'NF' ) + estuary --version + cargo-contract --version + + - name: Info forklift + run: forklift version + + - name: Info vars + run: | + echo "COMMIT_SHA: ${{ needs.preflight.outputs.COMMIT_SHA }}" + echo "COMMIT_SHA_SHORT: ${{ needs.preflight.outputs.COMMIT_SHA_SHORT }}" + echo "SOURCE_REF_SLUG: ${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" + echo "REF_SLUG: ${{ needs.preflight.outputs.REF_SLUG }}" + echo "RUNNER: ${{ needs.preflight.outputs.RUNNER }}" + echo "IMAGE: ${{ needs.preflight.outputs.IMAGE }}" + # echo "github.ref: ${{ github.ref }}" echo "github.ref_name: ${{ github.ref_name }}" - echo "github.sha: ${{ github.sha }}" + echo "github.sha: ${{ github.sha }}" \ No newline at end of file diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index 210714d847f..3c0eaf30e45 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -22,7 +22,7 @@ jobs: build: timeout-minutes: 80 needs: [preflight] - runs-on: ${{ needs.preflight.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} container: image: ${{ needs.preflight.outputs.IMAGE }} strategy: diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 6cf71422511..994f1c9b78e 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -68,7 +68,7 @@ jobs: runners: [ "${{ needs.preflight.outputs.RUNNER }}", - "${{ needs.preflight.outputs.OLDLINUXRUNNER }}", + "${{ needs.preflight.outputs.RUNNER_OLDLINUX }}", ] container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 843e09ef7a4..90162adc225 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -321,7 +321,7 @@ jobs: cargo-check-all-crate-macos: timeout-minutes: 30 needs: [preflight] - runs-on: parity-macos + runs-on: ${{ needs.preflight.outputs.RUNNER_MACOS }} env: SKIP_WASM_BUILD: 1 steps: -- GitLab From 9294572aa3a0cf3bde3b8b7ac5d48d2b1df113c2 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:01:35 -0300 Subject: [PATCH 004/127] Fix parachain-template-test (#5821) Fix `parachain-template-test` (bump `zombienet` version). Thx! --- Cargo.lock | 26 +++++++++++++------------ Cargo.toml | 4 ++++ polkadot/zombienet-sdk-tests/Cargo.toml | 20 +++++++++---------- templates/zombienet/Cargo.toml | 10 +++++----- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18517f94788..f97d29c56c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27230,17 +27230,19 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23322e411b8d19b41b5c20ab8e88c10822189a4fcfd069c7fcd1542b8d3035aa" +checksum = "ebbfc98adb25076777967f7aad078e74029e129b102eb0812c425432f8c2be7b" dependencies = [ "anyhow", "lazy_static", "multiaddr 0.18.1", "regex", + "reqwest 0.11.20", "serde", "serde_json", "thiserror", + "tokio", "toml 0.7.8", "url", "zombienet-support", @@ -27248,9 +27250,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381f701565b3918a909132743b3674569ce3da25b5c3a6493883abaf1046577a" +checksum = "5b17f4d1d05b3aedf02818eb0f4d5a76664da0e07bb2f7e7d02613e0ef0f316a" dependencies = [ "anyhow", "async-trait", @@ -27281,9 +27283,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab79fa58bcfecbcd41485c6f13052853ccde8b09f173b601f78747d7abc2b7f" +checksum = "7203390ab88919240da3a3eb06b625b6e300e94f98e04ba5141e9138dc663b7d" dependencies = [ "pest", "pest_derive", @@ -27292,9 +27294,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6af0264938da61b25da89f17ee0630393a4ba793582a4a8a1650eb15b47fc1ef" +checksum = "ee02ee957ec39b698798fa6dc2a0d5ba4524198471c37d57755e9685b67fb50c" dependencies = [ "anyhow", "async-trait", @@ -27323,9 +27325,9 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc5b7ebfba4ab62486c8cb5bcd7345c4376487487cfe3481476cb4d4accc75e" +checksum = "f594e67922182277a3da0926f21b693eb5a0c38b32ca7fd6ef16167809fe5064" dependencies = [ "async-trait", "futures", @@ -27340,9 +27342,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5b80d34a0eecca69dd84c2e13f84f1fae0cc378baf4f15f769027af068418b" +checksum = "93d3144537df7c8939bbb355cc5245a6dc0078446a6cdaf9272268bd1043c788" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 8e3330e4e61..b7c9c0cdcbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -737,6 +737,7 @@ either = { version = "1.8.1", default-features = false } emulated-integration-tests-common = { path = "cumulus/parachains/integration-tests/emulated/common", default-features = false } enumflags2 = { version = "0.7.7" } enumn = { version = "0.1.13" } +env_logger = { version = "0.11.2" } environmental = { version = "1.1.4", default-features = false } equivocation-detector = { path = "bridges/relays/equivocation" } ethabi = { version = "1.0.0", default-features = false, package = "ethabi-decode" } @@ -1299,6 +1300,8 @@ substrate-test-runtime-client = { path = "substrate/test-utils/runtime/client" } substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime/transaction-pool" } substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } +subxt = { version = "0.37", default-features = false } +subxt-signer = { version = "0.37" } syn = { version = "2.0.77" } sysinfo = { version = "0.30" } tar = { version = "0.4" } @@ -1368,6 +1371,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } +zombienet-sdk = { version = "0.2.13" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml index 3374ad572b9..4eac7af49f8 100644 --- a/polkadot/zombienet-sdk-tests/Cargo.toml +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -8,16 +8,16 @@ license.workspace = true publish = false [dependencies] -env_logger = "0.11.2" -log = "0.4" -subxt = { version = "0.37", features = ["substrate-compat"] } -subxt-signer = { version = "0.37" } -tokio = { version = "1.36.0", features = ["rt-multi-thread"] } -anyhow = "1.0.81" -zombienet-sdk = "0.2.6" -serde = "1.0.197" -serde_json = "1.0.114" -parity-scale-codec = { version = "3.6.9", features = ["derive"] } +env_logger = { workspace = true } +log = { workspace = true } +subxt = { workspace = true, features = ["substrate-compat"] } +subxt-signer = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } +anyhow = { workspace = true } +zombienet-sdk = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +codec = { workspace = true, features = ["derive"] } [features] zombie-metadata = [] diff --git a/templates/zombienet/Cargo.toml b/templates/zombienet/Cargo.toml index cb2adf70dbd..f29325dbe6a 100644 --- a/templates/zombienet/Cargo.toml +++ b/templates/zombienet/Cargo.toml @@ -10,11 +10,11 @@ edition.workspace = true publish = false [dependencies] -env_logger = "0.11.2" -log = "0.4" -tokio = { version = "1.36.0", features = ["rt-multi-thread"] } -anyhow = "1.0.81" -zombienet-sdk = "0.2.8" +env_logger = { workspace = true } +log = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } +anyhow = { workspace = true } +zombienet-sdk = { workspace = true } [features] zombienet = [] -- GitLab From 710e74ddefdff1e36b77ba65abe54feb0ac15040 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 24 Sep 2024 23:21:25 +0200 Subject: [PATCH 005/127] Bridges lane id agnostic for backwards compatibility (#5649) This PR primarily fixes the issue with `zombienet-bridges-0001-asset-transfer-works` (see: https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7404903). The PR looks large, but most of the changes involve splitting `LaneId` into `LegacyLaneId` and `HashedLaneId`. All pallets now use `LaneId` as a generic parameter. The actual bridging pallets are now backward compatible and work with actual **substrate-relay v1.6.10**, which does not even known anything about permissionless lanes or the new pallet changes. ## Important - [x] added migration for `pallet_bridge_relayers` and `RewardsAccountParams` change order of params, which generates different accounts ## Deployment follow ups - [ ] fix monitoring for `at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}` - [ ] check sovereign reward accounts - because of changed `RewardsAccountParams` - [ ] deploy another messages instances for permissionless lanes - on BHs or AHs? - [ ] return back `open_and_close_bridge_works` for another `pallet-bridge-messages` instance --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Adrian Catangiu Co-authored-by: Oliver Tale-Yazdi --- Cargo.lock | 7 + bridges/bin/runtime-common/src/extensions.rs | 11 +- .../bin/runtime-common/src/messages_api.rs | 8 +- .../src/messages_benchmarking.rs | 78 ++--- bridges/bin/runtime-common/src/mock.rs | 29 +- .../chains/chain-bridge-hub-kusama/src/lib.rs | 2 +- .../chain-bridge-hub-polkadot/src/lib.rs | 2 +- .../chains/chain-bridge-hub-rococo/src/lib.rs | 2 +- .../chain-bridge-hub-westend/src/lib.rs | 2 +- .../chains/chain-polkadot-bulletin/src/lib.rs | 2 +- bridges/modules/messages/src/benchmarking.rs | 18 +- bridges/modules/messages/src/call_ext.rs | 30 +- bridges/modules/messages/src/inbound_lane.rs | 10 +- bridges/modules/messages/src/lanes_manager.rs | 32 +- bridges/modules/messages/src/lib.rs | 82 +++-- bridges/modules/messages/src/outbound_lane.rs | 6 +- bridges/modules/messages/src/proofs.rs | 43 +-- .../messages/src/tests/messages_generation.rs | 16 +- bridges/modules/messages/src/tests/mock.rs | 95 +++--- .../messages/src/tests/pallet_tests.rs | 25 +- bridges/modules/relayers/src/benchmarking.rs | 54 ++-- .../relayers/src/extension/grandpa_adapter.rs | 23 +- .../src/extension/messages_adapter.rs | 19 +- bridges/modules/relayers/src/extension/mod.rs | 150 +++++---- .../src/extension/parachain_adapter.rs | 24 +- bridges/modules/relayers/src/lib.rs | 216 +++++++------ bridges/modules/relayers/src/migration.rs | 243 ++++++++++++++ bridges/modules/relayers/src/mock.rs | 54 +++- .../modules/relayers/src/payment_adapter.rs | 11 +- bridges/modules/relayers/src/stake_adapter.rs | 8 +- .../modules/xcm-bridge-hub/src/dispatcher.rs | 32 +- .../modules/xcm-bridge-hub/src/exporter.rs | 157 +++++++-- bridges/modules/xcm-bridge-hub/src/lib.rs | 110 +++---- .../modules/xcm-bridge-hub/src/migration.rs | 3 +- bridges/modules/xcm-bridge-hub/src/mock.rs | 21 +- bridges/primitives/messages/src/call_info.rs | 32 +- bridges/primitives/messages/src/lane.rs | 297 +++++++++++------- bridges/primitives/messages/src/lib.rs | 36 +-- .../primitives/messages/src/source_chain.rs | 20 +- .../primitives/messages/src/storage_keys.rs | 38 ++- .../primitives/messages/src/target_chain.rs | 44 ++- bridges/primitives/relayers/src/extension.rs | 30 +- bridges/primitives/relayers/src/lib.rs | 175 ++++++++--- .../primitives/relayers/src/registration.rs | 18 +- bridges/primitives/runtime/src/chain.rs | 20 +- bridges/primitives/runtime/src/lib.rs | 4 +- bridges/primitives/xcm-bridge-hub/src/lib.rs | 32 +- bridges/relays/client-substrate/src/chain.rs | 3 - .../lib-substrate-relay/src/cli/bridge.rs | 23 +- .../relays/lib-substrate-relay/src/cli/mod.rs | 43 +-- .../src/cli/relay_headers_and_messages/mod.rs | 61 ++-- .../src/cli/relay_messages.rs | 16 +- .../src/messages/metrics.rs | 19 +- .../lib-substrate-relay/src/messages/mod.rs | 77 +++-- .../src/messages/source.rs | 27 +- .../src/messages/target.rs | 12 +- bridges/relays/messages/Cargo.toml | 3 + bridges/relays/messages/src/lib.rs | 1 + bridges/relays/messages/src/message_lane.rs | 4 + .../relays/messages/src/message_lane_loop.rs | 49 ++- bridges/relays/messages/src/metrics.rs | 31 +- bridges/testing/README.md | 2 +- .../rococo-westend/bridges_rococo_westend.sh | 116 +++---- .../bridges/bridge-hub-rococo/Cargo.toml | 6 + .../bridges/bridge-hub-rococo/src/genesis.rs | 12 + .../bridges/bridge-hub-westend/Cargo.toml | 6 + .../bridges/bridge-hub-westend/src/genesis.rs | 12 + .../emulated/common/src/impls.rs | 16 +- .../bridge-hub-rococo/src/tests/mod.rs | 22 -- .../bridge-hub-westend/src/tests/mod.rs | 22 -- .../src/bridge_common_config.rs | 33 +- .../src/bridge_to_bulletin_config.rs | 69 ++-- .../src/bridge_to_westend_config.rs | 140 +++++++-- .../src/genesis_config_presets.rs | 11 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 125 +++++--- .../bridge-hub-rococo/tests/snowbridge.rs | 6 +- .../bridge-hub-rococo/tests/tests.rs | 119 ++++--- .../src/bridge_common_config.rs | 12 +- .../src/bridge_to_rococo_config.rs | 137 ++++++-- .../src/genesis_config_presets.rs | 11 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 54 +++- .../bridge-hub-westend/tests/tests.rs | 52 +-- .../bridge-hubs/test-utils/Cargo.toml | 2 + .../bridge-hubs/test-utils/src/lib.rs | 4 +- .../src/test_cases/from_grandpa_chain.rs | 135 ++++---- .../src/test_cases/from_parachain.rs | 148 +++++---- .../test-utils/src/test_cases/helpers.rs | 101 ++++-- .../test-utils/src/test_cases/mod.rs | 14 +- .../src/test_data/from_grandpa_chain.rs | 31 +- .../src/test_data/from_parachain.rs | 24 +- .../test-utils/src/test_data/mod.rs | 6 +- .../parachains/runtimes/test-utils/src/lib.rs | 2 +- cumulus/polkadot-parachain/Cargo.toml | 3 + ...ridges_zombienet_tests_injected.Dockerfile | 2 +- .../xcm/xcm-executor/src/traits/export.rs | 6 +- prdoc/pr_5649.prdoc | 49 +++ 96 files changed, 2759 insertions(+), 1491 deletions(-) create mode 100644 bridges/modules/relayers/src/migration.rs create mode 100644 prdoc/pr_5649.prdoc diff --git a/Cargo.lock b/Cargo.lock index f97d29c56c8..69eabeb04e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2188,12 +2188,14 @@ dependencies = [ name = "bridge-hub-rococo-emulated-chain" version = "0.0.0" dependencies = [ + "bp-messages", "bridge-hub-common", "bridge-hub-rococo-runtime", "emulated-integration-tests-common", "frame-support", "parachains-common", "sp-core 28.0.0", + "staging-xcm", "testnet-parachains-constants", ] @@ -2357,6 +2359,7 @@ dependencies = [ "pallet-bridge-relayers", "pallet-timestamp", "pallet-utility", + "pallet-xcm", "pallet-xcm-bridge-hub", "parachains-common", "parachains-runtimes-test-utils", @@ -2375,12 +2378,14 @@ dependencies = [ name = "bridge-hub-westend-emulated-chain" version = "0.0.0" dependencies = [ + "bp-messages", "bridge-hub-common", "bridge-hub-westend-runtime", "emulated-integration-tests-common", "frame-support", "parachains-common", "sp-core 28.0.0", + "staging-xcm", "testnet-parachains-constants", ] @@ -9560,6 +9565,7 @@ dependencies = [ "parking_lot 0.12.3", "relay-utils", "sp-arithmetic 23.0.0", + "sp-core 28.0.0", ] [[package]] @@ -14876,6 +14882,7 @@ version = "4.0.0" dependencies = [ "asset-hub-rococo-runtime", "asset-hub-westend-runtime", + "bp-messages", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", "collectives-westend-runtime", diff --git a/bridges/bin/runtime-common/src/extensions.rs b/bridges/bin/runtime-common/src/extensions.rs index dc7e14de28f..dced5023947 100644 --- a/bridges/bin/runtime-common/src/extensions.rs +++ b/bridges/bin/runtime-common/src/extensions.rs @@ -374,7 +374,7 @@ mod tests { use super::*; use crate::mock::*; use bp_header_chain::StoredHeaderDataBuilder; - use bp_messages::{InboundLaneData, LaneId, MessageNonce, OutboundLaneData}; + use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData}; use bp_parachains::{BestParaHeadHash, ParaInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; @@ -390,17 +390,16 @@ mod tests { }; parameter_types! { - pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::ThisChain, ); - pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::BridgedChain, ); - pub TestLaneId: LaneId = test_lane_id(); } pub struct MockCall { @@ -476,10 +475,6 @@ mod tests { } } - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } - fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance { let test_stake: ThisChainBalance = TestStake::get(); ExistentialDeposit::get().saturating_add(test_stake * 100) diff --git a/bridges/bin/runtime-common/src/messages_api.rs b/bridges/bin/runtime-common/src/messages_api.rs index 7fbdeb36612..c8522d4d1f2 100644 --- a/bridges/bin/runtime-common/src/messages_api.rs +++ b/bridges/bin/runtime-common/src/messages_api.rs @@ -16,14 +16,12 @@ //! Helpers for implementing various message-related runtime API methods. -use bp_messages::{ - InboundMessageDetails, LaneId, MessageNonce, MessagePayload, OutboundMessageDetails, -}; +use bp_messages::{InboundMessageDetails, MessageNonce, MessagePayload, OutboundMessageDetails}; use sp_std::vec::Vec; /// Implementation of the `To*OutboundLaneApi::message_details`. pub fn outbound_message_details( - lane: LaneId, + lane: Runtime::LaneId, begin: MessageNonce, end: MessageNonce, ) -> Vec @@ -48,7 +46,7 @@ where /// Implementation of the `To*InboundLaneApi::message_details`. pub fn inbound_message_details( - lane: LaneId, + lane: Runtime::LaneId, messages: Vec<(MessagePayload, OutboundMessageDetails)>, ) -> Vec where diff --git a/bridges/bin/runtime-common/src/messages_benchmarking.rs b/bridges/bin/runtime-common/src/messages_benchmarking.rs index 1880e65547f..acbdbcda8de 100644 --- a/bridges/bin/runtime-common/src/messages_benchmarking.rs +++ b/bridges/bin/runtime-common/src/messages_benchmarking.rs @@ -33,15 +33,15 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, ThisChainOf, + BridgedChainOf, LaneIdOf, ThisChainOf, }; use sp_runtime::traits::{Header, Zero}; use sp_std::prelude::*; use xcm::latest::prelude::*; /// Prepare inbound bridge message according to given message proof parameters. -fn prepare_inbound_message( - params: &MessageProofParams, +fn prepare_inbound_message( + params: &MessageProofParams, successful_dispatch_message_generator: impl Fn(usize) -> MessagePayload, ) -> MessagePayload { let expected_size = params.proof_params.db_size.unwrap_or(0) as usize; @@ -71,9 +71,9 @@ fn prepare_inbound_message( /// uses GRANDPA finality. For parachains, please use the `prepare_message_proof_from_parachain` /// function. pub fn prepare_message_proof_from_grandpa_chain( - params: MessageProofParams, + params: MessageProofParams>, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>, LaneIdOf>, Weight) where R: pallet_bridge_grandpa::Config> + pallet_bridge_messages::Config< @@ -84,18 +84,21 @@ where MI: 'static, { // prepare storage proof - let (state_root, storage_proof) = - prepare_messages_storage_proof::, ThisChainOf>( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.proof_params, - |_| prepare_inbound_message(¶ms, &message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage_proof) = prepare_messages_storage_proof::< + BridgedChainOf, + ThisChainOf, + LaneIdOf, + >( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.proof_params, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::(state_root); @@ -121,9 +124,9 @@ where /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_proof_from_grandpa_chain` function. pub fn prepare_message_proof_from_parachain( - params: MessageProofParams, + params: MessageProofParams>, message_generator: impl Fn(usize) -> MessagePayload, -) -> (FromBridgedChainMessagesProof>>, Weight) +) -> (FromBridgedChainMessagesProof>, LaneIdOf>, Weight) where R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, @@ -131,18 +134,21 @@ where BridgedChainOf: Chain + Parachain, { // prepare storage proof - let (state_root, storage_proof) = - prepare_messages_storage_proof::, ThisChainOf>( - params.lane, - params.message_nonces.clone(), - params.outbound_lane_data.clone(), - params.proof_params, - |_| prepare_inbound_message(¶ms, &message_generator), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (state_root, storage_proof) = prepare_messages_storage_proof::< + BridgedChainOf, + ThisChainOf, + LaneIdOf, + >( + params.lane, + params.message_nonces.clone(), + params.outbound_lane_data.clone(), + params.proof_params, + |_| prepare_inbound_message(¶ms, &message_generator), + encode_all_messages, + encode_lane_data, + false, + false, + ); // update runtime storage let (_, bridged_header_hash) = @@ -166,8 +172,8 @@ where /// uses GRANDPA finality. For parachains, please use the /// `prepare_message_delivery_proof_from_parachain` function. pub fn prepare_message_delivery_proof_from_grandpa_chain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> + params: MessageDeliveryProofParams>, LaneIdOf>, +) -> FromBridgedChainMessagesDeliveryProof>, LaneIdOf> where R: pallet_bridge_grandpa::Config> + pallet_bridge_messages::Config< @@ -182,6 +188,7 @@ where let (state_root, storage_proof) = prepare_message_delivery_storage_proof::< BridgedChainOf, ThisChainOf, + LaneIdOf, >(params.lane, params.inbound_lane_data, params.proof_params); // update runtime storage @@ -200,8 +207,8 @@ where /// uses parachain finality. For GRANDPA chains, please use the /// `prepare_message_delivery_proof_from_grandpa_chain` function. pub fn prepare_message_delivery_proof_from_parachain( - params: MessageDeliveryProofParams>>, -) -> FromBridgedChainMessagesDeliveryProof>> + params: MessageDeliveryProofParams>, LaneIdOf>, +) -> FromBridgedChainMessagesDeliveryProof>, LaneIdOf> where R: pallet_bridge_parachains::Config + pallet_bridge_messages::Config, PI: 'static, @@ -213,6 +220,7 @@ where let (state_root, storage_proof) = prepare_message_delivery_storage_proof::< BridgedChainOf, ThisChainOf, + LaneIdOf, >(params.lane, params.inbound_lane_data, params.proof_params); // update runtime storage diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index fed0d15cc08..1d4043fc4b6 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -21,7 +21,7 @@ use bp_header_chain::ChainWithGrandpa; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::PayRewardFromAccount; @@ -70,7 +70,7 @@ pub type BridgedChainHeader = sp_runtime::generic::Header; /// Rewards payment procedure. -pub type TestPaymentProcedure = PayRewardFromAccount; +pub type TestPaymentProcedure = PayRewardFromAccount; /// Stake that we are using in tests. pub type TestStake = ConstU64<5_000>; /// Stake and slash mechanism to use in tests. @@ -83,10 +83,11 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< ConstU32<8>, >; -/// Message lane used in tests. -#[allow(unused)] -pub fn test_lane_id() -> LaneId { - LaneId::new(1, 2) +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; +/// Lane that we're using in tests. +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() } /// Bridged chain id used in tests. @@ -189,10 +190,10 @@ impl pallet_bridge_messages::Config for TestRuntime { type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type OutboundPayload = Vec; - type InboundPayload = Vec; - type DeliveryPayments = (); + type LaneId = TestLaneIdType; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -213,13 +214,14 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; type WeightInfo = (); + type LaneId = TestLaneIdType; } /// Dummy message dispatcher. pub struct DummyMessageDispatch; impl DummyMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -227,18 +229,21 @@ impl DummyMessageDispatch { impl MessageDispatch for DummyMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs index c990e8a12f3..485fb3d31f2 100644 --- a/bridges/chains/chain-bridge-hub-kusama/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-kusama/src/lib.rs @@ -93,4 +93,4 @@ pub const WITH_BRIDGE_HUB_KUSAMA_MESSAGES_PALLET_NAME: &str = "BridgeKusamaMessa pub const WITH_BRIDGE_HUB_KUSAMA_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; decl_bridge_finality_runtime_apis!(bridge_hub_kusama); -decl_bridge_messages_runtime_apis!(bridge_hub_kusama); +decl_bridge_messages_runtime_apis!(bridge_hub_kusama, LegacyLaneId); diff --git a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs index 7379b8863b1..7a1793b4da4 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-polkadot/src/lib.rs @@ -85,4 +85,4 @@ pub const WITH_BRIDGE_HUB_POLKADOT_MESSAGES_PALLET_NAME: &str = "BridgePolkadotM pub const WITH_BRIDGE_HUB_POLKADOT_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; decl_bridge_finality_runtime_apis!(bridge_hub_polkadot); -decl_bridge_messages_runtime_apis!(bridge_hub_polkadot); +decl_bridge_messages_runtime_apis!(bridge_hub_polkadot, LegacyLaneId); diff --git a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs index 7920eb93403..538bc44019f 100644 --- a/bridges/chains/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-rococo/src/lib.rs @@ -99,7 +99,7 @@ pub const WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX: u8 = 51; pub const WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX: u8 = 61; decl_bridge_finality_runtime_apis!(bridge_hub_rococo); -decl_bridge_messages_runtime_apis!(bridge_hub_rococo); +decl_bridge_messages_runtime_apis!(bridge_hub_rococo, LegacyLaneId); frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Rococo diff --git a/bridges/chains/chain-bridge-hub-westend/src/lib.rs b/bridges/chains/chain-bridge-hub-westend/src/lib.rs index 644fa64c687..7a213fdb28c 100644 --- a/bridges/chains/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/chains/chain-bridge-hub-westend/src/lib.rs @@ -88,7 +88,7 @@ pub const WITH_BRIDGE_HUB_WESTEND_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; pub const WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX: u8 = 44; decl_bridge_finality_runtime_apis!(bridge_hub_westend); -decl_bridge_messages_runtime_apis!(bridge_hub_westend); +decl_bridge_messages_runtime_apis!(bridge_hub_westend, LegacyLaneId); frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Westend diff --git a/bridges/chains/chain-polkadot-bulletin/src/lib.rs b/bridges/chains/chain-polkadot-bulletin/src/lib.rs index 88980a95750..d0093691972 100644 --- a/bridges/chains/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/chains/chain-polkadot-bulletin/src/lib.rs @@ -228,4 +228,4 @@ impl ChainWithMessages for PolkadotBulletin { } decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa); -decl_bridge_messages_runtime_apis!(polkadot_bulletin); +decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::HashedLaneId); diff --git a/bridges/modules/messages/src/benchmarking.rs b/bridges/modules/messages/src/benchmarking.rs index b3a4447fb02..355fb08ab28 100644 --- a/bridges/modules/messages/src/benchmarking.rs +++ b/bridges/modules/messages/src/benchmarking.rs @@ -26,7 +26,7 @@ use crate::{ use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages, - InboundLaneData, LaneId, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, + InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, }; use bp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams}; @@ -44,7 +44,7 @@ pub struct Pallet, I: 'static = ()>(crate::Pallet); /// Benchmark-specific message proof parameters. #[derive(Debug)] -pub struct MessageProofParams { +pub struct MessageProofParams { /// Id of the lane. pub lane: LaneId, /// Range of messages to include in the proof. @@ -62,7 +62,7 @@ pub struct MessageProofParams { /// Benchmark-specific message delivery proof parameters. #[derive(Debug)] -pub struct MessageDeliveryProofParams { +pub struct MessageDeliveryProofParams { /// Id of the lane. pub lane: LaneId, /// The proof needs to include this inbound lane data. @@ -74,8 +74,8 @@ pub struct MessageDeliveryProofParams { /// Trait that must be implemented by runtime. pub trait Config: crate::Config { /// Lane id to use in benchmarks. - fn bench_lane_id() -> LaneId { - LaneId::new(1, 2) + fn bench_lane_id() -> Self::LaneId { + Self::LaneId::default() } /// Return id of relayer account at the bridged chain. @@ -94,12 +94,12 @@ pub trait Config: crate::Config { /// Prepare messages proof to receive by the module. fn prepare_message_proof( - params: MessageProofParams, - ) -> (FromBridgedChainMessagesProof>>, Weight); + params: MessageProofParams, + ) -> (FromBridgedChainMessagesProof>, Self::LaneId>, Weight); /// Prepare messages delivery proof to receive by the module. fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> FromBridgedChainMessagesDeliveryProof>>; + params: MessageDeliveryProofParams, + ) -> FromBridgedChainMessagesDeliveryProof>, Self::LaneId>; /// Returns true if message has been successfully dispatched or not. fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool { diff --git a/bridges/modules/messages/src/call_ext.rs b/bridges/modules/messages/src/call_ext.rs index 8e021c8e5e2..9e5f5f8d112 100644 --- a/bridges/modules/messages/src/call_ext.rs +++ b/bridges/modules/messages/src/call_ext.rs @@ -20,8 +20,8 @@ use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pallet, LOG_TAR use bp_messages::{ target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData, - LaneId, MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, - ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, + MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, + UnrewardedRelayerOccupation, }; use bp_runtime::{AccountIdOf, OwnedBridgeModule}; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; @@ -39,7 +39,7 @@ impl, I: 'static> CallHelper { /// /// - call is `receive_messages_delivery_proof` and all messages confirmations have been /// received. - pub fn was_successful(info: &MessagesCallInfo) -> bool { + pub fn was_successful(info: &MessagesCallInfo) -> bool { match info { MessagesCallInfo::ReceiveMessagesProof(info) => { let inbound_lane_data = match InboundLanes::::get(info.base.lane_id) { @@ -75,19 +75,21 @@ pub trait CallSubType, I: 'static>: IsSubType, T>> { /// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call. - fn receive_messages_proof_info(&self) -> Option; + fn receive_messages_proof_info(&self) -> Option>; /// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from /// a `ReceiveMessagesDeliveryProof` call. - fn receive_messages_delivery_proof_info(&self) -> Option; + fn receive_messages_delivery_proof_info( + &self, + ) -> Option>; /// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call. - fn call_info(&self) -> Option; + fn call_info(&self) -> Option>; /// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof` /// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane. - fn call_info_for(&self, lane_id: LaneId) -> Option; + fn call_info_for(&self, lane_id: T::LaneId) -> Option>; /// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call: /// @@ -114,7 +116,7 @@ impl< I: 'static, > CallSubType for T::RuntimeCall { - fn receive_messages_proof_info(&self) -> Option { + fn receive_messages_proof_info(&self) -> Option> { if let Some(crate::Call::::receive_messages_proof { ref proof, .. }) = self.is_sub_type() { @@ -135,7 +137,9 @@ impl< None } - fn receive_messages_delivery_proof_info(&self) -> Option { + fn receive_messages_delivery_proof_info( + &self, + ) -> Option> { if let Some(crate::Call::::receive_messages_delivery_proof { ref proof, ref relayers_state, @@ -159,7 +163,7 @@ impl< None } - fn call_info(&self) -> Option { + fn call_info(&self) -> Option> { if let Some(info) = self.receive_messages_proof_info() { return Some(MessagesCallInfo::ReceiveMessagesProof(info)) } @@ -171,7 +175,7 @@ impl< None } - fn call_info_for(&self, lane_id: LaneId) -> Option { + fn call_info_for(&self, lane_id: T::LaneId) -> Option> { self.call_info().filter(|info| { let actual_lane_id = match info { MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id, @@ -251,10 +255,6 @@ mod tests { }; use sp_std::ops::RangeInclusive; - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } - fn fill_unrewarded_relayers() { let mut inbound_lane_state = InboundLanes::::get(test_lane_id()).unwrap(); for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX { diff --git a/bridges/modules/messages/src/inbound_lane.rs b/bridges/modules/messages/src/inbound_lane.rs index 65240feb719..91f1159f8f9 100644 --- a/bridges/modules/messages/src/inbound_lane.rs +++ b/bridges/modules/messages/src/inbound_lane.rs @@ -20,8 +20,8 @@ use crate::{BridgedChainOf, Config}; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, MessageKey, - MessageNonce, OutboundLaneData, ReceptionResult, UnrewardedRelayer, + ChainWithMessages, DeliveredMessages, InboundLaneData, LaneState, MessageKey, MessageNonce, + OutboundLaneData, ReceptionResult, UnrewardedRelayer, }; use bp_runtime::AccountIdOf; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; @@ -33,9 +33,11 @@ use sp_std::prelude::PartialEq; pub trait InboundLaneStorage { /// Id of relayer on source chain. type Relayer: Clone + PartialEq; + /// Lane identifier type. + type LaneId: Encode; /// Lane id. - fn id(&self) -> LaneId; + fn id(&self) -> Self::LaneId; /// Return maximal number of unrewarded relayer entries in inbound lane. fn max_unrewarded_relayer_entries(&self) -> MessageNonce; /// Return maximal number of unconfirmed messages in inbound lane. @@ -181,7 +183,7 @@ impl InboundLane { } /// Receive new message. - pub fn receive_message( + pub fn receive_message>( &mut self, relayer_at_bridged_chain: &S::Relayer, nonce: MessageNonce, diff --git a/bridges/modules/messages/src/lanes_manager.rs b/bridges/modules/messages/src/lanes_manager.rs index 4f5ac1c0a40..27cab48535d 100644 --- a/bridges/modules/messages/src/lanes_manager.rs +++ b/bridges/modules/messages/src/lanes_manager.rs @@ -21,8 +21,8 @@ use crate::{ }; use bp_messages::{ - target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneId, LaneState, - MessageKey, MessageNonce, OutboundLaneData, + target_chain::MessageDispatch, ChainWithMessages, InboundLaneData, LaneState, MessageKey, + MessageNonce, OutboundLaneData, }; use bp_runtime::AccountIdOf; use codec::{Decode, Encode, MaxEncodedLen}; @@ -68,7 +68,7 @@ impl, I: 'static> LanesManager { /// Create new inbound lane in `Opened` state. pub fn create_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { InboundLanes::::try_mutate(lane_id, |lane| match lane { Some(_) => Err(LanesManagerError::InboundLaneAlreadyExists), @@ -87,7 +87,7 @@ impl, I: 'static> LanesManager { /// Create new outbound lane in `Opened` state. pub fn create_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { OutboundLanes::::try_mutate(lane_id, |lane| match lane { Some(_) => Err(LanesManagerError::OutboundLaneAlreadyExists), @@ -103,7 +103,7 @@ impl, I: 'static> LanesManager { /// Get existing inbound lane, checking that it is in usable state. pub fn active_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, true)?)) } @@ -111,7 +111,7 @@ impl, I: 'static> LanesManager { /// Get existing outbound lane, checking that it is in usable state. pub fn active_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, true)?)) } @@ -119,7 +119,7 @@ impl, I: 'static> LanesManager { /// Get existing inbound lane without any additional state checks. pub fn any_state_inbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(InboundLane::new(RuntimeInboundLaneStorage::from_lane_id(lane_id, false)?)) } @@ -127,7 +127,7 @@ impl, I: 'static> LanesManager { /// Get existing outbound lane without any additional state checks. pub fn any_state_outbound_lane( &self, - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, LanesManagerError> { Ok(OutboundLane::new(RuntimeOutboundLaneStorage::from_lane_id(lane_id, false)?)) } @@ -135,14 +135,14 @@ impl, I: 'static> LanesManager { /// Runtime inbound lane storage. pub struct RuntimeInboundLaneStorage, I: 'static = ()> { - pub(crate) lane_id: LaneId, + pub(crate) lane_id: T::LaneId, pub(crate) cached_data: InboundLaneData>>, } impl, I: 'static> RuntimeInboundLaneStorage { /// Creates new runtime inbound lane storage for given **existing** lane. fn from_lane_id( - lane_id: LaneId, + lane_id: T::LaneId, check_active: bool, ) -> Result, LanesManagerError> { let cached_data = @@ -196,8 +196,9 @@ impl, I: 'static> RuntimeInboundLaneStorage { impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage { type Relayer = AccountIdOf>; + type LaneId = T::LaneId; - fn id(&self) -> LaneId { + fn id(&self) -> Self::LaneId { self.lane_id } @@ -225,15 +226,15 @@ impl, I: 'static> InboundLaneStorage for RuntimeInboundLaneStorage< /// Runtime outbound lane storage. #[derive(Debug, PartialEq, Eq)] -pub struct RuntimeOutboundLaneStorage { - pub(crate) lane_id: LaneId, +pub struct RuntimeOutboundLaneStorage, I: 'static> { + pub(crate) lane_id: T::LaneId, pub(crate) cached_data: OutboundLaneData, pub(crate) _phantom: PhantomData<(T, I)>, } impl, I: 'static> RuntimeOutboundLaneStorage { /// Creates new runtime outbound lane storage for given **existing** lane. - fn from_lane_id(lane_id: LaneId, check_active: bool) -> Result { + fn from_lane_id(lane_id: T::LaneId, check_active: bool) -> Result { let cached_data = OutboundLanes::::get(lane_id).ok_or(LanesManagerError::UnknownOutboundLane)?; ensure!( @@ -246,8 +247,9 @@ impl, I: 'static> RuntimeOutboundLaneStorage { impl, I: 'static> OutboundLaneStorage for RuntimeOutboundLaneStorage { type StoredMessagePayload = StoredMessagePayload; + type LaneId = T::LaneId; - fn id(&self) -> LaneId { + fn id(&self) -> Self::LaneId { self.lane_id } diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index b7fe1c7dbb1..af14257db99 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -60,9 +60,9 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch, ProvedLaneMessages, ProvedMessages, }, - ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, LaneId, - MessageKey, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, - OutboundMessageDetails, UnrewardedRelayersState, VerificationError, + ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey, + MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, + UnrewardedRelayersState, VerificationError, }; use bp_runtime::{ AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt, @@ -97,7 +97,7 @@ pub const LOG_TARGET: &str = "runtime::bridge-messages"; #[frame_support::pallet] pub mod pallet { use super::*; - use bp_messages::{ReceivedMessages, ReceptionResult}; + use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult}; use bp_runtime::RangeInclusiveExt; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -123,17 +123,25 @@ pub mod pallet { type OutboundPayload: Parameter + Size; /// Payload type of inbound messages. This payload is dispatched on this chain. type InboundPayload: Decode; + /// Lane identifier type. + type LaneId: LaneIdType; /// Handler for relayer payments that happen during message delivery transaction. type DeliveryPayments: DeliveryPayments; /// Handler for relayer payments that happen during message delivery confirmation /// transaction. - type DeliveryConfirmationPayments: DeliveryConfirmationPayments; + type DeliveryConfirmationPayments: DeliveryConfirmationPayments< + Self::AccountId, + Self::LaneId, + >; /// Delivery confirmation callback. - type OnMessagesDelivered: OnMessagesDelivered; + type OnMessagesDelivered: OnMessagesDelivered; /// Message dispatch handler. - type MessageDispatch: MessageDispatch; + type MessageDispatch: MessageDispatch< + DispatchPayload = Self::InboundPayload, + LaneId = Self::LaneId, + >; } /// Shortcut to this chain type for Config. @@ -142,6 +150,8 @@ pub mod pallet { pub type BridgedChainOf = >::BridgedChain; /// Shortcut to bridged header chain type for Config. pub type BridgedHeaderChainOf = >::BridgedHeaderChain; + /// Shortcut to lane identifier type for Config. + pub type LaneIdOf = >::LaneId; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] @@ -203,7 +213,7 @@ pub mod pallet { pub fn receive_messages_proof( origin: OriginFor, relayer_id_at_bridged_chain: AccountIdOf>, - proof: Box>>>, + proof: Box>, T::LaneId>>, messages_count: u32, dispatch_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -350,7 +360,7 @@ pub mod pallet { ))] pub fn receive_messages_delivery_proof( origin: OriginFor, - proof: FromBridgedChainMessagesDeliveryProof>>, + proof: FromBridgedChainMessagesDeliveryProof>, T::LaneId>, mut relayers_state: UnrewardedRelayersState, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; @@ -387,7 +397,7 @@ pub mod pallet { // emit 'delivered' event let received_range = confirmed_messages.begin..=confirmed_messages.end; Self::deposit_event(Event::MessagesDelivered { - lane_id, + lane_id: lane_id.into(), messages: confirmed_messages, }); @@ -441,19 +451,22 @@ pub mod pallet { /// Message has been accepted and is waiting to be delivered. MessageAccepted { /// Lane, which has accepted the message. - lane_id: LaneId, + lane_id: T::LaneId, /// Nonce of accepted message. nonce: MessageNonce, }, /// Messages have been received from the bridged chain. MessagesReceived( /// Result of received messages dispatch. - ReceivedMessages<::DispatchLevelResult>, + ReceivedMessages< + ::DispatchLevelResult, + T::LaneId, + >, ), /// Messages in the inclusive range have been delivered to the bridged chain. MessagesDelivered { /// Lane for which the delivery has been confirmed. - lane_id: LaneId, + lane_id: T::LaneId, /// Delivered messages. messages: DeliveredMessages, }, @@ -510,13 +523,13 @@ pub mod pallet { /// Map of lane id => inbound lane data. #[pallet::storage] pub type InboundLanes, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, LaneId, StoredInboundLaneData, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData, OptionQuery>; /// Map of lane id => outbound lane data. #[pallet::storage] pub type OutboundLanes, I: 'static = ()> = StorageMap< Hasher = Blake2_128Concat, - Key = LaneId, + Key = T::LaneId, Value = OutboundLaneData, QueryKind = OptionQuery, >; @@ -524,7 +537,7 @@ pub mod pallet { /// All queued outbound messages. #[pallet::storage] pub type OutboundMessages, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; + StorageMap<_, Blake2_128Concat, MessageKey, StoredMessagePayload>; #[pallet::genesis_config] #[derive(DefaultNoBound)] @@ -534,7 +547,7 @@ pub mod pallet { /// Initial pallet owner. pub owner: Option, /// Opened lanes. - pub opened_lanes: Vec, + pub opened_lanes: Vec, /// Dummy marker. #[serde(skip)] pub _phantom: sp_std::marker::PhantomData, @@ -565,13 +578,16 @@ pub mod pallet { impl, I: 'static> Pallet { /// Get stored data of the outbound message with given nonce. - pub fn outbound_message_data(lane: LaneId, nonce: MessageNonce) -> Option { + pub fn outbound_message_data( + lane: T::LaneId, + nonce: MessageNonce, + ) -> Option { OutboundMessages::::get(MessageKey { lane_id: lane, nonce }).map(Into::into) } /// Prepare data, related to given inbound message. pub fn inbound_message_data( - lane: LaneId, + lane: T::LaneId, payload: MessagePayload, outbound_details: OutboundMessageDetails, ) -> InboundMessageDetails { @@ -585,13 +601,13 @@ pub mod pallet { } /// Return outbound lane data. - pub fn outbound_lane_data(lane: LaneId) -> Option { + pub fn outbound_lane_data(lane: T::LaneId) -> Option { OutboundLanes::::get(lane) } /// Return inbound lane data. pub fn inbound_lane_data( - lane: LaneId, + lane: T::LaneId, ) -> Option>>> { InboundLanes::::get(lane).map(|lane| lane.0) } @@ -654,12 +670,12 @@ pub mod pallet { /// to send it on the bridge. #[derive(Debug, PartialEq, Eq)] pub struct SendMessageArgs, I: 'static> { - lane_id: LaneId, + lane_id: T::LaneId, lane: OutboundLane>, payload: StoredMessagePayload, } -impl bp_messages::source_chain::MessagesBridge for Pallet +impl bp_messages::source_chain::MessagesBridge for Pallet where T: Config, I: 'static, @@ -668,7 +684,7 @@ where type SendMessageArgs = SendMessageArgs; fn validate_message( - lane_id: LaneId, + lane_id: T::LaneId, message: &T::OutboundPayload, ) -> Result, Self::Error> { // we can't accept any messages if the pallet is halted @@ -703,7 +719,10 @@ where message_len, ); - Pallet::::deposit_event(Event::MessageAccepted { lane_id: args.lane_id, nonce }); + Pallet::::deposit_event(Event::MessageAccepted { + lane_id: args.lane_id.into(), + nonce, + }); SendMessageArtifacts { nonce, enqueued_messages } } @@ -722,7 +741,7 @@ fn ensure_normal_operating_mode, I: 'static>() -> Result<(), Error< /// Creates new inbound lane object, backed by runtime storage. Lane must be active. fn active_inbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_inbound_lane(lane_id) @@ -731,7 +750,7 @@ fn active_inbound_lane, I: 'static>( /// Creates new outbound lane object, backed by runtime storage. Lane must be active. fn active_outbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .active_outbound_lane(lane_id) @@ -740,7 +759,7 @@ fn active_outbound_lane, I: 'static>( /// Creates new outbound lane object, backed by runtime storage. fn any_state_outbound_lane, I: 'static>( - lane_id: LaneId, + lane_id: T::LaneId, ) -> Result>, Error> { LanesManager::::new() .any_state_outbound_lane(lane_id) @@ -749,9 +768,12 @@ fn any_state_outbound_lane, I: 'static>( /// Verify messages proof and return proved messages with decoded payload. fn verify_and_decode_messages_proof, I: 'static>( - proof: FromBridgedChainMessagesProof>>, + proof: FromBridgedChainMessagesProof>, T::LaneId>, messages_count: u32, -) -> Result>, VerificationError> { +) -> Result< + ProvedMessages>, + VerificationError, +> { // `receive_messages_proof` weight formula and `MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX` // check guarantees that the `message_count` is sane and Vec may be allocated. // (tx with too many messages will either be rejected from the pool, or will fail earlier) diff --git a/bridges/modules/messages/src/outbound_lane.rs b/bridges/modules/messages/src/outbound_lane.rs index f71240ab7c7..c72713e7455 100644 --- a/bridges/modules/messages/src/outbound_lane.rs +++ b/bridges/modules/messages/src/outbound_lane.rs @@ -19,7 +19,7 @@ use crate::{Config, LOG_TARGET}; use bp_messages::{ - ChainWithMessages, DeliveredMessages, LaneId, LaneState, MessageNonce, OutboundLaneData, + ChainWithMessages, DeliveredMessages, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer, }; use codec::{Decode, Encode}; @@ -32,9 +32,11 @@ use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeIn pub trait OutboundLaneStorage { /// Stored message payload type. type StoredMessagePayload; + /// Lane identifier type. + type LaneId: Encode; /// Lane id. - fn id(&self) -> LaneId; + fn id(&self) -> Self::LaneId; /// Get lane data from the storage. fn data(&self) -> OutboundLaneData; /// Update lane data in the storage. diff --git a/bridges/modules/messages/src/proofs.rs b/bridges/modules/messages/src/proofs.rs index f35eb24d98c..dcd642341d7 100644 --- a/bridges/modules/messages/src/proofs.rs +++ b/bridges/modules/messages/src/proofs.rs @@ -22,7 +22,7 @@ use bp_header_chain::{HeaderChain, HeaderChainError}; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::{FromBridgedChainMessagesProof, ProvedLaneMessages, ProvedMessages}, - ChainWithMessages, InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, + ChainWithMessages, InboundLaneData, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData, VerificationError, }; use bp_runtime::{ @@ -32,8 +32,8 @@ use codec::Decode; use sp_std::vec::Vec; /// 'Parsed' message delivery proof - inbound lane id and its state. -pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = - (LaneId, InboundLaneData<::AccountId>); +pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = + (>::LaneId, InboundLaneData<::AccountId>); /// Verify proof of Bridged -> This chain messages. /// @@ -44,9 +44,9 @@ pub(crate) type ParsedMessagesDeliveryProofFromBridgedChain = /// outside of this function. This function only verifies that the proof declares exactly /// `messages_count` messages. pub fn verify_messages_proof, I: 'static>( - proof: FromBridgedChainMessagesProof>>, + proof: FromBridgedChainMessagesProof>, T::LaneId>, messages_count: u32, -) -> Result, VerificationError> { +) -> Result>, VerificationError> { let FromBridgedChainMessagesProof { bridged_header_hash, storage_proof, @@ -103,8 +103,8 @@ pub fn verify_messages_proof, I: 'static>( /// Verify proof of This -> Bridged chain messages delivery. pub fn verify_messages_delivery_proof, I: 'static>( - proof: FromBridgedChainMessagesDeliveryProof>>, -) -> Result, VerificationError> { + proof: FromBridgedChainMessagesDeliveryProof>, T::LaneId>, +) -> Result, VerificationError> { let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } = proof; let mut parser: MessagesStorageProofAdapter = MessagesStorageProofAdapter::try_new_with_verified_storage_proof( @@ -143,7 +143,7 @@ trait StorageProofAdapter, I: 'static> { fn read_and_decode_outbound_lane_data( &mut self, - lane_id: &LaneId, + lane_id: &T::LaneId, ) -> Result, StorageProofError> { let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key( T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, @@ -154,7 +154,7 @@ trait StorageProofAdapter, I: 'static> { fn read_and_decode_message_payload( &mut self, - message_key: &MessageKey, + message_key: &MessageKey, ) -> Result { let storage_message_key = bp_messages::storage_keys::message_key( T::ThisChain::WITH_CHAIN_MESSAGES_PALLET_NAME, @@ -229,19 +229,20 @@ mod tests { encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec, add_duplicate_key: bool, add_unused_key: bool, - test: impl Fn(FromBridgedChainMessagesProof) -> R, + test: impl Fn(FromBridgedChainMessagesProof) -> R, ) -> R { - let (state_root, storage_proof) = prepare_messages_storage_proof::( - test_lane_id(), - 1..=nonces_end, - outbound_lane_data, - bp_runtime::UnverifiedStorageProofParams::default(), - generate_dummy_message, - encode_message, - encode_outbound_lane_data, - add_duplicate_key, - add_unused_key, - ); + let (state_root, storage_proof) = + prepare_messages_storage_proof::( + test_lane_id(), + 1..=nonces_end, + outbound_lane_data, + bp_runtime::UnverifiedStorageProofParams::default(), + generate_dummy_message, + encode_message, + encode_outbound_lane_data, + add_duplicate_key, + add_unused_key, + ); sp_io::TestExternalities::new(Default::default()).execute_with(move || { let bridged_header = BridgedChainHeader::new( diff --git a/bridges/modules/messages/src/tests/messages_generation.rs b/bridges/modules/messages/src/tests/messages_generation.rs index 6c4867fa6de..00b1d3eefe4 100644 --- a/bridges/modules/messages/src/tests/messages_generation.rs +++ b/bridges/modules/messages/src/tests/messages_generation.rs @@ -17,8 +17,8 @@ //! Helpers for generating message storage proofs, that are used by tests and by benchmarks. use bp_messages::{ - storage_keys, ChainWithMessages, InboundLaneData, LaneId, MessageKey, MessageNonce, - MessagePayload, OutboundLaneData, + storage_keys, ChainWithMessages, InboundLaneData, MessageKey, MessageNonce, MessagePayload, + OutboundLaneData, }; use bp_runtime::{ grow_storage_value, record_all_trie_keys, AccountIdOf, Chain, HashOf, HasherOf, @@ -47,7 +47,11 @@ pub fn encode_lane_data(d: &OutboundLaneData) -> Vec { /// /// Returns state trie root and nodes with prepared messages. #[allow(clippy::too_many_arguments)] -pub fn prepare_messages_storage_proof( +pub fn prepare_messages_storage_proof< + BridgedChain: Chain, + ThisChain: ChainWithMessages, + LaneId: Encode + Copy, +>( lane: LaneId, message_nonces: RangeInclusive, outbound_lane_data: Option, @@ -132,7 +136,11 @@ where /// Prepare storage proof of given messages delivery. /// /// Returns state trie root and nodes with prepared messages. -pub fn prepare_message_delivery_storage_proof( +pub fn prepare_message_delivery_storage_proof< + BridgedChain: Chain, + ThisChain: ChainWithMessages, + LaneId: Encode, +>( lane: LaneId, inbound_lane_data: InboundLaneData>, proof_params: UnverifiedStorageProofParams, diff --git a/bridges/modules/messages/src/tests/mock.rs b/bridges/modules/messages/src/tests/mock.rs index 2caea9813e8..2935ebd6961 100644 --- a/bridges/modules/messages/src/tests/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -35,8 +35,9 @@ use bp_messages::{ DeliveryPayments, DispatchMessage, DispatchMessageData, FromBridgedChainMessagesProof, MessageDispatch, }, - ChainWithMessages, DeliveredMessages, InboundLaneData, LaneId, LaneState, Message, MessageKey, - MessageNonce, OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState, + ChainWithMessages, DeliveredMessages, HashedLaneId, InboundLaneData, LaneIdType, LaneState, + Message, MessageKey, MessageNonce, OutboundLaneData, UnrewardedRelayer, + UnrewardedRelayersState, }; use bp_runtime::{ messages::MessageDispatchResult, Chain, ChainId, Size, UnverifiedStorageProofParams, @@ -195,10 +196,10 @@ impl Config for TestRuntime { type BridgedHeaderChain = BridgedChainGrandpa; type OutboundPayload = TestPayload; - type InboundPayload = TestPayload; - type DeliveryPayments = TestDeliveryPayments; + type LaneId = TestLaneIdType; + type DeliveryPayments = TestDeliveryPayments; type DeliveryConfirmationPayments = TestDeliveryConfirmationPayments; type OnMessagesDelivered = TestOnMessagesDelivered; @@ -207,13 +208,13 @@ impl Config for TestRuntime { #[cfg(feature = "runtime-benchmarks")] impl crate::benchmarking::Config<()> for TestRuntime { - fn bench_lane_id() -> LaneId { + fn bench_lane_id() -> Self::LaneId { test_lane_id() } fn prepare_message_proof( - params: crate::benchmarking::MessageProofParams, - ) -> (FromBridgedChainMessagesProof, Weight) { + params: crate::benchmarking::MessageProofParams, + ) -> (FromBridgedChainMessagesProof, Weight) { use bp_runtime::RangeInclusiveExt; let dispatch_weight = @@ -228,8 +229,8 @@ impl crate::benchmarking::Config<()> for TestRuntime { } fn prepare_message_delivery_proof( - params: crate::benchmarking::MessageDeliveryProofParams, - ) -> FromBridgedChainMessagesDeliveryProof { + params: crate::benchmarking::MessageDeliveryProofParams, + ) -> FromBridgedChainMessagesDeliveryProof { // in mock run we only care about benchmarks correctness, not the benchmark results // => ignore size related arguments prepare_messages_delivery_proof(params.lane, params.inbound_lane_data) @@ -258,19 +259,21 @@ pub const TEST_RELAYER_B: AccountId = 101; /// Account id of additional test relayer - C. pub const TEST_RELAYER_C: AccountId = 102; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; /// Lane that we're using in tests. -pub fn test_lane_id() -> LaneId { - LaneId::new(1, 2) +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() } /// Lane that is completely unknown to our runtime. -pub fn unknown_lane_id() -> LaneId { - LaneId::new(1, 3) +pub fn unknown_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 3).unwrap() } /// Lane that is registered, but it is closed. -pub fn closed_lane_id() -> LaneId { - LaneId::new(1, 4) +pub fn closed_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 4).unwrap() } /// Regular message payload. @@ -316,11 +319,11 @@ impl TestDeliveryConfirmationPayments { } } -impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { +impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayments { type Error = &'static str; fn pay_reward( - _lane_id: LaneId, + _lane_id: TestLaneIdType, messages_relayers: VecDeque>, _confirmation_relayer: &AccountId, received_range: &RangeInclusive, @@ -341,7 +344,7 @@ impl DeliveryConfirmationPayments for TestDeliveryConfirmationPayment pub struct TestMessageDispatch; impl TestMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { // "enqueue" enough (to deactivate dispatcher) messages at dispatcher let latest_received_nonce = BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX + 1; for _ in 1..=latest_received_nonce { @@ -349,7 +352,7 @@ impl TestMessageDispatch { } } - pub fn emulate_enqueued_message(lane: LaneId) { + pub fn emulate_enqueued_message(lane: TestLaneIdType) { let key = (b"dispatched", lane).encode(); let dispatched = frame_support::storage::unhashed::get_or_default::(&key[..]); frame_support::storage::unhashed::put(&key[..], &(dispatched + 1)); @@ -359,14 +362,15 @@ impl TestMessageDispatch { impl MessageDispatch for TestMessageDispatch { type DispatchPayload = TestPayload; type DispatchLevelResult = TestDispatchLevelResult; + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::get_or_default::( &(b"dispatched", lane).encode()[..], ) <= BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX } - fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + fn dispatch_weight(message: &mut DispatchMessage) -> Weight { match message.data.payload.as_ref() { Ok(payload) => payload.declared_weight, Err(_) => Weight::zero(), @@ -374,7 +378,7 @@ impl MessageDispatch for TestMessageDispatch { } fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult { match message.data.payload.as_ref() { Ok(payload) => { @@ -390,13 +394,13 @@ impl MessageDispatch for TestMessageDispatch { pub struct TestOnMessagesDelivered; impl TestOnMessagesDelivered { - pub fn call_arguments() -> Option<(LaneId, MessageNonce)> { + pub fn call_arguments() -> Option<(TestLaneIdType, MessageNonce)> { frame_support::storage::unhashed::get(b"TestOnMessagesDelivered.OnMessagesDelivered") } } -impl OnMessagesDelivered for TestOnMessagesDelivered { - fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { +impl OnMessagesDelivered for TestOnMessagesDelivered { + fn on_messages_delivered(lane: TestLaneIdType, enqueued_messages: MessageNonce) { frame_support::storage::unhashed::put( b"TestOnMessagesDelivered.OnMessagesDelivered", &(lane, enqueued_messages), @@ -405,7 +409,7 @@ impl OnMessagesDelivered for TestOnMessagesDelivered { } /// Return test lane message with given nonce and payload. -pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { +pub fn message(nonce: MessageNonce, payload: TestPayload) -> Message { Message { key: MessageKey { lane_id: test_lane_id(), nonce }, payload: payload.encode() } } @@ -449,7 +453,7 @@ pub fn unrewarded_relayer( } /// Returns unrewarded relayers state at given lane. -pub fn inbound_unrewarded_relayers_state(lane: bp_messages::LaneId) -> UnrewardedRelayersState { +pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRelayersState { let inbound_lane_data = crate::InboundLanes::::get(lane).unwrap().0; UnrewardedRelayersState::from(&inbound_lane_data) } @@ -486,24 +490,25 @@ pub fn run_test(test: impl FnOnce() -> T) -> T { /// Since this function changes the runtime storage, you can't "inline" it in the /// `asset_noop` macro calls. pub fn prepare_messages_proof( - messages: Vec, + messages: Vec>, outbound_lane_data: Option, -) -> Box> { +) -> Box> { // first - let's generate storage proof let lane = messages.first().unwrap().key.lane_id; let nonces_start = messages.first().unwrap().key.nonce; let nonces_end = messages.last().unwrap().key.nonce; - let (storage_root, storage_proof) = prepare_messages_storage_proof::( - lane, - nonces_start..=nonces_end, - outbound_lane_data, - UnverifiedStorageProofParams::default(), - |nonce| messages[(nonce - nonces_start) as usize].payload.clone(), - encode_all_messages, - encode_lane_data, - false, - false, - ); + let (storage_root, storage_proof) = + prepare_messages_storage_proof::( + lane, + nonces_start..=nonces_end, + outbound_lane_data, + UnverifiedStorageProofParams::default(), + |nonce| messages[(nonce - nonces_start) as usize].payload.clone(), + encode_all_messages, + encode_lane_data, + false, + false, + ); // let's now insert bridged chain header into the storage let bridged_header_hash = Default::default(); @@ -512,7 +517,7 @@ pub fn prepare_messages_proof( StoredHeaderData { number: 0, state_root: storage_root }, ); - Box::new(FromBridgedChainMessagesProof:: { + Box::new(FromBridgedChainMessagesProof:: { bridged_header_hash, storage_proof, lane, @@ -527,12 +532,12 @@ pub fn prepare_messages_proof( /// Since this function changes the runtime storage, you can't "inline" it in the /// `asset_noop` macro calls. pub fn prepare_messages_delivery_proof( - lane: LaneId, + lane: TestLaneIdType, inbound_lane_data: InboundLaneData, -) -> FromBridgedChainMessagesDeliveryProof { +) -> FromBridgedChainMessagesDeliveryProof { // first - let's generate storage proof let (storage_root, storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane, inbound_lane_data, UnverifiedStorageProofParams::default(), @@ -545,7 +550,7 @@ pub fn prepare_messages_delivery_proof( StoredHeaderData { number: 0, state_root: storage_root }, ); - FromBridgedChainMessagesDeliveryProof:: { + FromBridgedChainMessagesDeliveryProof:: { bridged_header_hash, storage_proof, lane, diff --git a/bridges/modules/messages/src/tests/pallet_tests.rs b/bridges/modules/messages/src/tests/pallet_tests.rs index ceb1744c066..9df103a7cf6 100644 --- a/bridges/modules/messages/src/tests/pallet_tests.rs +++ b/bridges/modules/messages/src/tests/pallet_tests.rs @@ -30,7 +30,7 @@ use bp_messages::{ source_chain::{FromBridgedChainMessagesDeliveryProof, MessagesBridge}, target_chain::{FromBridgedChainMessagesProof, MessageDispatch}, BridgeMessagesCall, ChainWithMessages, DeliveredMessages, InboundLaneData, - InboundMessageDetails, LaneId, LaneState, MessageKey, MessageNonce, MessagesOperatingMode, + InboundMessageDetails, LaneIdType, LaneState, MessageKey, MessageNonce, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, UnrewardedRelayer, UnrewardedRelayersState, VerificationError, }; @@ -51,7 +51,7 @@ fn get_ready_for_events() { System::::reset_events(); } -fn send_regular_message(lane_id: LaneId) { +fn send_regular_message(lane_id: TestLaneIdType) { get_ready_for_events(); let outbound_lane = active_outbound_lane::(lane_id).unwrap(); @@ -67,7 +67,10 @@ fn send_regular_message(lane_id: LaneId) { System::::events(), vec![EventRecord { phase: Phase::Initialization, - event: TestEvent::Messages(Event::MessageAccepted { lane_id, nonce: message_nonce }), + event: TestEvent::Messages(Event::MessageAccepted { + lane_id: lane_id.into(), + nonce: message_nonce + }), topics: vec![], }], ); @@ -105,7 +108,7 @@ fn receive_messages_delivery_proof() { vec![EventRecord { phase: Phase::Initialization, event: TestEvent::Messages(Event::MessagesDelivered { - lane_id: test_lane_id(), + lane_id: test_lane_id().into(), messages: DeliveredMessages::new(1), }), topics: vec![], @@ -629,7 +632,7 @@ fn receive_messages_delivery_proof_rewards_relayers() { fn receive_messages_delivery_proof_rejects_invalid_proof() { run_test(|| { let mut proof = prepare_messages_delivery_proof(test_lane_id(), Default::default()); - proof.lane = bp_messages::LaneId::new(42, 84); + proof.lane = TestLaneIdType::try_new(42, 84).unwrap(); assert_noop!( Pallet::::receive_messages_delivery_proof( @@ -1038,8 +1041,8 @@ fn test_bridge_messages_call_is_correctly_defined() { }; let indirect_receive_messages_proof_call = BridgeMessagesCall::< AccountId, - FromBridgedChainMessagesProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, >::receive_messages_proof { relayer_id_at_bridged_chain: account_id, proof: *message_proof, @@ -1058,8 +1061,8 @@ fn test_bridge_messages_call_is_correctly_defined() { }; let indirect_receive_messages_delivery_proof_call = BridgeMessagesCall::< AccountId, - FromBridgedChainMessagesProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesProof, + FromBridgedChainMessagesDeliveryProof, >::receive_messages_delivery_proof { proof: message_delivery_proof, relayers_state: unrewarded_relayer_state, @@ -1084,7 +1087,7 @@ fn inbound_storage_extra_proof_size_bytes_works() { fn storage(relayer_entries: usize) -> RuntimeInboundLaneStorage { RuntimeInboundLaneStorage { - lane_id: LaneId::new(1, 2), + lane_id: TestLaneIdType::try_new(1, 2).unwrap(), cached_data: InboundLaneData { state: LaneState::Opened, relayers: vec![relayer_entry(); relayer_entries].into(), @@ -1165,7 +1168,7 @@ fn receive_messages_proof_fails_if_inbound_lane_is_not_opened() { #[test] fn receive_messages_delivery_proof_fails_if_outbound_lane_is_unknown() { run_test(|| { - let make_proof = |lane: LaneId| { + let make_proof = |lane: TestLaneIdType| { prepare_messages_delivery_proof( lane, InboundLaneData { diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 8a3f905a8f2..8fe3fc11d6a 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -20,9 +20,8 @@ use crate::*; -use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::One; @@ -30,27 +29,34 @@ use sp_runtime::traits::One; const REWARD_AMOUNT: u32 = u32::MAX; /// Pallet we're benchmarking here. -pub struct Pallet(crate::Pallet); +pub struct Pallet, I: 'static = ()>(crate::Pallet); /// Trait that must be implemented by runtime. -pub trait Config: crate::Config { +pub trait Config: crate::Config { + /// Lane id to use in benchmarks. + fn bench_lane_id() -> Self::LaneId { + Self::LaneId::default() + } /// Prepare environment for paying given reward for serving given lane. - fn prepare_rewards_account(account_params: RewardsAccountParams, reward: Self::Reward); + fn prepare_rewards_account( + account_params: RewardsAccountParams, + reward: Self::Reward, + ); /// Give enough balance to given account. fn deposit_account(account: Self::AccountId, balance: Self::Reward); } -benchmarks! { +benchmarks_instance_pallet! { // Benchmark `claim_rewards` call. claim_rewards { - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let account_params = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); let relayer: T::AccountId = whitelisted_caller(); let reward = T::Reward::from(REWARD_AMOUNT); T::prepare_rewards_account(account_params, reward); - RelayerRewards::::insert(&relayer, account_params, reward); + RelayerRewards::::insert(&relayer, account_params, reward); }: _(RawOrigin::Signed(relayer), account_params) verify { // we can't check anything here, because `PaymentProcedure` is responsible for @@ -62,30 +68,30 @@ benchmarks! { register { let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); }: _(RawOrigin::Signed(relayer.clone()), valid_till) verify { - assert!(crate::Pallet::::is_registration_active(&relayer)); + assert!(crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `deregister` call. deregister { let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); frame_system::Pallet::::set_block_number(valid_till.saturating_add(One::one())); }: _(RawOrigin::Signed(relayer.clone())) verify { - assert!(!crate::Pallet::::is_registration_active(&relayer)); + assert!(!crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `slash_and_deregister` method of the pallet. We are adding this weight to @@ -95,36 +101,36 @@ benchmarks! { // prepare and register relayer account let relayer: T::AccountId = whitelisted_caller(); let valid_till = frame_system::Pallet::::block_number() - .saturating_add(crate::Pallet::::required_registration_lease()) + .saturating_add(crate::Pallet::::required_registration_lease()) .saturating_add(One::one()) .saturating_add(One::one()); - T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); - crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); + T::deposit_account(relayer.clone(), crate::Pallet::::required_stake()); + crate::Pallet::::register(RawOrigin::Signed(relayer.clone()).into(), valid_till).unwrap(); // create slash destination account - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); T::prepare_rewards_account(slash_destination, Zero::zero()); }: { - crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) + crate::Pallet::::slash_and_deregister(&relayer, slash_destination.into()) } verify { - assert!(!crate::Pallet::::is_registration_active(&relayer)); + assert!(!crate::Pallet::::is_registration_active(&relayer)); } // Benchmark `register_relayer_reward` method of the pallet. We are adding this weight to // the weight of message delivery call if `RefundBridgedParachainMessages` signed extension // is deployed at runtime level. register_relayer_reward { - let lane = LaneId::new(1, 2); + let lane = T::bench_lane_id(); let relayer: T::AccountId = whitelisted_caller(); let account_params = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); }: { - crate::Pallet::::register_relayer_reward(account_params, &relayer, One::one()); + crate::Pallet::::register_relayer_reward(account_params, &relayer, One::one()); } verify { - assert_eq!(RelayerRewards::::get(relayer, &account_params), Some(One::one())); + assert_eq!(RelayerRewards::::get(relayer, &account_params), Some(One::one())); } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime) diff --git a/bridges/modules/relayers/src/extension/grandpa_adapter.rs b/bridges/modules/relayers/src/extension/grandpa_adapter.rs index 6c9ae1c2968..2a8a6e78ef9 100644 --- a/bridges/modules/relayers/src/extension/grandpa_adapter.rs +++ b/bridges/modules/relayers/src/extension/grandpa_adapter.rs @@ -30,7 +30,7 @@ use pallet_bridge_grandpa::{ SubmitFinalityProofHelper, }; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use sp_runtime::{ traits::{Dispatchable, Get}, @@ -54,6 +54,8 @@ pub struct WithGrandpaChainExtensionConfig< BridgeGrandpaPalletInstance, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, >( @@ -63,20 +65,22 @@ pub struct WithGrandpaChainExtensionConfig< BatchCallUnpacker, BridgeGrandpaPalletInstance, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, )>, ); -impl ExtensionConfig - for WithGrandpaChainExtensionConfig +impl ExtensionConfig + for WithGrandpaChainExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + R: BridgeRelayersConfig + BridgeMessagesConfig> + BridgeGrandpaConfig, BCU: BatchCallUnpacker, GI: 'static, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype @@ -85,14 +89,15 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let calls = BCU::unpack(call, 2); @@ -120,12 +125,12 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { verify_submit_finality_proof_succeeded::(call_info, call_data, relayer) && - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } @@ -134,7 +139,7 @@ where /// /// Only returns false when GRANDPA chain state update call has failed. pub(crate) fn verify_submit_finality_proof_succeeded( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool diff --git a/bridges/modules/relayers/src/extension/messages_adapter.rs b/bridges/modules/relayers/src/extension/messages_adapter.rs index ecb575524bb..e8c2088b7f2 100644 --- a/bridges/modules/relayers/src/extension/messages_adapter.rs +++ b/bridges/modules/relayers/src/extension/messages_adapter.rs @@ -23,7 +23,7 @@ use bp_relayers::{ExtensionCallData, ExtensionCallInfo, ExtensionConfig}; use bp_runtime::StaticStrProvider; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use sp_runtime::{ traits::{Dispatchable, Get}, @@ -37,6 +37,7 @@ pub struct WithMessagesExtensionConfig< IdProvider, Runtime, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, >( PhantomData<( @@ -46,16 +47,19 @@ pub struct WithMessagesExtensionConfig< Runtime, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, )>, ); -impl ExtensionConfig for WithMessagesExtensionConfig +impl ExtensionConfig for WithMessagesExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + BridgeMessagesConfig, + R: BridgeRelayersConfig + BridgeMessagesConfig, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeMessagesCallSubType, @@ -63,14 +67,15 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = (); + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let call = Self::check_obsolete_parsed_call(call)?; @@ -85,10 +90,10 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } diff --git a/bridges/modules/relayers/src/extension/mod.rs b/bridges/modules/relayers/src/extension/mod.rs index e1a7abd0ad1..9a248eb8e79 100644 --- a/bridges/modules/relayers/src/extension/mod.rs +++ b/bridges/modules/relayers/src/extension/mod.rs @@ -36,7 +36,9 @@ use frame_support::{ CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use frame_system::Config as SystemConfig; -use pallet_bridge_messages::{CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig}; +use pallet_bridge_messages::{ + CallHelper as MessagesCallHelper, Config as BridgeMessagesConfig, LaneIdOf, +}; use pallet_transaction_payment::{ Config as TransactionPaymentConfig, OnChargeTransaction, Pallet as TransactionPaymentPallet, }; @@ -62,15 +64,19 @@ mod priority; /// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`. #[cfg_attr(test, derive(Debug, PartialEq))] -pub struct PreDispatchData { +pub struct PreDispatchData< + AccountId, + RemoteGrandpaChainBlockNumber: Debug, + LaneId: Clone + Copy + Debug, +> { /// Transaction submitter (relayer) account. relayer: AccountId, /// Type of the call. - call_info: ExtensionCallInfo, + call_info: ExtensionCallInfo, } -impl - PreDispatchData +impl + PreDispatchData { /// Returns mutable reference to pre-dispatch `finality_target` sent to the /// `SubmitFinalityProof` call. @@ -88,13 +94,13 @@ impl /// The actions on relayer account that need to be performed because of his actions. #[derive(RuntimeDebug, PartialEq)] -pub enum RelayerAccountAction { +pub enum RelayerAccountAction { /// Do nothing with relayer account. None, /// Reward the relayer. - Reward(AccountId, RewardsAccountParams, Reward), + Reward(AccountId, RewardsAccountParams, Reward), /// Slash the relayer. - Slash(AccountId, RewardsAccountParams), + Slash(AccountId, RewardsAccountParams), } /// A signed extension, built around `pallet-bridge-relayers`. @@ -112,19 +118,22 @@ pub enum RelayerAccountAction { RuntimeDebugNoBound, TypeInfo, )] -#[scale_info(skip_type_params(Runtime, Config))] -pub struct BridgeRelayersSignedExtension(PhantomData<(Runtime, Config)>); +#[scale_info(skip_type_params(Runtime, Config, LaneId))] +pub struct BridgeRelayersSignedExtension( + PhantomData<(Runtime, Config, LaneId)>, +); -impl BridgeRelayersSignedExtension +impl BridgeRelayersSignedExtension where Self: 'static + Send + Sync, - R: RelayersConfig - + BridgeMessagesConfig + R: RelayersConfig + + BridgeMessagesConfig + TransactionPaymentConfig, - C: ExtensionConfig, + C: ExtensionConfig, R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + LaneId: Clone + Copy + Decode + Encode + Debug + TypeInfo, { /// Returns number of bundled messages `Some(_)`, if the given call info is a: /// @@ -136,7 +145,7 @@ where /// virtually boosted. The relayer registration (we only boost priority for registered /// relayer transactions) must be checked outside. fn bundled_messages_for_priority_boost( - call_info: Option<&ExtensionCallInfo>, + call_info: Option<&ExtensionCallInfo>, ) -> Option { // we only boost priority of message delivery transactions let parsed_call = match call_info { @@ -160,12 +169,14 @@ where /// Given post-dispatch information, analyze the outcome of relayer call and return /// actions that need to be performed on relayer account. fn analyze_call_result( - pre: Option>>, + pre: Option< + Option>, + >, info: &DispatchInfo, post_info: &PostDispatchInfo, len: usize, result: &DispatchResult, - ) -> RelayerAccountAction { + ) -> RelayerAccountAction { // We don't refund anything for transactions that we don't support. let (relayer, call_info) = match pre { Some(Some(pre)) => (pre.relayer, pre.call_info), @@ -263,22 +274,23 @@ where } } -impl SignedExtension for BridgeRelayersSignedExtension +impl SignedExtension for BridgeRelayersSignedExtension where Self: 'static + Send + Sync, - R: RelayersConfig - + BridgeMessagesConfig + R: RelayersConfig + + BridgeMessagesConfig + TransactionPaymentConfig, - C: ExtensionConfig, + C: ExtensionConfig, R::RuntimeCall: Dispatchable, ::OnChargeTransaction: OnChargeTransaction, + LaneId: Clone + Copy + Decode + Encode + Debug + TypeInfo, { const IDENTIFIER: &'static str = C::IdProvider::STR; type AccountId = R::AccountId; type Call = R::RuntimeCall; type AdditionalSigned = (); - type Pre = Option>; + type Pre = Option>; fn additional_signed(&self) -> Result<(), TransactionValidityError> { Ok(()) @@ -392,19 +404,23 @@ where } /// Verify that the messages pallet call, supported by extension has succeeded. -pub(crate) fn verify_messages_call_succeeded( - call_info: &ExtensionCallInfo, +pub(crate) fn verify_messages_call_succeeded( + call_info: &ExtensionCallInfo< + C::RemoteGrandpaChainBlockNumber, + LaneIdOf, + >, _call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool where C: ExtensionConfig, - MI: 'static, - C::Runtime: BridgeMessagesConfig, + C::Runtime: BridgeMessagesConfig, { let messages_call = call_info.messages_call_info(); - if !MessagesCallHelper::::was_successful(messages_call) { + if !MessagesCallHelper::::was_successful( + messages_call, + ) { log::trace!( target: LOG_TARGET, "{}.{:?}: relayer {:?} has submitted invalid messages call", @@ -427,9 +443,9 @@ mod tests { use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, BaseMessagesProofInfo, DeliveredMessages, - InboundLaneData, LaneId, MessageNonce, MessagesCallInfo, MessagesOperatingMode, - OutboundLaneData, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, - UnrewardedRelayer, UnrewardedRelayerOccupation, UnrewardedRelayersState, + InboundLaneData, MessageNonce, MessagesCallInfo, MessagesOperatingMode, OutboundLaneData, + ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayer, + UnrewardedRelayerOccupation, UnrewardedRelayersState, }; use bp_parachains::{BestParaHeadHash, ParaInfo, SubmitParachainHeadsInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; @@ -454,17 +470,16 @@ mod tests { parameter_types! { TestParachain: u32 = BridgedUnderlyingParachain::PARACHAIN_ID; - pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::ThisChain, ); - pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( + pub MsgDeliveryProofsRewardsAccount: RewardsAccountParams = RewardsAccountParams::new( test_lane_id(), TEST_BRIDGED_CHAIN_ID, RewardsAccountOwner::BridgedChain, ); - pub TestLaneId: LaneId = test_lane_id(); } bp_runtime::generate_static_str_provider!(TestGrandpaExtension); @@ -477,31 +492,31 @@ mod tests { RuntimeWithUtilityPallet, (), (), + (), ConstU64<1>, >; type TestGrandpaExtension = - BridgeRelayersSignedExtension; + BridgeRelayersSignedExtension; type TestExtensionConfig = parachain_adapter::WithParachainExtensionConfig< StrTestExtension, TestRuntime, RuntimeWithUtilityPallet, (), (), + (), ConstU64<1>, >; - type TestExtension = BridgeRelayersSignedExtension; + type TestExtension = + BridgeRelayersSignedExtension; type TestMessagesExtensionConfig = messages_adapter::WithMessagesExtensionConfig< StrTestMessagesExtension, TestRuntime, (), + (), ConstU64<1>, >; type TestMessagesExtension = - BridgeRelayersSignedExtension; - - fn test_lane_id() -> LaneId { - LaneId::new(1, 2) - } + BridgeRelayersSignedExtension; fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance { let test_stake: ThisChainBalance = Stake::get(); @@ -795,7 +810,7 @@ mod tests { } fn all_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::AllFinalityAndMsgs( @@ -832,14 +847,14 @@ mod tests { #[cfg(test)] fn all_finality_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = all_finality_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn all_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::AllFinalityAndMsgs( @@ -869,14 +884,14 @@ mod tests { } fn all_finality_confirmation_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = all_finality_confirmation_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn relay_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::RelayFinalityAndMsgs( @@ -906,14 +921,14 @@ mod tests { } fn relay_finality_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = relay_finality_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn relay_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::RelayFinalityAndMsgs( @@ -937,14 +952,14 @@ mod tests { } fn relay_finality_confirmation_pre_dispatch_data_ex( - ) -> PreDispatchData { + ) -> PreDispatchData { let mut data = relay_finality_confirmation_pre_dispatch_data(); data.submit_finality_proof_info_mut().unwrap().current_set_id = Some(TEST_GRANDPA_SET_ID); data } fn parachain_finality_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::ParachainFinalityAndMsgs( @@ -972,7 +987,7 @@ mod tests { } fn parachain_finality_confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::ParachainFinalityAndMsgs( @@ -994,7 +1009,7 @@ mod tests { } fn delivery_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesProof( @@ -1016,7 +1031,7 @@ mod tests { } fn confirmation_pre_dispatch_data( - ) -> PreDispatchData { + ) -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: ExtensionCallInfo::Msgs(MessagesCallInfo::ReceiveMessagesDeliveryProof( @@ -1030,9 +1045,13 @@ mod tests { } fn set_bundled_range_end( - mut pre_dispatch_data: PreDispatchData, + mut pre_dispatch_data: PreDispatchData< + ThisChainAccountId, + BridgedChainBlockNumber, + TestLaneIdType, + >, end: MessageNonce, - ) -> PreDispatchData { + ) -> PreDispatchData { let msg_info = match pre_dispatch_data.call_info { ExtensionCallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info, ExtensionCallInfo::RelayFinalityAndMsgs(_, ref mut info) => info, @@ -1072,7 +1091,7 @@ mod tests { fn run_pre_dispatch( call: RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { sp_tracing::try_init_simple(); @@ -1083,7 +1102,7 @@ mod tests { fn run_grandpa_pre_dispatch( call: RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let extension: TestGrandpaExtension = BridgeRelayersSignedExtension(PhantomData); @@ -1092,7 +1111,10 @@ mod tests { fn run_messages_pre_dispatch( call: RuntimeCall, - ) -> Result>, TransactionValidityError> { + ) -> Result< + Option>, + TransactionValidityError, + > { let extension: TestMessagesExtension = BridgeRelayersSignedExtension(PhantomData); extension.pre_dispatch(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0) } @@ -1113,7 +1135,9 @@ mod tests { } fn run_post_dispatch( - pre_dispatch_data: Option>, + pre_dispatch_data: Option< + PreDispatchData, + >, dispatch_result: DispatchResult, ) { let post_dispatch_result = TestExtension::post_dispatch( @@ -1886,9 +1910,13 @@ mod tests { } fn run_analyze_call_result( - pre_dispatch_data: PreDispatchData, + pre_dispatch_data: PreDispatchData< + ThisChainAccountId, + BridgedChainBlockNumber, + TestLaneIdType, + >, dispatch_result: DispatchResult, - ) -> RelayerAccountAction { + ) -> RelayerAccountAction { TestExtension::analyze_call_result( Some(Some(pre_dispatch_data)), &dispatch_info(), @@ -2318,7 +2346,7 @@ mod tests { .unwrap(); // allow empty message delivery transactions - let lane_id = TestLaneId::get(); + let lane_id = test_lane_id(); let in_lane_data = InboundLaneData { last_confirmed_nonce: 0, relayers: vec![UnrewardedRelayer { diff --git a/bridges/modules/relayers/src/extension/parachain_adapter.rs b/bridges/modules/relayers/src/extension/parachain_adapter.rs index b6f57cebc30..69cf766dd67 100644 --- a/bridges/modules/relayers/src/extension/parachain_adapter.rs +++ b/bridges/modules/relayers/src/extension/parachain_adapter.rs @@ -32,7 +32,7 @@ use pallet_bridge_grandpa::{ CallSubType as BridgeGrandpaCallSubtype, Config as BridgeGrandpaConfig, }; use pallet_bridge_messages::{ - CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, + CallSubType as BridgeMessagesCallSubType, Config as BridgeMessagesConfig, LaneIdOf, }; use pallet_bridge_parachains::{ CallSubType as BridgeParachainsCallSubtype, Config as BridgeParachainsConfig, @@ -58,6 +58,8 @@ pub struct WithParachainExtensionConfig< BridgeParachainsPalletInstance, // instance of BridgedChain `pallet-bridge-messages`, tracked by this extension BridgeMessagesPalletInstance, + // instance of `pallet-bridge-relayers`, tracked by this extension + BridgeRelayersPalletInstance, // message delivery transaction priority boost for every additional message PriorityBoostPerMessage, >( @@ -67,20 +69,23 @@ pub struct WithParachainExtensionConfig< BatchCallUnpacker, BridgeParachainsPalletInstance, BridgeMessagesPalletInstance, + BridgeRelayersPalletInstance, PriorityBoostPerMessage, )>, ); -impl ExtensionConfig for WithParachainExtensionConfig +impl ExtensionConfig + for WithParachainExtensionConfig where ID: StaticStrProvider, - R: BridgeRelayersConfig + R: BridgeRelayersConfig + BridgeMessagesConfig + BridgeParachainsConfig + BridgeGrandpaConfig, BCU: BatchCallUnpacker, PI: 'static, MI: 'static, + RI: 'static, P: Get, R::RuntimeCall: Dispatchable + BridgeGrandpaCallSubtype @@ -91,15 +96,16 @@ where type IdProvider = ID; type Runtime = R; type BridgeMessagesPalletInstance = MI; + type BridgeRelayersPalletInstance = RI; type PriorityBoostPerMessage = P; - type Reward = R::Reward; type RemoteGrandpaChainBlockNumber = pallet_bridge_grandpa::BridgedBlockNumber; + type LaneId = LaneIdOf; fn parse_and_check_for_obsolete_call( call: &R::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, > { let calls = BCU::unpack(call, 3); @@ -109,7 +115,7 @@ where let msgs_call = calls.next().transpose()?.and_then(|c| c.call_info()); let para_finality_call = calls.next().transpose()?.and_then(|c| { let r = c.submit_parachain_heads_info_for( - >::BridgedChain::PARACHAIN_ID, + >::BridgedChain::PARACHAIN_ID, ); r }); @@ -139,14 +145,14 @@ where } fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &R::AccountId, ) -> bool { verify_submit_finality_proof_succeeded::( call_info, call_data, relayer, ) && verify_submit_parachain_head_succeeded::(call_info, call_data, relayer) && - verify_messages_call_succeeded::(call_info, call_data, relayer) + verify_messages_call_succeeded::(call_info, call_data, relayer) } } @@ -155,7 +161,7 @@ where /// /// Only returns false when parachain state update call has failed. pub(crate) fn verify_submit_parachain_head_succeeded( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, _call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index b9627774db1..f06c2e16ac2 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -43,6 +43,7 @@ mod weights_ext; pub mod benchmarking; pub mod extension; +pub mod migration; pub mod weights; /// The target that will be used when publishing logs related to this pallet. @@ -51,46 +52,58 @@ pub const LOG_TARGET: &str = "runtime::bridge-relayers"; #[frame_support::pallet] pub mod pallet { use super::*; + use bp_messages::LaneIdType; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; /// `RelayerRewardsKeyProvider` for given configuration. - type RelayerRewardsKeyProviderOf = - RelayerRewardsKeyProvider<::AccountId, ::Reward>; + type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider< + ::AccountId, + >::Reward, + >::LaneId, + >; #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; /// Type of relayer reward. type Reward: AtLeast32BitUnsigned + Copy + Member + Parameter + MaxEncodedLen; /// Pay rewards scheme. - type PaymentProcedure: PaymentProcedure; + type PaymentProcedure: PaymentProcedure< + Self::AccountId, + Self::Reward, + LaneId = Self::LaneId, + >; /// Stake and slash scheme. type StakeAndSlash: StakeAndSlash, Self::Reward>; /// Pallet call weights. type WeightInfo: WeightInfoExt; + /// Lane identifier type. + type LaneId: LaneIdType + Send + Sync; } #[pallet::pallet] - pub struct Pallet(PhantomData); + #[pallet::storage_version(migration::STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Claim accumulated rewards. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim_rewards())] pub fn claim_rewards( origin: OriginFor, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, ) -> DispatchResult { let relayer = ensure_signed(origin)?; - RelayerRewards::::try_mutate_exists( + RelayerRewards::::try_mutate_exists( &relayer, rewards_account_params, |maybe_reward| -> DispatchResult { - let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; + let reward = maybe_reward.take().ok_or(Error::::NoRewardForRelayer)?; T::PaymentProcedure::pay_reward(&relayer, rewards_account_params, reward) .map_err(|e| { log::trace!( @@ -100,10 +113,10 @@ pub mod pallet { relayer, e, ); - Error::::FailedToPayReward + Error::::FailedToPayReward })?; - Self::deposit_event(Event::::RewardPaid { + Self::deposit_event(Event::::RewardPaid { relayer: relayer.clone(), rewards_account_params, reward, @@ -125,53 +138,57 @@ pub mod pallet { // than the `RequiredRegistrationLease` let lease = valid_till.saturating_sub(frame_system::Pallet::::block_number()); ensure!( - lease > Pallet::::required_registration_lease(), - Error::::InvalidRegistrationLease + lease > Self::required_registration_lease(), + Error::::InvalidRegistrationLease ); - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let mut registration = maybe_registration - .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); + RegisteredRelayers::::try_mutate( + &relayer, + |maybe_registration| -> DispatchResult { + let mut registration = maybe_registration + .unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() }); + + // new `valid_till` must be larger (or equal) than the old one + ensure!( + valid_till >= registration.valid_till, + Error::::CannotReduceRegistrationLease, + ); + registration.valid_till = valid_till; + + // regarding stake, there are three options: + // - if relayer stake is larger than required stake, we may do unreserve + // - if relayer stake equals to required stake, we do nothing + // - if relayer stake is smaller than required stake, we do additional reserve + let required_stake = Self::required_stake(); + if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) { + Self::do_unreserve(&relayer, to_unreserve)?; + } else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) + { + T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| { + log::trace!( + target: LOG_TARGET, + "Failed to reserve {:?} on relayer {:?} account: {:?}", + to_reserve, + relayer, + e, + ); - // new `valid_till` must be larger (or equal) than the old one - ensure!( - valid_till >= registration.valid_till, - Error::::CannotReduceRegistrationLease, - ); - registration.valid_till = valid_till; - - // regarding stake, there are three options: - // - if relayer stake is larger than required stake, we may do unreserve - // - if relayer stake equals to required stake, we do nothing - // - if relayer stake is smaller than required stake, we do additional reserve - let required_stake = Pallet::::required_stake(); - if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) { - Self::do_unreserve(&relayer, to_unreserve)?; - } else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) { - T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| { - log::trace!( - target: LOG_TARGET, - "Failed to reserve {:?} on relayer {:?} account: {:?}", - to_reserve, - relayer, - e, - ); - - Error::::FailedToReserve - })?; - } - registration.stake = required_stake; - - log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); - Self::deposit_event(Event::::RegistrationUpdated { - relayer: relayer.clone(), - registration, - }); - - *maybe_registration = Some(registration); - - Ok(()) - }) + Error::::FailedToReserve + })?; + } + registration.stake = required_stake; + + log::trace!(target: LOG_TARGET, "Successfully registered relayer: {:?}", relayer); + Self::deposit_event(Event::::RegistrationUpdated { + relayer: relayer.clone(), + registration, + }); + + *maybe_registration = Some(registration); + + Ok(()) + }, + ) } /// `Deregister` relayer. @@ -183,34 +200,37 @@ pub mod pallet { pub fn deregister(origin: OriginFor) -> DispatchResult { let relayer = ensure_signed(origin)?; - RegisteredRelayers::::try_mutate(&relayer, |maybe_registration| -> DispatchResult { - let registration = match maybe_registration.take() { - Some(registration) => registration, - None => fail!(Error::::NotRegistered), - }; - - // we can't deregister until `valid_till + 1` - ensure!( - registration.valid_till < frame_system::Pallet::::block_number(), - Error::::RegistrationIsStillActive, - ); + RegisteredRelayers::::try_mutate( + &relayer, + |maybe_registration| -> DispatchResult { + let registration = match maybe_registration.take() { + Some(registration) => registration, + None => fail!(Error::::NotRegistered), + }; + + // we can't deregister until `valid_till + 1` + ensure!( + registration.valid_till < frame_system::Pallet::::block_number(), + Error::::RegistrationIsStillActive, + ); - // if stake is non-zero, we should do unreserve - if !registration.stake.is_zero() { - Self::do_unreserve(&relayer, registration.stake)?; - } + // if stake is non-zero, we should do unreserve + if !registration.stake.is_zero() { + Self::do_unreserve(&relayer, registration.stake)?; + } - log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); - Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); + log::trace!(target: LOG_TARGET, "Successfully deregistered relayer: {:?}", relayer); + Self::deposit_event(Event::::Deregistered { relayer: relayer.clone() }); - *maybe_registration = None; + *maybe_registration = None; - Ok(()) - }) + Ok(()) + }, + ) } } - impl Pallet { + impl, I: 'static> Pallet { /// Returns true if given relayer registration is active at current block. /// /// This call respects both `RequiredStake` and `RequiredRegistrationLease`, meaning that @@ -243,9 +263,9 @@ pub mod pallet { /// It may fail inside, but error is swallowed and we only log it. pub fn slash_and_deregister( relayer: &T::AccountId, - slash_destination: ExplicitOrAccountParams, + slash_destination: ExplicitOrAccountParams, ) { - let registration = match RegisteredRelayers::::take(relayer) { + let registration = match RegisteredRelayers::::take(relayer) { Some(registration) => registration, None => { log::trace!( @@ -304,7 +324,7 @@ pub mod pallet { /// Register reward for given relayer. pub fn register_relayer_reward( - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, relayer: &T::AccountId, reward: T::Reward, ) { @@ -312,7 +332,7 @@ pub mod pallet { return } - RelayerRewards::::mutate( + RelayerRewards::::mutate( relayer, rewards_account_params, |old_reward: &mut Option| { @@ -327,7 +347,7 @@ pub mod pallet { new_reward, ); - Self::deposit_event(Event::::RewardRegistered { + Self::deposit_event(Event::::RewardRegistered { relayer: relayer.clone(), rewards_account_params, reward, @@ -366,7 +386,7 @@ pub mod pallet { relayer, ); - fail!(Error::::FailedToUnreserve) + fail!(Error::::FailedToUnreserve) } Ok(()) @@ -375,13 +395,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// Relayer reward has been registered and may be claimed later. RewardRegistered { /// Relayer account that can claim reward. relayer: T::AccountId, /// Relayer can claim reward from this account. - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, /// Reward amount. reward: T::Reward, }, @@ -390,7 +410,7 @@ pub mod pallet { /// Relayer account that has been rewarded. relayer: T::AccountId, /// Relayer has received reward from this account. - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, /// Reward amount. reward: T::Reward, }, @@ -416,7 +436,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { + pub enum Error { /// No reward can be claimed by given relayer. NoRewardForRelayer, /// Reward payment procedure has failed. @@ -439,13 +459,13 @@ pub mod pallet { /// Map of the relayer => accumulated reward. #[pallet::storage] #[pallet::getter(fn relayer_reward)] - pub type RelayerRewards = StorageDoubleMap< + pub type RelayerRewards, I: 'static = ()> = StorageDoubleMap< _, - as StorageDoubleMapKeyProvider>::Hasher1, - as StorageDoubleMapKeyProvider>::Key1, - as StorageDoubleMapKeyProvider>::Hasher2, - as StorageDoubleMapKeyProvider>::Key2, - as StorageDoubleMapKeyProvider>::Value, + as StorageDoubleMapKeyProvider>::Hasher1, + as StorageDoubleMapKeyProvider>::Key1, + as StorageDoubleMapKeyProvider>::Hasher2, + as StorageDoubleMapKeyProvider>::Key2, + as StorageDoubleMapKeyProvider>::Value, OptionQuery, >; @@ -457,7 +477,7 @@ pub mod pallet { /// relayer is present. #[pallet::storage] #[pallet::getter(fn registered_relayer)] - pub type RegisteredRelayers = StorageMap< + pub type RegisteredRelayers, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::AccountId, @@ -469,10 +489,10 @@ pub mod pallet { #[cfg(test)] mod tests { use super::*; + use bp_messages::LaneIdType; use mock::{RuntimeEvent as TestEvent, *}; use crate::Event::{RewardPaid, RewardRegistered}; - use bp_messages::LaneId; use bp_relayers::RewardsAccountOwner; use frame_support::{ assert_noop, assert_ok, @@ -596,16 +616,16 @@ mod tests { fn pay_reward_from_account_actually_pays_reward() { type Balances = pallet_balances::Pallet; type PayLaneRewardFromAccount = - bp_relayers::PayRewardFromAccount; + bp_relayers::PayRewardFromAccount; run_test(|| { let in_lane_0 = RewardsAccountParams::new( - LaneId::new(1, 2), + TestLaneIdType::try_new(1, 2).unwrap(), *b"test", RewardsAccountOwner::ThisChain, ); let out_lane_1 = RewardsAccountParams::new( - LaneId::new(1, 3), + TestLaneIdType::try_new(1, 3).unwrap(), *b"test", RewardsAccountOwner::BridgedChain, ); diff --git a/bridges/modules/relayers/src/migration.rs b/bridges/modules/relayers/src/migration.rs new file mode 100644 index 00000000000..8bf473b300c --- /dev/null +++ b/bridges/modules/relayers/src/migration.rs @@ -0,0 +1,243 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity Bridges Common is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity Bridges Common. If not, see . + +//! A module that is responsible for migration of storage. + +use frame_support::{ + traits::{Get, StorageVersion}, + weights::Weight, +}; + +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +/// This module contains data structures that are valid for the initial state of `0`. +/// (used with v1 migration). +pub mod v0 { + use crate::{Config, Pallet}; + use bp_relayers::RewardsAccountOwner; + use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; + use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; + use frame_support::{pallet_prelude::OptionQuery, Blake2_128Concat, Identity}; + use scale_info::TypeInfo; + use sp_runtime::traits::AccountIdConversion; + use sp_std::marker::PhantomData; + + /// Structure used to identify the account that pays a reward to the relayer. + #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] + pub struct RewardsAccountParams { + /// lane_id + pub lane_id: LaneId, + /// bridged_chain_id + pub bridged_chain_id: ChainId, + /// owner + pub owner: RewardsAccountOwner, + } + + impl RewardsAccountParams { + /// Create a new instance of `RewardsAccountParams`. + pub const fn new( + lane_id: LaneId, + bridged_chain_id: ChainId, + owner: RewardsAccountOwner, + ) -> Self { + Self { lane_id, bridged_chain_id, owner } + } + } + + impl sp_runtime::TypeId for RewardsAccountParams { + const TYPE_ID: [u8; 4] = *b"brap"; + } + + pub(crate) struct RelayerRewardsKeyProvider( + PhantomData<(AccountId, Reward, LaneId)>, + ); + + impl StorageDoubleMapKeyProvider + for RelayerRewardsKeyProvider + where + AccountId: 'static + Codec + EncodeLike + Send + Sync, + Reward: 'static + Codec + EncodeLike + Send + Sync, + LaneId: Codec + EncodeLike + Send + Sync, + { + const MAP_NAME: &'static str = "RelayerRewards"; + + type Hasher1 = Blake2_128Concat; + type Key1 = AccountId; + type Hasher2 = Identity; + type Key2 = RewardsAccountParams; + type Value = Reward; + } + + pub(crate) type RelayerRewardsKeyProviderOf = RelayerRewardsKeyProvider< + ::AccountId, + >::Reward, + >::LaneId, + >; + + #[frame_support::storage_alias] + pub(crate) type RelayerRewards, I: 'static> = StorageDoubleMap< + Pallet, + as StorageDoubleMapKeyProvider>::Hasher1, + as StorageDoubleMapKeyProvider>::Key1, + as StorageDoubleMapKeyProvider>::Hasher2, + as StorageDoubleMapKeyProvider>::Key2, + as StorageDoubleMapKeyProvider>::Value, + OptionQuery, + >; + + /// Reward account generator for `v0`. + pub struct PayRewardFromAccount(PhantomData<(Account, LaneId)>); + impl PayRewardFromAccount + where + Account: Decode + Encode, + LaneId: Decode + Encode, + { + /// Return account that pays rewards based on the provided parameters. + pub fn rewards_account(params: RewardsAccountParams) -> Account { + params.into_sub_account_truncating(b"rewards-account") + } + } +} + +/// This migration updates `RelayerRewards` where `RewardsAccountParams` was used as the key with +/// `lane_id` as the first attribute, which affects `into_sub_account_truncating`. We are migrating +/// this key to use the new `RewardsAccountParams` where `lane_id` is the last attribute. +pub mod v1 { + use super::*; + use crate::{Config, Pallet}; + use bp_relayers::RewardsAccountParams; + use frame_support::traits::UncheckedOnRuntimeUpgrade; + use sp_std::marker::PhantomData; + + #[cfg(feature = "try-runtime")] + use crate::RelayerRewards; + + /// Migrates the pallet storage to v1. + pub struct UncheckedMigrationV0ToV1(PhantomData<(T, I)>); + + #[cfg(feature = "try-runtime")] + const LOG_TARGET: &str = "runtime::bridge-relayers-migration"; + + impl, I: 'static> UncheckedOnRuntimeUpgrade for UncheckedMigrationV0ToV1 { + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + // list all rewards (we cannot do this as one step because of `drain` limitation) + let mut rewards_to_migrate = + sp_std::vec::Vec::with_capacity(v0::RelayerRewards::::iter().count()); + for (key1, key2, reward) in v0::RelayerRewards::::drain() { + rewards_to_migrate.push((key1, key2, reward)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + // re-register rewards with new format of `RewardsAccountParams`. + for (key1, key2, reward) in rewards_to_migrate { + // expand old key + let v0::RewardsAccountParams { owner, lane_id, bridged_chain_id } = key2; + + // re-register reward + Pallet::::register_relayer_reward( + v1::RewardsAccountParams::new(lane_id, bridged_chain_id, owner), + &key1, + reward, + ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + use codec::Encode; + use frame_support::BoundedBTreeMap; + use sp_runtime::traits::ConstU32; + + // collect actual rewards + let mut rewards: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = BoundedBTreeMap::new(); + for (key1, key2, reward) in v0::RelayerRewards::::iter() { + log::info!(target: LOG_TARGET, "Reward to migrate: {key1:?}::{key2:?} - {reward:?}"); + rewards = rewards + .try_mutate(|inner| { + inner + .entry((key1.clone(), key2.lane_id)) + .and_modify(|value| *value += reward) + .or_insert(reward); + }) + .unwrap(); + } + log::info!(target: LOG_TARGET, "Found total rewards to migrate: {rewards:?}"); + + Ok(rewards.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: sp_std::vec::Vec) -> Result<(), sp_runtime::DispatchError> { + use codec::Decode; + use frame_support::BoundedBTreeMap; + use sp_runtime::traits::ConstU32; + + let rewards_before: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = Decode::decode(&mut &state[..]).unwrap(); + + // collect migrated rewards + let mut rewards_after: BoundedBTreeMap< + (T::AccountId, T::LaneId), + T::Reward, + ConstU32<{ u32::MAX }>, + > = BoundedBTreeMap::new(); + for (key1, key2, reward) in v1::RelayerRewards::::iter() { + log::info!(target: LOG_TARGET, "Migrated rewards: {key1:?}::{key2:?} - {reward:?}"); + rewards_after = rewards_after + .try_mutate(|inner| { + inner + .entry((key1.clone(), *key2.lane_id())) + .and_modify(|value| *value += reward) + .or_insert(reward); + }) + .unwrap(); + } + log::info!(target: LOG_TARGET, "Found total migrated rewards: {rewards_after:?}"); + + frame_support::ensure!( + rewards_before == rewards_after, + "The rewards were not migrated correctly!." + ); + + log::info!(target: LOG_TARGET, "migrated all."); + Ok(()) + } + } + + /// [`UncheckedMigrationV0ToV1`] wrapped in a + /// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the + /// migration is only performed when on-chain version is 0. + pub type MigrationToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + UncheckedMigrationV0ToV1, + Pallet, + ::DbWeight, + >; +} diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs index de1d292b7c0..d186e968e64 100644 --- a/bridges/modules/relayers/src/mock.rs +++ b/bridges/modules/relayers/src/mock.rs @@ -21,7 +21,7 @@ use crate as pallet_bridge_relayers; use bp_header_chain::ChainWithGrandpa; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, LaneIdType, MessageNonce, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bp_relayers::{ @@ -75,6 +75,13 @@ pub const TEST_BRIDGED_CHAIN_ID: ChainId = *b"brdg"; /// Maximal extrinsic size at the `BridgedChain`. pub const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; +/// Lane that we're using in tests. +pub fn test_lane_id() -> TestLaneIdType { + TestLaneIdType::try_new(1, 2).unwrap() +} + /// Underlying chain of `ThisChain`. pub struct ThisUnderlyingChain; @@ -253,10 +260,10 @@ impl pallet_bridge_messages::Config for TestRuntime { type WeightInfo = pallet_bridge_messages::weights::BridgeWeight; type OutboundPayload = Vec; - type InboundPayload = Vec; - type DeliveryPayments = (); + type LaneId = TestLaneIdType; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), @@ -276,15 +283,20 @@ impl pallet_bridge_relayers::Config for TestRuntime { type PaymentProcedure = TestPaymentProcedure; type StakeAndSlash = TestStakeAndSlash; type WeightInfo = (); + type LaneId = TestLaneIdType; } #[cfg(feature = "runtime-benchmarks")] impl pallet_bridge_relayers::benchmarking::Config for TestRuntime { - fn prepare_rewards_account(account_params: RewardsAccountParams, reward: ThisChainBalance) { - let rewards_account = - bp_relayers::PayRewardFromAccount::::rewards_account( - account_params, - ); + fn prepare_rewards_account( + account_params: RewardsAccountParams, + reward: Self::Reward, + ) { + let rewards_account = bp_relayers::PayRewardFromAccount::< + Balances, + ThisChainAccountId, + Self::LaneId, + >::rewards_account(account_params); Self::deposit_account(rewards_account, reward); } @@ -306,17 +318,18 @@ pub const REGISTER_RELAYER: ThisChainAccountId = 42; pub struct TestPaymentProcedure; impl TestPaymentProcedure { - pub fn rewards_account(params: RewardsAccountParams) -> ThisChainAccountId { - PayRewardFromAccount::<(), ThisChainAccountId>::rewards_account(params) + pub fn rewards_account(params: RewardsAccountParams) -> ThisChainAccountId { + PayRewardFromAccount::<(), ThisChainAccountId, TestLaneIdType>::rewards_account(params) } } impl PaymentProcedure for TestPaymentProcedure { type Error = (); + type LaneId = TestLaneIdType; fn pay_reward( relayer: &ThisChainAccountId, - _lane_id: RewardsAccountParams, + _lane_id: RewardsAccountParams, _reward: ThisChainBalance, ) -> Result<(), Self::Error> { match *relayer { @@ -330,7 +343,7 @@ impl PaymentProcedure for TestPaymentProce pub struct DummyMessageDispatch; impl DummyMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -338,26 +351,33 @@ impl DummyMessageDispatch { impl MessageDispatch for DummyMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } } /// Reward account params that we are using in tests. -pub fn test_reward_account_param() -> RewardsAccountParams { - RewardsAccountParams::new(LaneId::new(1, 2), *b"test", RewardsAccountOwner::ThisChain) +pub fn test_reward_account_param() -> RewardsAccountParams { + RewardsAccountParams::new( + TestLaneIdType::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain, + ) } /// Return test externalities to use in tests. diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs index 3693793a3e5..5383cba5ecb 100644 --- a/bridges/modules/relayers/src/payment_adapter.rs +++ b/bridges/modules/relayers/src/payment_adapter.rs @@ -20,11 +20,12 @@ use crate::{Config, Pallet}; use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewards}, - LaneId, MessageNonce, + MessageNonce, }; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; use frame_support::{sp_runtime::SaturatedConversion, traits::Get}; +use pallet_bridge_messages::LaneIdOf; use sp_arithmetic::traits::{Saturating, Zero}; use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive}; @@ -34,17 +35,17 @@ pub struct DeliveryConfirmationPaymentsAdapter( PhantomData<(T, MI, DeliveryReward)>, ); -impl DeliveryConfirmationPayments +impl DeliveryConfirmationPayments> for DeliveryConfirmationPaymentsAdapter where - T: Config + pallet_bridge_messages::Config, + T: Config + pallet_bridge_messages::Config::LaneId>, MI: 'static, DeliveryReward: Get, { type Error = &'static str; fn pay_reward( - lane_id: LaneId, + lane_id: LaneIdOf, messages_relayers: VecDeque>, confirmation_relayer: &T::AccountId, received_range: &RangeInclusive, @@ -72,7 +73,7 @@ where fn register_relayers_rewards( confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewards, - lane_id: RewardsAccountParams, + lane_id: RewardsAccountParams, delivery_fee: T::Reward, ) { // reward every relayer except `confirmation_relayer` diff --git a/bridges/modules/relayers/src/stake_adapter.rs b/bridges/modules/relayers/src/stake_adapter.rs index 0c965e9e6bf..1792f0be831 100644 --- a/bridges/modules/relayers/src/stake_adapter.rs +++ b/bridges/modules/relayers/src/stake_adapter.rs @@ -18,7 +18,7 @@ //! mechanism of the relayers pallet. use bp_relayers::{ExplicitOrAccountParams, PayRewardFromAccount, StakeAndSlash}; -use codec::Codec; +use codec::{Codec, Decode, Encode}; use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency}; use sp_runtime::{traits::Get, DispatchError, DispatchResult}; use sp_std::{fmt::Debug, marker::PhantomData}; @@ -53,15 +53,15 @@ where Currency::unreserve_named(&ReserveId::get(), relayer, amount) } - fn repatriate_reserved( + fn repatriate_reserved( relayer: &AccountId, - beneficiary: ExplicitOrAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Currency::Balance, ) -> Result { let beneficiary_account = match beneficiary { ExplicitOrAccountParams::Explicit(account) => account, ExplicitOrAccountParams::Params(params) => - PayRewardFromAccount::<(), AccountId>::rewards_account(params), + PayRewardFromAccount::<(), AccountId, LaneId>::rewards_account(params), }; Currency::repatriate_reserved_named( &ReserveId::get(), diff --git a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs index 2412bb0f3bb..dd855c7069a 100644 --- a/bridges/modules/xcm-bridge-hub/src/dispatcher.rs +++ b/bridges/modules/xcm-bridge-hub/src/dispatcher.rs @@ -23,10 +23,7 @@ use crate::{Config, Pallet, LOG_TARGET}; -use bp_messages::{ - target_chain::{DispatchMessage, MessageDispatch}, - LaneId, -}; +use bp_messages::target_chain::{DispatchMessage, MessageDispatch}; use bp_runtime::messages::MessageDispatchResult; use bp_xcm_bridge_hub::{LocalXcmChannelManager, XcmAsPlainPayload}; use codec::{Decode, Encode}; @@ -58,15 +55,18 @@ where { type DispatchPayload = XcmAsPlainPayload; type DispatchLevelResult = XcmBlobMessageDispatchResult; + type LaneId = T::LaneId; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { Pallet::::bridge_by_lane_id(&lane) .and_then(|(_, bridge)| bridge.bridge_origin_relative_location.try_as().cloned().ok()) .map(|recipient: Location| !T::LocalXcmChannelManager::is_congested(&recipient)) .unwrap_or(false) } - fn dispatch_weight(message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + message: &mut DispatchMessage, + ) -> Weight { match message.data.payload { Ok(ref payload) => { let payload_size = payload.encoded_size().saturated_into(); @@ -77,14 +77,14 @@ where } fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult { let payload = match message.data.payload { Ok(payload) => payload, Err(e) => { log::error!( target: LOG_TARGET, - "dispatch - payload error: {e:?} for lane_id: {} and message_nonce: {:?}", + "dispatch - payload error: {e:?} for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -98,7 +98,7 @@ where Ok(_) => { log::debug!( target: LOG_TARGET, - "dispatch - `DispatchBlob::dispatch_blob` was ok for lane_id: {} and message_nonce: {:?}", + "dispatch - `DispatchBlob::dispatch_blob` was ok for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -107,7 +107,7 @@ where Err(e) => { log::error!( target: LOG_TARGET, - "dispatch - `DispatchBlob::dispatch_blob` failed with error: {e:?} for lane_id: {} and message_nonce: {:?}", + "dispatch - `DispatchBlob::dispatch_blob` failed with error: {e:?} for lane_id: {:?} and message_nonce: {:?}", message.key.lane_id, message.key.nonce ); @@ -123,13 +123,13 @@ mod tests { use super::*; use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf}; - use bp_messages::{target_chain::DispatchMessageData, MessageKey}; + use bp_messages::{target_chain::DispatchMessageData, LaneIdType, MessageKey}; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; use frame_support::assert_ok; use pallet_bridge_messages::InboundLaneStorage; use xcm_executor::traits::ConvertLocation; - fn bridge() -> (Box, LaneId) { + fn bridge() -> (Box, TestLaneIdType) { let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = @@ -194,16 +194,16 @@ mod tests { }); } - fn invalid_message() -> DispatchMessage> { + fn invalid_message() -> DispatchMessage, TestLaneIdType> { DispatchMessage { - key: MessageKey { lane_id: LaneId::new(1, 2), nonce: 1 }, + key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 }, data: DispatchMessageData { payload: Err(codec::Error::from("test")) }, } } - fn valid_message() -> DispatchMessage> { + fn valid_message() -> DispatchMessage, TestLaneIdType> { DispatchMessage { - key: MessageKey { lane_id: LaneId::new(1, 2), nonce: 1 }, + key: MessageKey { lane_id: TestLaneIdType::try_new(1, 2).unwrap(), nonce: 1 }, data: DispatchMessageData { payload: Ok(vec![42]) }, } } diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index b42ae1e267f..5afb9f36bc9 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -26,7 +26,7 @@ use crate::{BridgeOf, Bridges}; use bp_messages::{ source_chain::{MessagesBridge, OnMessagesDelivered}, - LaneId, MessageNonce, + MessageNonce, }; use bp_xcm_bridge_hub::{BridgeId, BridgeState, LocalXcmChannelManager, XcmAsPlainPayload}; use frame_support::{ensure, traits::Get}; @@ -62,7 +62,7 @@ where type Ticket = ( BridgeId, BridgeOf, - as MessagesBridge>::SendMessageArgs, + as MessagesBridge>::SendMessageArgs, XcmHash, ); @@ -94,7 +94,7 @@ where "Destination: {dest:?} is already universal, checking dest_network: {dest_network:?} and network: {network:?} if matches: {:?}", dest_network == network ); - ensure!(dest_network == network, SendError::Unroutable); + ensure!(dest_network == network, SendError::NotApplicable); // ok, `dest` looks like a universal location, so let's use it dest }, @@ -108,23 +108,12 @@ where error_data.0, error_data.1, ); - SendError::Unroutable + SendError::NotApplicable })? }, } }; - // check if we are able to route the message. We use existing `HaulBlobExporter` for that. - // It will make all required changes and will encode message properly, so that the - // `DispatchBlob` at the bridged bridge hub will be able to decode it - let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( - network, - channel, - universal_source, - destination, - message, - )?; - // prepare the origin relative location let bridge_origin_relative_location = bridge_origin_universal_location.relative_to(&T::UniversalLocation::get()); @@ -139,9 +128,28 @@ where target: LOG_TARGET, "Validate `bridge_locations` with error: {e:?}", ); - SendError::Unroutable + SendError::NotApplicable + })?; + let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| { + log::error!( + target: LOG_TARGET, + "No opened bridge for requested bridge_origin_relative_location: {:?} and bridge_destination_universal_location: {:?}", + locations.bridge_origin_relative_location(), + locations.bridge_destination_universal_location(), + ); + SendError::NotApplicable })?; - let bridge = Self::bridge(locations.bridge_id()).ok_or(SendError::Unroutable)?; + + // check if we are able to route the message. We use existing `HaulBlobExporter` for that. + // It will make all required changes and will encode message properly, so that the + // `DispatchBlob` at the bridged bridge hub will be able to decode it + let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( + network, + channel, + universal_source, + destination, + message, + )?; let bridge_message = MessagesPallet::::validate_message(bridge.lane_id, &blob) .map_err(|e| { @@ -190,8 +198,8 @@ where } } -impl, I: 'static> OnMessagesDelivered for Pallet { - fn on_messages_delivered(lane_id: LaneId, enqueued_messages: MessageNonce) { +impl, I: 'static> OnMessagesDelivered for Pallet { + fn on_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { Self::on_bridge_messages_delivered(lane_id, enqueued_messages); } } @@ -265,7 +273,7 @@ impl, I: 'static> Pallet { } /// Must be called whenever we receive a message delivery confirmation. - fn on_bridge_messages_delivered(lane_id: LaneId, enqueued_messages: MessageNonce) { + fn on_bridge_messages_delivered(lane_id: T::LaneId, enqueued_messages: MessageNonce) { // if the bridge queue is still congested, we don't want to do anything let is_congested = enqueued_messages > OUTBOUND_LANE_UNCONGESTED_THRESHOLD; if is_congested { @@ -373,7 +381,7 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> (BridgeLocations, LaneId) { + fn open_lane() -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); @@ -430,7 +438,7 @@ mod tests { (*locations, lane_id) } - fn open_lane_and_send_regular_message() -> (BridgeId, LaneId) { + fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { let (locations, lane_id) = open_lane(); // now let's try to enqueue message using our `ExportXcm` implementation @@ -466,7 +474,7 @@ mod tests { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -495,11 +503,11 @@ mod tests { } assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); open_lane_and_send_regular_message(); assert!(TestLocalXcmChannelManager::is_bridge_suspened()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Suspended); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -516,7 +524,7 @@ mod tests { ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Suspended); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -530,7 +538,7 @@ mod tests { ); assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -547,7 +555,7 @@ mod tests { ); assert!(TestLocalXcmChannelManager::is_bridge_resumed()); - assert_eq!(XcmOverBridge::bridge(bridge_id).unwrap().state, BridgeState::Opened); + assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -679,4 +687,97 @@ mod tests { ); }) } + + #[test] + fn validate_works() { + run_test(|| { + let xcm: Xcm<()> = vec![ClearOrigin].into(); + + // check that router does not consume when `NotApplicable` + let mut xcm_wrapper = Some(xcm.clone()); + let mut universal_source_wrapper = Some(universal_source()); + + // wrong `NetworkId` + let mut dest_wrapper = Some(bridged_relative_destination()); + assert_eq!( + XcmOverBridge::validate( + NetworkId::ByGenesis([0; 32]), + 0, + &mut universal_source_wrapper, + &mut dest_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!(&Some(bridged_relative_destination()), &dest_wrapper); + + // dest starts with wrong `NetworkId` + let mut invalid_dest_wrapper = Some( + [GlobalConsensus(NetworkId::ByGenesis([0; 32])), Parachain(BRIDGED_ASSET_HUB_ID)] + .into(), + ); + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut invalid_dest_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!( + &Some( + [ + GlobalConsensus(NetworkId::ByGenesis([0; 32]),), + Parachain(BRIDGED_ASSET_HUB_ID) + ] + .into() + ), + &invalid_dest_wrapper + ); + + // no opened lane for dest + let mut dest_without_lane_wrapper = + Some([GlobalConsensus(BridgedRelayNetwork::get()), Parachain(5679)].into()); + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut dest_without_lane_wrapper, + &mut xcm_wrapper, + ), + Err(SendError::NotApplicable), + ); + // dest and xcm is NOT consumed and untouched + assert_eq!(&Some(xcm.clone()), &xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!( + &Some([GlobalConsensus(BridgedRelayNetwork::get(),), Parachain(5679)].into()), + &dest_without_lane_wrapper + ); + + // ok + let _ = open_lane(); + let mut dest_wrapper = Some(bridged_relative_destination()); + assert_ok!(XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut dest_wrapper, + &mut xcm_wrapper, + )); + // dest and xcm IS consumed + assert_eq!(None, xcm_wrapper); + assert_eq!(&Some(universal_source()), &universal_source_wrapper); + assert_eq!(None, dest_wrapper); + }); + } } diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 02d578386a7..22c60fb4ad6 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -143,7 +143,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_messages::{LaneId, LaneState, MessageNonce}; +use bp_messages::{LaneState, MessageNonce}; use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt}; pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError, LocalXcmChannelManager}; @@ -213,9 +213,8 @@ pub mod pallet { type DestinationVersion: GetVersion; /// The origin that is allowed to call privileged operations on the pallet, e.g. open/close - /// bridge for location that coresponds to `Self::BridgeOriginAccountIdConverter` and - /// `Self::BridgedNetwork`. - type AdminOrigin: EnsureOrigin<::RuntimeOrigin>; + /// bridge for locations. + type ForceOrigin: EnsureOrigin<::RuntimeOrigin>; /// A set of XCM locations within local consensus system that are allowed to open /// bridges with remote destinations. type OpenBridgeOrigin: EnsureOrigin< @@ -248,10 +247,13 @@ pub mod pallet { } /// An alias for the bridge metadata. - pub type BridgeOf = Bridge>; + pub type BridgeOf = Bridge, LaneIdOf>; /// An alias for this chain. pub type ThisChainOf = pallet_bridge_messages::ThisChainOf>::BridgeMessagesPalletInstance>; + /// An alias for lane identifier type. + pub type LaneIdOf = + >::BridgeMessagesPalletInstance>>::LaneId; /// An alias for the associated lanes manager. pub type LanesManagerOf = pallet_bridge_messages::LanesManager>::BridgeMessagesPalletInstance>; @@ -392,7 +394,7 @@ pub mod pallet { // deposit the `ClosingBridge` event Self::deposit_event(Event::::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages, enqueued_messages, }); @@ -439,7 +441,7 @@ pub mod pallet { // deposit the `BridgePruned` event Self::deposit_event(Event::::BridgePruned { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), bridge_deposit: released_deposit, pruned_messages, }); @@ -449,9 +451,10 @@ pub mod pallet { } impl, I: 'static> Pallet { - pub(crate) fn do_open_bridge( + /// Open bridge for lane. + pub fn do_open_bridge( locations: Box, - lane_id: LaneId, + lane_id: T::LaneId, create_lanes: bool, ) -> Result<(), DispatchError> { // reserve balance on the origin's sovereign account (if needed) @@ -542,7 +545,7 @@ pub mod pallet { remote_endpoint: Box::new( locations.bridge_destination_universal_location().clone(), ), - lane_id, + lane_id: lane_id.into(), }); Ok(()) @@ -585,10 +588,15 @@ pub mod pallet { }) } + /// Return bridge metadata by bridge_id + pub fn bridge(bridge_id: &BridgeId) -> Option> { + Bridges::::get(bridge_id) + } + /// Return bridge metadata by lane_id - pub fn bridge_by_lane_id(lane_id: &LaneId) -> Option<(BridgeId, BridgeOf)> { + pub fn bridge_by_lane_id(lane_id: &T::LaneId) -> Option<(BridgeId, BridgeOf)> { LaneToBridge::::get(lane_id) - .and_then(|bridge_id| Self::bridge(bridge_id).map(|bridge| (bridge_id, bridge))) + .and_then(|bridge_id| Self::bridge(&bridge_id).map(|bridge| (bridge_id, bridge))) } } @@ -634,7 +642,7 @@ pub mod pallet { pub fn do_try_state_for_bridge( bridge_id: BridgeId, bridge: BridgeOf, - ) -> Result { + ) -> Result { log::info!(target: LOG_TARGET, "Checking `do_try_state_for_bridge` for bridge_id: {bridge_id:?} and bridge: {bridge:?}"); // check `BridgeId` points to the same `LaneId` and vice versa. @@ -707,13 +715,12 @@ pub mod pallet { /// All registered bridges. #[pallet::storage] - #[pallet::getter(fn bridge)] pub type Bridges, I: 'static = ()> = StorageMap<_, Identity, BridgeId, BridgeOf>; /// All registered `lane_id` and `bridge_id` mappings. #[pallet::storage] pub type LaneToBridge, I: 'static = ()> = - StorageMap<_, Identity, LaneId, BridgeId>; + StorageMap<_, Identity, T::LaneId, BridgeId>; #[pallet::genesis_config] #[derive(DefaultNoBound)] @@ -723,7 +730,7 @@ pub mod pallet { /// Keep in mind that we are **NOT** reserving any amount for the bridges opened at /// genesis. We are **NOT** opening lanes, used by this bridge. It all must be done using /// other pallets genesis configuration or some other means. - pub opened_bridges: Vec<(Location, InteriorLocation)>, + pub opened_bridges: Vec<(Location, InteriorLocation, Option)>, /// Dummy marker. #[serde(skip)] pub _phantom: sp_std::marker::PhantomData<(T, I)>, @@ -735,48 +742,26 @@ pub mod pallet { T: frame_system::Config>>, { fn build(&self) { - for (bridge_origin_relative_location, bridge_destination_universal_location) in - &self.opened_bridges + for ( + bridge_origin_relative_location, + bridge_destination_universal_location, + maybe_lane_id, + ) in &self.opened_bridges { let locations = Pallet::::bridge_locations( bridge_origin_relative_location.clone(), bridge_destination_universal_location.clone().into(), ) .expect("Invalid genesis configuration"); - let lane_id = - locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); - let bridge_owner_account = T::BridgeOriginAccountIdConverter::convert_location( - locations.bridge_origin_relative_location(), - ) - .expect("Invalid genesis configuration"); - Bridges::::insert( - locations.bridge_id(), - Bridge { - bridge_origin_relative_location: Box::new( - locations.bridge_origin_relative_location().clone().into(), - ), - bridge_origin_universal_location: Box::new( - locations.bridge_origin_universal_location().clone().into(), - ), - bridge_destination_universal_location: Box::new( - locations.bridge_destination_universal_location().clone().into(), - ), - state: BridgeState::Opened, - bridge_owner_account, - deposit: Zero::zero(), - lane_id, - }, - ); - LaneToBridge::::insert(lane_id, locations.bridge_id()); + let lane_id = match maybe_lane_id { + Some(lane_id) => *lane_id, + None => + locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"), + }; - let lanes_manager = LanesManagerOf::::new(); - lanes_manager - .create_inbound_lane(lane_id) - .expect("Invalid genesis configuration"); - lanes_manager - .create_outbound_lane(lane_id) - .expect("Invalid genesis configuration"); + Pallet::::do_open_bridge(locations, lane_id, true) + .expect("Valid opened bridge!"); } } } @@ -796,14 +781,14 @@ pub mod pallet { /// Universal location of remote bridge endpoint. remote_endpoint: Box, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, }, /// Bridge is going to be closed, but not yet fully pruned from the runtime storage. ClosingBridge { /// Bridge identifier. bridge_id: BridgeId, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, /// Number of pruned messages during the close call. pruned_messages: MessageNonce, /// Number of enqueued messages that need to be pruned in follow up calls. @@ -815,7 +800,7 @@ pub mod pallet { /// Bridge identifier. bridge_id: BridgeId, /// Lane identifier. - lane_id: LaneId, + lane_id: T::LaneId, /// Amount of deposit released. bridge_deposit: BalanceOf>, /// Number of pruned messages during the close call. @@ -849,12 +834,11 @@ pub mod pallet { #[cfg(test)] mod tests { use super::*; + use bp_messages::LaneIdType; use mock::*; - use bp_messages::LaneId; use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec}; use frame_system::{EventRecord, Phase}; - use sp_core::H256; use sp_runtime::TryRuntimeError; fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId { @@ -911,7 +895,7 @@ mod tests { mock_open_bridge_from_with(origin, deposit, bridged_asset_hub_universal_location()) } - fn enqueue_message(lane: LaneId) { + fn enqueue_message(lane: TestLaneIdType) { let lanes_manager = LanesManagerOf::::new(); lanes_manager .active_outbound_lane(lane) @@ -1212,7 +1196,7 @@ mod tests { remote_endpoint: Box::new( locations.bridge_destination_universal_location().clone() ), - lane_id + lane_id: lane_id.into() }), topics: vec![], }), @@ -1355,7 +1339,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages: 16, enqueued_messages: 16, }), @@ -1403,7 +1387,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), pruned_messages: 8, enqueued_messages: 8, }), @@ -1444,7 +1428,7 @@ mod tests { phase: Phase::Initialization, event: RuntimeEvent::XcmOverBridge(Event::BridgePruned { bridge_id: *locations.bridge_id(), - lane_id: bridge.lane_id, + lane_id: bridge.lane_id.into(), bridge_deposit: expected_deposit, pruned_messages: 8, }), @@ -1456,8 +1440,6 @@ mod tests { #[test] fn do_try_state_works() { - use sp_runtime::Either; - let bridge_origin_relative_location = SiblingLocation::get(); let bridge_origin_universal_location = SiblingUniversalLocation::get(); let bridge_destination_universal_location = BridgedUniversalDestination::get(); @@ -1471,8 +1453,8 @@ mod tests { &bridge_destination_universal_location, ); let bridge_id_mismatch = BridgeId::new(&InteriorLocation::Here, &InteriorLocation::Here); - let lane_id = LaneId::from_inner(Either::Left(H256::default())); - let lane_id_mismatch = LaneId::from_inner(Either::Left(H256::from([1u8; 32]))); + let lane_id = TestLaneIdType::try_new(1, 2).unwrap(); + let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap(); let test_bridge_state = |id, bridge, diff --git a/bridges/modules/xcm-bridge-hub/src/migration.rs b/bridges/modules/xcm-bridge-hub/src/migration.rs index c9d8b67176a..ffd5233a917 100644 --- a/bridges/modules/xcm-bridge-hub/src/migration.rs +++ b/bridges/modules/xcm-bridge-hub/src/migration.rs @@ -17,7 +17,6 @@ //! A module that is responsible for migration of storage. use crate::{Config, Pallet, LOG_TARGET}; -use bp_messages::LaneId; use frame_support::{ traits::{Get, OnRuntimeUpgrade, StorageVersion}, weights::Weight, @@ -52,7 +51,7 @@ pub struct OpenBridgeForLane< impl< T: Config, I: 'static, - Lane: Get, + Lane: Get, CreateLane: Get, SourceRelativeLocation: Get, BridgedUniversalLocation: Get, diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index aff3526b558..6511b9fc5b0 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -20,7 +20,7 @@ use crate as pallet_xcm_bridge_hub; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, - ChainWithMessages, LaneId, MessageNonce, + ChainWithMessages, HashedLaneId, MessageNonce, }; use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager}; @@ -50,6 +50,9 @@ pub type AccountId = AccountId32; pub type Balance = u64; type Block = frame_system::mocking::MockBlock; +/// Lane identifier type used for tests. +pub type TestLaneIdType = HashedLaneId; + pub const SIBLING_ASSET_HUB_ID: u32 = 2001; pub const THIS_BRIDGE_HUB_ID: u32 = 2002; pub const BRIDGED_ASSET_HUB_ID: u32 = 1001; @@ -92,6 +95,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type OutboundPayload = Vec; type InboundPayload = Vec; + type LaneId = TestLaneIdType; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); @@ -152,7 +156,7 @@ parameter_types! { pub SiblingLocation: Location = Location::new(1, [Parachain(SIBLING_ASSET_HUB_ID)]); pub SiblingUniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID)].into(); - pub const BridgedRelayNetwork: NetworkId = NetworkId::Polkadot; + pub const BridgedRelayNetwork: NetworkId = NetworkId::ByGenesis([1; 32]); pub BridgedRelayNetworkLocation: Location = (Parent, GlobalConsensus(BridgedRelayNetwork::get())).into(); pub BridgedRelativeDestination: InteriorLocation = [Parachain(BRIDGED_ASSET_HUB_ID)].into(); pub BridgedUniversalDestination: InteriorLocation = [GlobalConsensus(BridgedRelayNetwork::get()), Parachain(BRIDGED_ASSET_HUB_ID)].into(); @@ -190,7 +194,7 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type MessageExportPrice = (); type DestinationVersion = AlwaysLatest; - type AdminOrigin = frame_system::EnsureNever<()>; + type ForceOrigin = frame_system::EnsureNever<()>; type OpenBridgeOrigin = OpenBridgeOrigin; type BridgeOriginAccountIdConverter = LocationToAccountId; @@ -523,7 +527,7 @@ impl bp_header_chain::HeaderChain for BridgedHeaderChain pub struct TestMessageDispatch; impl TestMessageDispatch { - pub fn deactivate(lane: LaneId) { + pub fn deactivate(lane: TestLaneIdType) { frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); } } @@ -531,18 +535,21 @@ impl TestMessageDispatch { impl MessageDispatch for TestMessageDispatch { type DispatchPayload = Vec; type DispatchLevelResult = (); + type LaneId = TestLaneIdType; - fn is_active(lane: LaneId) -> bool { + fn is_active(lane: Self::LaneId) -> bool { frame_support::storage::unhashed::take::(&(b"inactive", lane).encode()[..]) != Some(false) } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::zero() } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/primitives/messages/src/call_info.rs b/bridges/primitives/messages/src/call_info.rs index c8f06ed8cb7..dfd076f029b 100644 --- a/bridges/primitives/messages/src/call_info.rs +++ b/bridges/primitives/messages/src/call_info.rs @@ -16,22 +16,14 @@ //! Defines structures related to calls of the `pallet-bridge-messages` pallet. -use crate::{source_chain, target_chain, LaneId, MessageNonce, UnrewardedRelayersState}; +use crate::{MessageNonce, UnrewardedRelayersState}; -use bp_runtime::{AccountIdOf, HashOf}; use codec::{Decode, Encode}; use frame_support::weights::Weight; use scale_info::TypeInfo; use sp_core::RuntimeDebug; use sp_std::ops::RangeInclusive; -/// The `BridgeMessagesCall` used to bridge with a given chain. -pub type BridgeMessagesCallOf = BridgeMessagesCall< - AccountIdOf, - target_chain::FromBridgedChainMessagesProof>, - source_chain::FromBridgedChainMessagesDeliveryProof>, ->; - /// A minimized version of `pallet-bridge-messages::Call` that can be used without a runtime. #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] #[allow(non_camel_case_types)] @@ -60,7 +52,7 @@ pub enum BridgeMessagesCall { /// Generic info about a messages delivery/confirmation proof. #[derive(PartialEq, RuntimeDebug)] -pub struct BaseMessagesProofInfo { +pub struct BaseMessagesProofInfo { /// Message lane, used by the call. pub lane_id: LaneId, /// Nonces of messages, included in the call. @@ -75,7 +67,7 @@ pub struct BaseMessagesProofInfo { pub best_stored_nonce: MessageNonce, } -impl BaseMessagesProofInfo { +impl BaseMessagesProofInfo { /// Returns true if `bundled_range` continues the `0..=best_stored_nonce` range. pub fn appends_to_stored_nonce(&self) -> bool { Some(*self.bundled_range.start()) == self.best_stored_nonce.checked_add(1) @@ -94,14 +86,14 @@ pub struct UnrewardedRelayerOccupation { /// Info about a `ReceiveMessagesProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub struct ReceiveMessagesProofInfo { +pub struct ReceiveMessagesProofInfo { /// Base messages proof info - pub base: BaseMessagesProofInfo, + pub base: BaseMessagesProofInfo, /// State of unrewarded relayers vector. pub unrewarded_relayers: UnrewardedRelayerOccupation, } -impl ReceiveMessagesProofInfo { +impl ReceiveMessagesProofInfo { /// Returns true if: /// /// - either inbound lane is ready to accept bundled messages; @@ -134,9 +126,9 @@ impl ReceiveMessagesProofInfo { /// Info about a `ReceiveMessagesDeliveryProof` call which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo); +pub struct ReceiveMessagesDeliveryProofInfo(pub BaseMessagesProofInfo); -impl ReceiveMessagesDeliveryProofInfo { +impl ReceiveMessagesDeliveryProofInfo { /// Returns true if outbound lane is ready to accept confirmations of bundled messages. pub fn is_obsolete(&self) -> bool { self.0.bundled_range.is_empty() || !self.0.appends_to_stored_nonce() @@ -146,14 +138,14 @@ impl ReceiveMessagesDeliveryProofInfo { /// Info about a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call /// which tries to update a single lane. #[derive(PartialEq, RuntimeDebug)] -pub enum MessagesCallInfo { +pub enum MessagesCallInfo { /// Messages delivery call info. - ReceiveMessagesProof(ReceiveMessagesProofInfo), + ReceiveMessagesProof(ReceiveMessagesProofInfo), /// Messages delivery confirmation call info. - ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), + ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo), } -impl MessagesCallInfo { +impl MessagesCallInfo { /// Returns lane, used by the call. pub fn lane_id(&self) -> LaneId { match *self { diff --git a/bridges/primitives/messages/src/lane.rs b/bridges/primitives/messages/src/lane.rs index 6d4ca402eb3..c835449db27 100644 --- a/bridges/primitives/messages/src/lane.rs +++ b/bridges/primitives/messages/src/lane.rs @@ -16,12 +16,88 @@ //! Primitives of messages module, that represents lane id. -use codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen}; -use frame_support::sp_runtime::Either; +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_core::{RuntimeDebug, TypeId, H256}; use sp_io::hashing::blake2_256; +use sp_std::fmt::Debug; + +/// Trait representing a generic `LaneId` type. +pub trait LaneIdType: + Clone + + Copy + + Codec + + EncodeLike + + Debug + + Default + + PartialEq + + Eq + + Ord + + TypeInfo + + MaxEncodedLen + + Serialize + + DeserializeOwned +{ + /// Creates a new `LaneId` type (if supported). + fn try_new(endpoint1: E, endpoint2: E) -> Result; +} + +/// Bridge lane identifier (legacy). +/// +/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`. +#[derive( + Clone, + Copy, + Decode, + Default, + Encode, + Eq, + Ord, + PartialOrd, + PartialEq, + TypeInfo, + MaxEncodedLen, + Serialize, + Deserialize, +)] +pub struct LegacyLaneId(pub [u8; 4]); + +impl LaneIdType for LegacyLaneId { + /// Create lane identifier from two locations. + fn try_new(_endpoint1: T, _endpoint2: T) -> Result { + // we don't support this for `LegacyLaneId`, because it was hard-coded before + Err(()) + } +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl TryFrom> for LegacyLaneId { + type Error = (); + + fn try_from(value: Vec) -> Result { + if value.len() == 4 { + return <[u8; 4]>::try_from(value).map(Self).map_err(|_| ()); + } + Err(()) + } +} + +impl core::fmt::Debug for LegacyLaneId { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + self.0.fmt(fmt) + } +} + +impl AsRef<[u8]> for LegacyLaneId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl TypeId for LegacyLaneId { + const TYPE_ID: [u8; 4] = *b"blan"; +} /// Bridge lane identifier. /// @@ -41,12 +117,11 @@ use sp_io::hashing::blake2_256; /// (endpoint2, VALUES_SEPARATOR, endpoint1) /// }.using_encoded(blake2_256); /// ``` -/// -/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`. #[derive( Clone, Copy, Decode, + Default, Encode, Eq, Ord, @@ -57,115 +132,67 @@ use sp_io::hashing::blake2_256; Serialize, Deserialize, )] -pub struct LaneId(InnerLaneId); - -impl LaneId { - /// Create lane identifier from two locations. - pub fn new(endpoint1: T, endpoint2: T) -> Self { - const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator"; - - LaneId(InnerLaneId::Hash( - if endpoint1 < endpoint2 { - (endpoint1, VALUES_SEPARATOR, endpoint2) - } else { - (endpoint2, VALUES_SEPARATOR, endpoint1) - } - .using_encoded(blake2_256) - .into(), - )) - } +pub struct HashedLaneId(H256); +impl HashedLaneId { /// Create lane identifier from given hash. /// /// There's no `From` implementation for the `LaneId`, because using this conversion /// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we /// want the call to be explicit. - pub const fn from_inner(inner: Either) -> Self { - LaneId(match inner { - Either::Left(hash) => InnerLaneId::Hash(hash), - Either::Right(array) => InnerLaneId::Array(array), - }) + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub const fn from_inner(inner: H256) -> Self { + Self(inner) + } + + /// Access the inner lane representation. + pub fn inner(&self) -> &H256 { + &self.0 } } -impl core::fmt::Display for LaneId { +impl core::fmt::Display for HashedLaneId { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Display::fmt(&self.0, f) } } -impl core::fmt::Debug for LaneId { +impl core::fmt::Debug for HashedLaneId { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Debug::fmt(&self.0, f) } } -impl AsRef<[u8]> for LaneId { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } +impl TypeId for HashedLaneId { + const TYPE_ID: [u8; 4] = *b"hlan"; } -impl TypeId for LaneId { - const TYPE_ID: [u8; 4] = *b"blan"; -} - -#[derive( - Clone, Copy, Eq, Ord, PartialOrd, PartialEq, TypeInfo, MaxEncodedLen, Serialize, Deserialize, -)] -enum InnerLaneId { - /// Old format (for backwards compatibility). - Array([u8; 4]), - /// New format 32-byte hash generated by `blake2_256`. - Hash(H256), -} - -impl Encode for InnerLaneId { - fn encode(&self) -> sp_std::vec::Vec { - match self { - InnerLaneId::Array(array) => array.encode(), - InnerLaneId::Hash(hash) => hash.encode(), - } - } -} - -impl Decode for InnerLaneId { - fn decode(input: &mut I) -> Result { - // check backwards compatibly first - if input.remaining_len() == Ok(Some(4)) { - let array: [u8; 4] = Decode::decode(input)?; - return Ok(InnerLaneId::Array(array)) - } - - // else check new format - H256::decode(input).map(InnerLaneId::Hash) - } -} +impl LaneIdType for HashedLaneId { + /// Create lane identifier from two locations. + fn try_new(endpoint1: T, endpoint2: T) -> Result { + const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator"; -impl core::fmt::Display for InnerLaneId { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - match self { - InnerLaneId::Array(array) => write!(f, "Array({:?})", array), - InnerLaneId::Hash(hash) => write!(f, "Hash({:?})", hash), - } + Ok(Self( + if endpoint1 < endpoint2 { + (endpoint1, VALUES_SEPARATOR, endpoint2) + } else { + (endpoint2, VALUES_SEPARATOR, endpoint1) + } + .using_encoded(blake2_256) + .into(), + )) } } -impl core::fmt::Debug for InnerLaneId { - fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - match self { - InnerLaneId::Array(array) => array.fmt(fmt), - InnerLaneId::Hash(hash) => hash.fmt(fmt), - } - } -} +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl TryFrom> for HashedLaneId { + type Error = (); -impl AsRef<[u8]> for InnerLaneId { - fn as_ref(&self) -> &[u8] { - match self { - InnerLaneId::Array(array) => array.as_ref(), - InnerLaneId::Hash(hash) => hash.as_ref(), + fn try_from(value: Vec) -> Result { + if value.len() == 32 { + return <[u8; 32]>::try_from(value).map(|v| Self(H256::from(v))).map_err(|_| ()); } + Err(()) } } @@ -194,63 +221,89 @@ impl LaneState { #[cfg(test)] mod tests { use super::*; + use crate::MessageNonce; #[test] fn lane_id_debug_format_matches_inner_hash_format() { assert_eq!( - format!("{:?}", LaneId(InnerLaneId::Hash(H256::from([1u8; 32])))), + format!("{:?}", HashedLaneId(H256::from([1u8; 32]))), format!("{:?}", H256::from([1u8; 32])), ); - assert_eq!( - format!("{:?}", LaneId(InnerLaneId::Array([0, 0, 0, 1]))), - format!("{:?}", [0, 0, 0, 1]), - ); + assert_eq!(format!("{:?}", LegacyLaneId([0, 0, 0, 1])), format!("{:?}", [0, 0, 0, 1]),); } #[test] - fn lane_id_as_ref_works() { + fn hashed_encode_decode_works() { + // simple encode/decode - new format + let lane_id = HashedLaneId(H256::from([1u8; 32])); + let encoded_lane_id = lane_id.encode(); + let decoded_lane_id = HashedLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable"); + assert_eq!(lane_id, decoded_lane_id); assert_eq!( "0101010101010101010101010101010101010101010101010101010101010101", - hex::encode(LaneId(InnerLaneId::Hash(H256::from([1u8; 32]))).as_ref()), + hex::encode(encoded_lane_id) ); - assert_eq!("00000001", hex::encode(LaneId(InnerLaneId::Array([0, 0, 0, 1])).as_ref()),); } #[test] - fn lane_id_encode_decode_works() { - let test_encode_decode = |expected_hex, lane_id: LaneId| { - let enc = lane_id.encode(); - let decoded_lane_id = LaneId::decode(&mut &enc[..]).expect("decodable"); - assert_eq!(lane_id, decoded_lane_id); - - assert_eq!(expected_hex, hex::encode(lane_id.as_ref()),); - assert_eq!(expected_hex, hex::encode(decoded_lane_id.as_ref()),); - - let hex_bytes = hex::decode(expected_hex).expect("valid hex"); - let hex_decoded_lane_id = LaneId::decode(&mut &hex_bytes[..]).expect("decodable"); - assert_eq!(hex_decoded_lane_id, lane_id); - assert_eq!(hex_decoded_lane_id, decoded_lane_id); - }; - - test_encode_decode( - "0101010101010101010101010101010101010101010101010101010101010101", - LaneId(InnerLaneId::Hash(H256::from([1u8; 32]))), - ); - test_encode_decode("00000001", LaneId(InnerLaneId::Array([0, 0, 0, 1]))); + fn legacy_encode_decode_works() { + // simple encode/decode - old format + let lane_id = LegacyLaneId([0, 0, 0, 1]); + let encoded_lane_id = lane_id.encode(); + let decoded_lane_id = LegacyLaneId::decode(&mut &encoded_lane_id[..]).expect("decodable"); + assert_eq!(lane_id, decoded_lane_id); + assert_eq!("00000001", hex::encode(encoded_lane_id)); + + // decode sample + let bytes = vec![0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]; + let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, LegacyLaneId([0, 0, 0, 2])); + assert_eq!(nonce_start, 1); + assert_eq!(nonce_end, 1); + + // run encode/decode for `LaneId` with different positions + let expected_lane = LegacyLaneId([0, 0, 0, 1]); + let expected_nonce_start = 1088_u64; + let expected_nonce_end = 9185_u64; + + // decode: LaneId,Nonce,Nonce + let bytes = (expected_lane, expected_nonce_start, expected_nonce_end).encode(); + let (lane, nonce_start, nonce_end): (LegacyLaneId, MessageNonce, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); + + // decode: Nonce,LaneId,Nonce + let bytes = (expected_nonce_start, expected_lane, expected_nonce_end).encode(); + let (nonce_start, lane, nonce_end): (MessageNonce, LegacyLaneId, MessageNonce) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); + + // decode: Nonce,Nonce,LaneId + let bytes = (expected_nonce_start, expected_nonce_end, expected_lane).encode(); + let (nonce_start, nonce_end, lane): (MessageNonce, MessageNonce, LegacyLaneId) = + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(lane, expected_lane); + assert_eq!(nonce_start, expected_nonce_start); + assert_eq!(nonce_end, expected_nonce_end); } #[test] - fn lane_id_is_generated_using_ordered_endpoints() { - assert_eq!(LaneId::new(1, 2), LaneId::new(2, 1)); + fn hashed_lane_id_is_generated_using_ordered_endpoints() { + assert_eq!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(2, 1).unwrap()); } #[test] - fn lane_id_is_different_for_different_endpoints() { - assert_ne!(LaneId::new(1, 2), LaneId::new(1, 3)); + fn hashed_lane_id_is_different_for_different_endpoints() { + assert_ne!(HashedLaneId::try_new(1, 2).unwrap(), HashedLaneId::try_new(1, 3).unwrap()); } #[test] - fn lane_id_is_different_even_if_arguments_has_partial_matching_encoding() { + fn hashed_lane_id_is_different_even_if_arguments_has_partial_matching_encoding() { /// Some artificial type that generates the same encoding for different values /// concatenations. I.e. the encoding for `(Either::Two(1, 2), Either::Two(3, 4))` /// is the same as encoding of `(Either::Three(1, 2, 3), Either::One(4))`. @@ -274,8 +327,8 @@ mod tests { } assert_ne!( - LaneId::new(Either::Two(1, 2), Either::Two(3, 4)), - LaneId::new(Either::Three(1, 2, 3), Either::One(4)), + HashedLaneId::try_new(Either::Two(1, 2), Either::Two(3, 4)).unwrap(), + HashedLaneId::try_new(Either::Three(1, 2, 3), Either::One(4)).unwrap(), ); } } diff --git a/bridges/primitives/messages/src/lib.rs b/bridges/primitives/messages/src/lib.rs index 7eb0c562939..2776b806cc1 100644 --- a/bridges/primitives/messages/src/lib.rs +++ b/bridges/primitives/messages/src/lib.rs @@ -35,10 +35,10 @@ use sp_core::RuntimeDebug; use sp_std::{collections::vec_deque::VecDeque, ops::RangeInclusive, prelude::*}; pub use call_info::{ - BaseMessagesProofInfo, BridgeMessagesCall, BridgeMessagesCallOf, MessagesCallInfo, - ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, + BaseMessagesProofInfo, BridgeMessagesCall, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, + ReceiveMessagesProofInfo, UnrewardedRelayerOccupation, }; -pub use lane::{LaneId, LaneState}; +pub use lane::{HashedLaneId, LaneIdType, LaneState, LegacyLaneId}; mod call_info; mod lane; @@ -181,7 +181,7 @@ pub type MessagePayload = Vec; /// Message key (unique message identifier) as it is stored in the storage. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct MessageKey { +pub struct MessageKey { /// ID of the message lane. pub lane_id: LaneId, /// Message nonce. @@ -190,9 +190,9 @@ pub struct MessageKey { /// Message as it is stored in the storage. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct Message { +pub struct Message { /// Message key. - pub key: MessageKey, + pub key: MessageKey, /// Message payload. pub payload: MessagePayload, } @@ -200,11 +200,6 @@ pub struct Message { /// Inbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct InboundLaneData { - /// Inbound lane state. - /// - /// If state is `Closed`, then all attempts to deliver messages to this end will fail. - pub state: LaneState, - /// Identifiers of relayers and messages that they have delivered to this lane (ordered by /// message nonce). /// @@ -233,6 +228,11 @@ pub struct InboundLaneData { /// This value is updated indirectly when an `OutboundLane` state of the source /// chain is received alongside with new messages delivery. pub last_confirmed_nonce: MessageNonce, + + /// Inbound lane state. + /// + /// If state is `Closed`, then all attempts to deliver messages to this end will fail. + pub state: LaneState, } impl Default for InboundLaneData { @@ -337,20 +337,20 @@ pub struct UnrewardedRelayer { /// Received messages with their dispatch result. #[derive(Clone, Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] -pub struct ReceivedMessages { +pub struct ReceivedMessages { /// Id of the lane which is receiving messages. pub lane: LaneId, /// Result of messages which we tried to dispatch pub receive_results: Vec<(MessageNonce, ReceptionResult)>, } -impl ReceivedMessages { +impl ReceivedMessages { /// Creates new `ReceivedMessages` structure from given results. pub fn new( lane: LaneId, receive_results: Vec<(MessageNonce, ReceptionResult)>, ) -> Self { - ReceivedMessages { lane, receive_results } + ReceivedMessages { lane: lane.into(), receive_results } } /// Push `result` of the `message` delivery onto `receive_results` vector. @@ -449,10 +449,6 @@ impl From<&InboundLaneData> for UnrewardedRelayersState { /// Outbound lane data. #[derive(Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct OutboundLaneData { - /// Lane state. - /// - /// If state is `Closed`, then all attempts to send messages messages at this end will fail. - pub state: LaneState, /// Nonce of the oldest message that we haven't yet pruned. May point to not-yet-generated /// message if all sent messages are already pruned. pub oldest_unpruned_nonce: MessageNonce, @@ -460,6 +456,10 @@ pub struct OutboundLaneData { pub latest_received_nonce: MessageNonce, /// Nonce of the latest message, generated by us. pub latest_generated_nonce: MessageNonce, + /// Lane state. + /// + /// If state is `Closed`, then all attempts to send messages at this end will fail. + pub state: LaneState, } impl OutboundLaneData { diff --git a/bridges/primitives/messages/src/source_chain.rs b/bridges/primitives/messages/src/source_chain.rs index 64f015bdb82..1d4a513035c 100644 --- a/bridges/primitives/messages/src/source_chain.rs +++ b/bridges/primitives/messages/src/source_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the source chain. -use crate::{LaneId, MessageNonce, UnrewardedRelayer}; +use crate::{MessageNonce, UnrewardedRelayer}; use bp_runtime::{raw_storage_proof_size, RawStorageProof, Size}; use codec::{Decode, Encode}; @@ -39,7 +39,7 @@ use sp_std::{ /// /// - lane id. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct FromBridgedChainMessagesDeliveryProof { +pub struct FromBridgedChainMessagesDeliveryProof { /// Hash of the bridge header the proof is for. pub bridged_header_hash: BridgedHeaderHash, /// Storage trie proof generated for [`Self::bridged_header_hash`]. @@ -48,7 +48,9 @@ pub struct FromBridgedChainMessagesDeliveryProof { pub lane: LaneId, } -impl Size for FromBridgedChainMessagesDeliveryProof { +impl Size + for FromBridgedChainMessagesDeliveryProof +{ fn size(&self) -> u32 { use frame_support::sp_runtime::SaturatedConversion; raw_storage_proof_size(&self.storage_proof).saturated_into() @@ -60,7 +62,7 @@ pub type RelayersRewards = BTreeMap; /// Manages payments that are happening at the source chain during delivery confirmation /// transaction. -pub trait DeliveryConfirmationPayments { +pub trait DeliveryConfirmationPayments { /// Error type. type Error: Debug + Into<&'static str>; @@ -78,7 +80,7 @@ pub trait DeliveryConfirmationPayments { ) -> MessageNonce; } -impl DeliveryConfirmationPayments for () { +impl DeliveryConfirmationPayments for () { type Error = &'static str; fn pay_reward( @@ -94,14 +96,14 @@ impl DeliveryConfirmationPayments for () { /// Callback that is called at the source chain (bridge hub) when we get delivery confirmation /// for new messages. -pub trait OnMessagesDelivered { +pub trait OnMessagesDelivered { /// New messages delivery has been confirmed. /// /// The only argument of the function is the number of yet undelivered messages fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce); } -impl OnMessagesDelivered for () { +impl OnMessagesDelivered for () { fn on_messages_delivered(_lane: LaneId, _enqueued_messages: MessageNonce) {} } @@ -115,7 +117,7 @@ pub struct SendMessageArtifacts { } /// Messages bridge API to be used from other pallets. -pub trait MessagesBridge { +pub trait MessagesBridge { /// Error type. type Error: Debug; @@ -141,7 +143,7 @@ pub trait MessagesBridge { /// where outbound messages are forbidden. pub struct ForbidOutboundMessages; -impl DeliveryConfirmationPayments for ForbidOutboundMessages { +impl DeliveryConfirmationPayments for ForbidOutboundMessages { type Error = &'static str; fn pay_reward( diff --git a/bridges/primitives/messages/src/storage_keys.rs b/bridges/primitives/messages/src/storage_keys.rs index ff62dab078e..fb3371cb830 100644 --- a/bridges/primitives/messages/src/storage_keys.rs +++ b/bridges/primitives/messages/src/storage_keys.rs @@ -25,7 +25,7 @@ pub const OUTBOUND_LANES_MAP_NAME: &str = "OutboundLanes"; /// Name of the `InboundLanes` storage map. pub const INBOUND_LANES_MAP_NAME: &str = "InboundLanes"; -use crate::{LaneId, MessageKey, MessageNonce}; +use crate::{MessageKey, MessageNonce}; use codec::Encode; use frame_support::Blake2_128Concat; @@ -43,16 +43,20 @@ pub fn operating_mode_key(pallet_prefix: &str) -> StorageKey { } /// Storage key of the outbound message in the runtime storage. -pub fn message_key(pallet_prefix: &str, lane: &LaneId, nonce: MessageNonce) -> StorageKey { +pub fn message_key( + pallet_prefix: &str, + lane: LaneId, + nonce: MessageNonce, +) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, OUTBOUND_MESSAGES_MAP_NAME, - &MessageKey { lane_id: *lane, nonce }.encode(), + &MessageKey { lane_id: lane, nonce }.encode(), ) } /// Storage key of the outbound message lane state in the runtime storage. -pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { +pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, OUTBOUND_LANES_MAP_NAME, @@ -61,7 +65,7 @@ pub fn outbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey } /// Storage key of the inbound message lane state in the runtime storage. -pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { +pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { bp_runtime::storage_map_final_key::( pallet_prefix, INBOUND_LANES_MAP_NAME, @@ -72,7 +76,10 @@ pub fn inbound_lane_data_key(pallet_prefix: &str, lane: &LaneId) -> StorageKey { #[cfg(test)] mod tests { use super::*; - use frame_support::sp_runtime::Either; + use crate::{ + lane::{HashedLaneId, LegacyLaneId}, + LaneIdType, + }; use hex_literal::hex; #[test] @@ -92,7 +99,8 @@ mod tests { fn storage_message_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted messages proofs. - let storage_key = message_key("BridgeMessages", &LaneId::new(1, 2), 42).0; + let storage_key = + message_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap(), 42).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea70e9bdb8f50c68d12f06eabb57759ee5eb1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea02a00000000000000").to_vec(), @@ -101,8 +109,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - message_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test")), 42).0; + let storage_key = message_key("BridgeMessages", &LegacyLaneId(*b"test"), 42).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed018a395e6242c6813b196ca31ed0547ea79446af0e09063bd4a7874aef8a997cec746573742a00000000000000").to_vec(), @@ -115,7 +122,8 @@ mod tests { fn outbound_lane_data_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted outbound lane state proofs. - let storage_key = outbound_lane_data_key("BridgeMessages", &LaneId::new(1, 2)).0; + let storage_key = + outbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1fd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(), @@ -124,9 +132,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - outbound_lane_data_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test"))) - .0; + let storage_key = outbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed0196c246acb9b55077390e3ca723a0ca1f44a8995dd50b6657a037a7839304535b74657374").to_vec(), @@ -139,7 +145,8 @@ mod tests { fn inbound_lane_data_key_computed_properly() { // If this test fails, then something has been changed in module storage that is breaking // all previously crafted inbound lane state proofs. - let storage_key = inbound_lane_data_key("BridgeMessages", &LaneId::new(1, 2)).0; + let storage_key = + inbound_lane_data_key("BridgeMessages", &HashedLaneId::try_new(1, 2).unwrap()).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fabd3bef8b00df8ca7b01813b5e2741950db1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dcf87f9793be208e5ea0").to_vec(), @@ -148,8 +155,7 @@ mod tests { ); // check backwards compatibility - let storage_key = - inbound_lane_data_key("BridgeMessages", &LaneId::from_inner(Either::Right(*b"test"))).0; + let storage_key = inbound_lane_data_key("BridgeMessages", &LegacyLaneId(*b"test")).0; assert_eq!( storage_key, hex!("dd16c784ebd3390a9bc0357c7511ed01e5f83cf83f2127eb47afdc35d6e43fab44a8995dd50b6657a037a7839304535b74657374").to_vec(), diff --git a/bridges/primitives/messages/src/target_chain.rs b/bridges/primitives/messages/src/target_chain.rs index 67868ff7c7c..cf07a400933 100644 --- a/bridges/primitives/messages/src/target_chain.rs +++ b/bridges/primitives/messages/src/target_chain.rs @@ -16,7 +16,7 @@ //! Primitives of messages module, that are used on the target chain. -use crate::{LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData}; +use crate::{Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData}; use bp_runtime::{messages::MessageDispatchResult, raw_storage_proof_size, RawStorageProof, Size}; use codec::{Decode, Encode, Error as CodecError}; @@ -38,20 +38,20 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; /// /// - nonces (inclusive range) of messages which are included in this proof. #[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct FromBridgedChainMessagesProof { +pub struct FromBridgedChainMessagesProof { /// Hash of the finalized bridged header the proof is for. pub bridged_header_hash: BridgedHeaderHash, /// A storage trie proof of messages being delivered. pub storage_proof: RawStorageProof, /// Messages in this proof are sent over this lane. - pub lane: LaneId, + pub lane: Lane, /// Nonce of the first message being delivered. pub nonces_start: MessageNonce, /// Nonce of the last message being delivered. pub nonces_end: MessageNonce, } -impl Size for FromBridgedChainMessagesProof { +impl Size for FromBridgedChainMessagesProof { fn size(&self) -> u32 { use frame_support::sp_runtime::SaturatedConversion; raw_storage_proof_size(&self.storage_proof).saturated_into() @@ -59,7 +59,7 @@ impl Size for FromBridgedChainMessagesProof = (LaneId, ProvedLaneMessages); +pub type ProvedMessages = (LaneId, ProvedLaneMessages); /// Proved messages from single lane of the source chain. #[derive(RuntimeDebug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] @@ -79,9 +79,9 @@ pub struct DispatchMessageData { /// Message with decoded dispatch payload. #[derive(RuntimeDebug)] -pub struct DispatchMessage { +pub struct DispatchMessage { /// Message key. - pub key: MessageKey, + pub key: MessageKey, /// Message data with decoded dispatch payload. pub data: DispatchMessageData, } @@ -96,6 +96,9 @@ pub trait MessageDispatch { /// Fine-grained result of single message dispatch (for better diagnostic purposes) type DispatchLevelResult: Clone + sp_std::fmt::Debug + Eq; + /// Lane identifier type. + type LaneId: Encode; + /// Returns `true` if dispatcher is ready to accept additional messages. The `false` should /// be treated as a hint by both dispatcher and its consumers - i.e. dispatcher shall not /// simply drop messages if it returns `false`. The consumer may still call the `dispatch` @@ -103,21 +106,23 @@ pub trait MessageDispatch { /// /// We check it in the messages delivery transaction prologue. So if it becomes `false` /// after some portion of messages is already dispatched, it doesn't fail the whole transaction. - fn is_active(lane: LaneId) -> bool; + fn is_active(lane: Self::LaneId) -> bool; /// Estimate dispatch weight. /// /// This function must return correct upper bound of dispatch weight. The return value /// of this function is expected to match return value of the corresponding /// `FromInboundLaneApi::message_details().dispatch_weight` call. - fn dispatch_weight(message: &mut DispatchMessage) -> Weight; + fn dispatch_weight( + message: &mut DispatchMessage, + ) -> Weight; /// Called when inbound message is received. /// /// It is up to the implementers of this trait to determine whether the message /// is invalid (i.e. improperly encoded, has too large weight, ...) or not. fn dispatch( - message: DispatchMessage, + message: DispatchMessage, ) -> MessageDispatchResult; } @@ -146,8 +151,10 @@ impl Default for ProvedLaneMessages { } } -impl From for DispatchMessage { - fn from(message: Message) -> Self { +impl From> + for DispatchMessage +{ + fn from(message: Message) -> Self { DispatchMessage { key: message.key, data: message.payload.into() } } } @@ -173,22 +180,27 @@ impl DeliveryPayments for () { /// Structure that may be used in place of `MessageDispatch` on chains, /// where inbound messages are forbidden. -pub struct ForbidInboundMessages(PhantomData); +pub struct ForbidInboundMessages(PhantomData<(DispatchPayload, LaneId)>); -impl MessageDispatch for ForbidInboundMessages { +impl MessageDispatch + for ForbidInboundMessages +{ type DispatchPayload = DispatchPayload; type DispatchLevelResult = (); + type LaneId = LaneId; fn is_active(_: LaneId) -> bool { false } - fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + fn dispatch_weight( + _message: &mut DispatchMessage, + ) -> Weight { Weight::MAX } fn dispatch( - _: DispatchMessage, + _: DispatchMessage, ) -> MessageDispatchResult { MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } } diff --git a/bridges/primitives/relayers/src/extension.rs b/bridges/primitives/relayers/src/extension.rs index 5ab8e6cde96..a9fa4df27ea 100644 --- a/bridges/primitives/relayers/src/extension.rs +++ b/bridges/primitives/relayers/src/extension.rs @@ -21,6 +21,7 @@ use bp_header_chain::SubmitFinalityProofInfo; use bp_messages::MessagesCallInfo; use bp_parachains::SubmitParachainHeadsInfo; use bp_runtime::StaticStrProvider; +use codec::{Decode, Encode}; use frame_support::{ dispatch::CallableCallFor, traits::IsSubType, weights::Weight, RuntimeDebugNoBound, }; @@ -35,25 +36,28 @@ use sp_std::{fmt::Debug, marker::PhantomData, vec, vec::Vec}; /// Type of the call that the signed extension recognizes. #[derive(PartialEq, RuntimeDebugNoBound)] -pub enum ExtensionCallInfo { +pub enum ExtensionCallInfo { /// Relay chain finality + parachain finality + message delivery/confirmation calls. AllFinalityAndMsgs( SubmitFinalityProofInfo, SubmitParachainHeadsInfo, - MessagesCallInfo, + MessagesCallInfo, ), /// Relay chain finality + message delivery/confirmation calls. - RelayFinalityAndMsgs(SubmitFinalityProofInfo, MessagesCallInfo), + RelayFinalityAndMsgs( + SubmitFinalityProofInfo, + MessagesCallInfo, + ), /// Parachain finality + message delivery/confirmation calls. /// /// This variant is used only when bridging with parachain. - ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo), + ParachainFinalityAndMsgs(SubmitParachainHeadsInfo, MessagesCallInfo), /// Standalone message delivery/confirmation call. - Msgs(MessagesCallInfo), + Msgs(MessagesCallInfo), } -impl - ExtensionCallInfo +impl + ExtensionCallInfo { /// Returns true if call is a message delivery call (with optional finality calls). pub fn is_receive_messages_proof_call(&self) -> bool { @@ -84,7 +88,7 @@ impl } /// Returns the pre-dispatch `ReceiveMessagesProofInfo`. - pub fn messages_call_info(&self) -> &MessagesCallInfo { + pub fn messages_call_info(&self) -> &MessagesCallInfo { match self { Self::AllFinalityAndMsgs(_, _, info) => info, Self::RelayFinalityAndMsgs(_, info) => info, @@ -119,25 +123,27 @@ pub trait ExtensionConfig { /// Runtime that optionally supports batched calls. We assume that batched call /// succeeds if and only if all of its nested calls succeed. type Runtime: frame_system::Config; + /// Relayers pallet instance. + type BridgeRelayersPalletInstance: 'static; /// Messages pallet instance. type BridgeMessagesPalletInstance: 'static; /// Additional priority that is added to base message delivery transaction priority /// for every additional bundled message. type PriorityBoostPerMessage: Get; - /// Type of reward, that the `pallet-bridge-relayers` is using. - type Reward; /// Block number for the remote **GRANDPA chain**. Mind that this chain is not /// necessarily the chain that we are bridging with. If we are bridging with /// parachain, it must be its parent relay chain. If we are bridging with the /// GRANDPA chain, it must be it. type RemoteGrandpaChainBlockNumber: Clone + Copy + Debug; + /// Lane identifier type. + type LaneId: Clone + Copy + Decode + Encode + Debug; /// Given runtime call, check if it is supported by the signed extension. Additionally, /// check if call (or any of batched calls) are obsolete. fn parse_and_check_for_obsolete_call( call: &::RuntimeCall, ) -> Result< - Option>, + Option>, TransactionValidityError, >; @@ -149,7 +155,7 @@ pub trait ExtensionConfig { /// Given runtime call info, check that this call has been successful and has updated /// runtime storage accordingly. fn check_call_result( - call_info: &ExtensionCallInfo, + call_info: &ExtensionCallInfo, call_data: &mut ExtensionCallData, relayer: &::AccountId, ) -> bool; diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs index 1e63c89ecd7..faa4cb17762 100644 --- a/bridges/primitives/relayers/src/lib.rs +++ b/bridges/primitives/relayers/src/lib.rs @@ -25,7 +25,6 @@ pub use extension::{ }; pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash}; -use bp_messages::LaneId; use bp_runtime::{ChainId, StorageDoubleMapKeyProvider}; use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity}; use scale_info::TypeInfo; @@ -61,7 +60,7 @@ pub enum RewardsAccountOwner { /// of the sovereign accounts will pay rewards for different operations. So we need multiple /// parameters to identify the account that pays a reward to the relayer. #[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] -pub struct RewardsAccountParams { +pub struct RewardsAccountParams { // **IMPORTANT NOTE**: the order of fields here matters - we are using // `into_account_truncating` and lane id is already `32` byte, so if other fields are encoded // after it, they're simply dropped. So lane id shall be the last field. @@ -70,7 +69,7 @@ pub struct RewardsAccountParams { lane_id: LaneId, } -impl RewardsAccountParams { +impl RewardsAccountParams { /// Create a new instance of `RewardsAccountParams`. pub const fn new( lane_id: LaneId, @@ -79,9 +78,14 @@ impl RewardsAccountParams { ) -> Self { Self { lane_id, bridged_chain_id, owner } } + + /// Getter for `lane_id`. + pub const fn lane_id(&self) -> &LaneId { + &self.lane_id + } } -impl TypeId for RewardsAccountParams { +impl TypeId for RewardsAccountParams { const TYPE_ID: [u8; 4] = *b"brap"; } @@ -89,47 +93,58 @@ impl TypeId for RewardsAccountParams { pub trait PaymentProcedure { /// Error that may be returned by the procedure. type Error: Debug; + /// Lane identifier type. + type LaneId: Decode + Encode; /// Pay reward to the relayer from the account with provided params. fn pay_reward( relayer: &Relayer, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, reward: Reward, ) -> Result<(), Self::Error>; } impl PaymentProcedure for () { type Error = &'static str; + type LaneId = (); - fn pay_reward(_: &Relayer, _: RewardsAccountParams, _: Reward) -> Result<(), Self::Error> { + fn pay_reward( + _: &Relayer, + _: RewardsAccountParams, + _: Reward, + ) -> Result<(), Self::Error> { Ok(()) } } /// Reward payment procedure that does `balances::transfer` call from the account, derived from /// given params. -pub struct PayRewardFromAccount(PhantomData<(T, Relayer)>); +pub struct PayRewardFromAccount(PhantomData<(T, Relayer, LaneId)>); -impl PayRewardFromAccount +impl PayRewardFromAccount where Relayer: Decode + Encode, + LaneId: Decode + Encode, { /// Return account that pays rewards based on the provided parameters. - pub fn rewards_account(params: RewardsAccountParams) -> Relayer { + pub fn rewards_account(params: RewardsAccountParams) -> Relayer { params.into_sub_account_truncating(b"rewards-account") } } -impl PaymentProcedure for PayRewardFromAccount +impl PaymentProcedure + for PayRewardFromAccount where T: frame_support::traits::fungible::Mutate, Relayer: Decode + Encode + Eq, + LaneId: Decode + Encode, { type Error = sp_runtime::DispatchError; + type LaneId = LaneId; fn pay_reward( relayer: &Relayer, - rewards_account_params: RewardsAccountParams, + rewards_account_params: RewardsAccountParams, reward: T::Balance, ) -> Result<(), Self::Error> { T::transfer( @@ -142,48 +157,56 @@ where } } -/// Can be use to access the runtime storage key within the `RelayerRewards` map of the relayers +/// Can be used to access the runtime storage key within the `RelayerRewards` map of the relayers /// pallet. -pub struct RelayerRewardsKeyProvider(PhantomData<(AccountId, Reward)>); +pub struct RelayerRewardsKeyProvider( + PhantomData<(AccountId, Reward, LaneId)>, +); -impl StorageDoubleMapKeyProvider for RelayerRewardsKeyProvider +impl StorageDoubleMapKeyProvider + for RelayerRewardsKeyProvider where AccountId: 'static + Codec + EncodeLike + Send + Sync, Reward: 'static + Codec + EncodeLike + Send + Sync, + LaneId: Codec + EncodeLike + Send + Sync, { const MAP_NAME: &'static str = "RelayerRewards"; type Hasher1 = Blake2_128Concat; type Key1 = AccountId; type Hasher2 = Identity; - type Key2 = RewardsAccountParams; + type Key2 = RewardsAccountParams; type Value = Reward; } #[cfg(test)] mod tests { use super::*; - use bp_messages::LaneId; - use sp_runtime::testing::H256; + use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId}; + use sp_runtime::{app_crypto::Ss58Codec, testing::H256}; #[test] fn different_lanes_are_using_different_accounts() { assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 3), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 3).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374a43e8951aa302c133beb5f85821a21645f07b487270ef3") .into(), ); @@ -192,23 +215,101 @@ mod tests { #[test] fn different_directions_are_using_different_accounts() { assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::ThisChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::ThisChain + ) + ), hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); assert_eq!( - PayRewardFromAccount::<(), H256>::rewards_account(RewardsAccountParams::new( - LaneId::new(1, 2), - *b"test", - RewardsAccountOwner::BridgedChain - )), + PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account( + RewardsAccountParams::new( + HashedLaneId::try_new(1, 2).unwrap(), + *b"test", + RewardsAccountOwner::BridgedChain + ) + ), hex_literal::hex!("627261700174657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc") .into(), ); } + + #[test] + fn pay_reward_from_account_for_legacy_lane_id_works() { + let test_data = vec![ + // Note: these accounts are used for integration tests within + // `bridges_rococo_westend.sh` + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhks", + RewardsAccountOwner::ThisChain, + (0_u16, "13E5fui97x6KTwNnSjaEKZ8s7kJNot5F3aUsy3jUtuoMyUec"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhks", + RewardsAccountOwner::BridgedChain, + (0_u16, "13E5fui9Ka9Vz4JbGN3xWjmwDNxnxF1N9Hhhbeu3VCqLChuj"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhpd", + RewardsAccountOwner::ThisChain, + (2_u16, "EoQBtnwtXqnSnr9cgBEJpKU7NjeC9EnR4D1VjgcvHz9ZYmS"), + ), + ( + LegacyLaneId([0, 0, 0, 1]), + b"bhpd", + RewardsAccountOwner::BridgedChain, + (2_u16, "EoQBtnx69txxumxSJexVzxYD1Q4LWAuWmRq8LrBWb27nhYN"), + ), + // Note: these accounts are used for integration tests within + // `bridges_polkadot_kusama.sh` from fellows. + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhwd", + RewardsAccountOwner::ThisChain, + (4_u16, "SNihsskf7bFhnHH9HJFMjWD3FJ96ESdAQTFZUAtXudRQbaH"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhwd", + RewardsAccountOwner::BridgedChain, + (4_u16, "SNihsskrjeSDuD5xumyYv9H8sxZEbNkG7g5C5LT8CfPdaSE"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhro", + RewardsAccountOwner::ThisChain, + (4_u16, "SNihsskf7bF2vWogkC6uFoiqPhd3dUX6TGzYZ1ocJdo3xHp"), + ), + ( + LegacyLaneId([0, 0, 0, 2]), + b"bhro", + RewardsAccountOwner::BridgedChain, + (4_u16, "SNihsskrjeRZ3ScWNfq6SSnw2N3BzQeCAVpBABNCbfmHENB"), + ), + ]; + + for (lane_id, bridged_chain_id, owner, (expected_ss58, expected_account)) in test_data { + assert_eq!( + expected_account, + sp_runtime::AccountId32::new(PayRewardFromAccount::< + [u8; 32], + [u8; 32], + LegacyLaneId, + >::rewards_account(RewardsAccountParams::new( + lane_id, + *bridged_chain_id, + owner + ))) + .to_ss58check_with_version(expected_ss58.into()) + ); + } + } } diff --git a/bridges/primitives/relayers/src/registration.rs b/bridges/primitives/relayers/src/registration.rs index 9d9b7e48122..d74ef18cf70 100644 --- a/bridges/primitives/relayers/src/registration.rs +++ b/bridges/primitives/relayers/src/registration.rs @@ -48,15 +48,17 @@ use sp_runtime::{ /// Either explicit account reference or `RewardsAccountParams`. #[derive(Clone, Debug)] -pub enum ExplicitOrAccountParams { +pub enum ExplicitOrAccountParams { /// Explicit account reference. Explicit(AccountId), /// Account, referenced using `RewardsAccountParams`. - Params(RewardsAccountParams), + Params(RewardsAccountParams), } -impl From for ExplicitOrAccountParams { - fn from(params: RewardsAccountParams) -> Self { +impl From> + for ExplicitOrAccountParams +{ + fn from(params: RewardsAccountParams) -> Self { ExplicitOrAccountParams::Params(params) } } @@ -103,9 +105,9 @@ pub trait StakeAndSlash { /// `beneficiary`. /// /// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake. - fn repatriate_reserved( + fn repatriate_reserved( relayer: &AccountId, - beneficiary: ExplicitOrAccountParams, + beneficiary: ExplicitOrAccountParams, amount: Balance, ) -> Result; } @@ -126,9 +128,9 @@ where Zero::zero() } - fn repatriate_reserved( + fn repatriate_reserved( _relayer: &AccountId, - _beneficiary: ExplicitOrAccountParams, + _beneficiary: ExplicitOrAccountParams, _amount: Balance, ) -> Result { Ok(Zero::zero()) diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index 0db4eac79a7..eba3bcadfea 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -365,17 +365,23 @@ macro_rules! decl_bridge_finality_runtime_apis { }; } +// Re-export to avoid include tuplex dependency everywhere. +#[doc(hidden)] +pub mod __private { + pub use codec; +} + /// Convenience macro that declares bridge messages runtime apis and related constants for a chain. /// This includes: /// - chain-specific bridge runtime APIs: -/// - `ToOutboundLaneApi` -/// - `FromInboundLaneApi` +/// - `ToOutboundLaneApi` +/// - `FromInboundLaneApi` /// - constants that are stringified names of runtime API methods: /// - `FROM__MESSAGE_DETAILS_METHOD`, /// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_messages_runtime_apis { - ($chain: ident) => { + ($chain: ident, $lane_id_type:ty) => { bp_runtime::paste::item! { mod [<$chain _messages_api>] { use super::*; @@ -400,7 +406,7 @@ macro_rules! decl_bridge_messages_runtime_apis { /// If some (or all) messages are missing from the storage, they'll also will /// be missing from the resulting vector. The vector is ordered by the nonce. fn message_details( - lane: bp_messages::LaneId, + lane: $lane_id_type, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> sp_std::vec::Vec; @@ -416,7 +422,7 @@ macro_rules! decl_bridge_messages_runtime_apis { pub trait [] { /// Return details of given inbound messages. fn message_details( - lane: bp_messages::LaneId, + lane: $lane_id_type, messages: sp_std::vec::Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> sp_std::vec::Vec; } @@ -433,8 +439,8 @@ macro_rules! decl_bridge_messages_runtime_apis { /// The name of the chain has to be specified in snake case (e.g. `bridge_hub_polkadot`). #[macro_export] macro_rules! decl_bridge_runtime_apis { - ($chain: ident $(, $consensus: ident)?) => { + ($chain: ident $(, $consensus: ident, $lane_id_type:ident)?) => { bp_runtime::decl_bridge_finality_runtime_apis!($chain $(, $consensus)?); - bp_runtime::decl_bridge_messages_runtime_apis!($chain); + bp_runtime::decl_bridge_messages_runtime_apis!($chain, $lane_id_type); }; } diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs index 8f5040ad9a1..90eb72922be 100644 --- a/bridges/primitives/runtime/src/lib.rs +++ b/bridges/primitives/runtime/src/lib.rs @@ -36,7 +36,7 @@ use sp_std::{fmt::Debug, ops::RangeInclusive, vec, vec::Vec}; pub use chain::{ AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, EncodedOrDecodedCall, HashOf, HasherOf, HeaderOf, NonceOf, Parachain, ParachainIdOf, SignatureOf, TransactionEraOf, - UnderlyingChainOf, UnderlyingChainProvider, + UnderlyingChainOf, UnderlyingChainProvider, __private, }; pub use frame_support::storage::storage_prefix as storage_value_final_key; use num_traits::{CheckedAdd, CheckedSub, One, SaturatingAdd, Zero}; @@ -272,7 +272,7 @@ pub trait StorageMapKeyProvider { } } -/// Can be use to access the runtime storage key of a `StorageDoubleMap`. +/// Can be used to access the runtime storage key of a `StorageDoubleMap`. pub trait StorageDoubleMapKeyProvider { /// The name of the variable that holds the `StorageDoubleMap`. const MAP_NAME: &'static str; diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs index 44a90a57d4f..061e7a27506 100644 --- a/bridges/primitives/xcm-bridge-hub/src/lib.rs +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use bp_messages::LaneId; +use bp_messages::LaneIdType; use bp_runtime::{AccountIdOf, BalanceOf, Chain}; pub use call_info::XcmBridgeHubCall; use codec::{Decode, Encode, MaxEncodedLen}; @@ -63,7 +63,6 @@ pub type XcmAsPlainPayload = sp_std::vec::Vec; Ord, PartialOrd, PartialEq, - RuntimeDebug, TypeInfo, MaxEncodedLen, Serialize, @@ -90,6 +89,12 @@ impl BridgeId { } } +impl core::fmt::Debug for BridgeId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) + } +} + /// Local XCM channel manager. pub trait LocalXcmChannelManager { /// Error that may be returned when suspending/resuming the bridge. @@ -149,8 +154,8 @@ pub enum BridgeState { #[derive( CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound, )] -#[scale_info(skip_type_params(ThisChain))] -pub struct Bridge { +#[scale_info(skip_type_params(ThisChain, LaneId))] +pub struct Bridge { /// Relative location of the bridge origin chain. This is expected to be **convertible** to the /// `latest` XCM, so the check and migration needs to be ensured. pub bridge_origin_relative_location: Box, @@ -204,6 +209,8 @@ pub enum BridgeLocationsError { UnsupportedDestinationLocation, /// The version of XCM location argument is unsupported. UnsupportedXcmVersion, + /// The `LaneIdType` generator is not supported. + UnsupportedLaneIdType, } impl BridgeLocations { @@ -318,7 +325,7 @@ impl BridgeLocations { /// Generates the exact same `LaneId` on the both bridge hubs. /// /// Note: Use this **only** when opening a new bridge. - pub fn calculate_lane_id( + pub fn calculate_lane_id( &self, xcm_version: XcmVersion, ) -> Result { @@ -341,10 +348,11 @@ impl BridgeLocations { .into_version(xcm_version) .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion); - Ok(LaneId::new( + LaneId::try_new( EncodedVersionedInteriorLocation(universal_location1.encode()), EncodedVersionedInteriorLocation(universal_location2.encode()), - )) + ) + .map_err(|_| BridgeLocationsError::UnsupportedLaneIdType) } } @@ -590,6 +598,8 @@ mod tests { #[test] fn calculate_lane_id_works() { + type TestLaneId = bp_messages::HashedLaneId; + let from_local_to_remote = run_successful_test(SuccessfulTest { here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)] .into(), @@ -631,12 +641,12 @@ mod tests { }); assert_ne!( - from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), - from_remote_to_local.calculate_lane_id(xcm::latest::VERSION - 1), + from_local_to_remote.calculate_lane_id::(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id::(xcm::latest::VERSION - 1), ); assert_eq!( - from_local_to_remote.calculate_lane_id(xcm::latest::VERSION), - from_remote_to_local.calculate_lane_id(xcm::latest::VERSION), + from_local_to_remote.calculate_lane_id::(xcm::latest::VERSION), + from_remote_to_local.calculate_lane_id::(xcm::latest::VERSION), ); } diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs index 227e9c31c5b..9856f0d0237 100644 --- a/bridges/relays/client-substrate/src/chain.rs +++ b/bridges/relays/client-substrate/src/chain.rs @@ -113,9 +113,6 @@ impl Parachain for T where T: UnderlyingChainProvider + Chain + ParachainBase /// Substrate-based chain with messaging support from minimal relay-client point of view. pub trait ChainWithMessages: Chain + ChainWithMessagesBase { - // TODO (https://github.com/paritytech/parity-bridges-common/issues/1692): check all the names - // after the issue is fixed - all names must be changed - /// Name of the bridge relayers pallet (used in `construct_runtime` macro call) that is deployed /// at some other chain to bridge with this `ChainWithMessages`. /// diff --git a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs index 28b0eb0ad52..2e15562f6c2 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs @@ -23,9 +23,12 @@ use crate::{ parachains::SubstrateParachainsPipeline, }; use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; +use codec::{Codec, EncodeLike}; +use messages_relay::Labeled; use relay_substrate_client::{ Chain, ChainWithRuntimeVersion, ChainWithTransactions, Parachain, RelayChain, }; +use std::fmt::Debug; /// Minimal bridge representation that can be used from the CLI. /// It connects a source chain to a target chain. @@ -99,7 +102,22 @@ where /// Bridge representation that can be used from the CLI for relaying messages. pub trait MessagesCliBridge: CliBridgeBase { /// The Source -> Destination messages synchronization pipeline. - type MessagesLane: SubstrateMessageLane; + type MessagesLane: SubstrateMessageLane< + SourceChain = Self::Source, + TargetChain = Self::Target, + LaneId = Self::LaneId, + >; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; /// Optional messages delivery transaction limits that the messages relay is going /// to use. If it returns `None`, limits are estimated using `TransactionPayment` API @@ -108,3 +126,6 @@ pub trait MessagesCliBridge: CliBridgeBase { None } } + +/// An alias for lane identifier type. +pub type MessagesLaneIdOf = ::LaneId; diff --git a/bridges/relays/lib-substrate-relay/src/cli/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/mod.rs index ef8403ff68e..be64866fc14 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/mod.rs @@ -16,10 +16,8 @@ //! Deal with CLI args of substrate-to-substrate relay. -use bp_messages::LaneId; use rbtag::BuildInfo; -use sp_core::H256; -use sp_runtime::Either; +use sp_runtime::traits::TryConvert; use std::str::FromStr; use structopt::StructOpt; use strum::{EnumString, VariantNames}; @@ -43,36 +41,19 @@ pub type DefaultClient = relay_substrate_client::RpcWithCachingClient; /// Lane id. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct HexLaneId(Either); +pub struct HexLaneId(Vec); -impl From for LaneId { - fn from(lane_id: HexLaneId) -> LaneId { - LaneId::from_inner(lane_id.0) +impl>> TryConvert for HexLaneId { + fn try_convert(lane_id: HexLaneId) -> Result { + T::try_from(lane_id.0.clone()).map_err(|_| lane_id) } } impl FromStr for HexLaneId { - type Err = rustc_hex::FromHexError; + type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - // check `H256` variant at first - match H256::from_str(s) { - Ok(hash) => Ok(HexLaneId(Either::Left(hash))), - Err(hash_error) => { - // check backwards compatible - let mut lane_id = [0u8; 4]; - match hex::decode_to_slice(s, &mut lane_id) { - Ok(_) => Ok(HexLaneId(Either::Right(lane_id))), - Err(array_error) => { - log::error!( - target: "bridge", - "Failed to parse `HexLaneId` as hex string: {s:?} - hash_error: {hash_error:?}, array_error: {array_error:?}", - ); - Err(hash_error) - }, - } - }, - } + hex::decode(s).map(Self) } } @@ -172,6 +153,8 @@ pub enum RuntimeVersionType { #[cfg(test)] mod tests { use super::*; + use bp_messages::{HashedLaneId, LegacyLaneId}; + use sp_core::H256; #[test] fn hex_lane_id_from_str_works() { @@ -185,21 +168,21 @@ mod tests { ) .is_err()); assert_eq!( - LaneId::from( + HexLaneId::try_convert( HexLaneId::from_str( "0101010101010101010101010101010101010101010101010101010101010101" ) .unwrap() ), - LaneId::from_inner(Either::Left(H256::from([1u8; 32]))) + Ok(HashedLaneId::from_inner(H256::from([1u8; 32]))) ); // array variant assert!(HexLaneId::from_str("0000001").is_err()); assert!(HexLaneId::from_str("000000001").is_err()); assert_eq!( - LaneId::from(HexLaneId::from_str("00000001").unwrap()), - LaneId::from_inner(Either::Right([0, 0, 0, 1])) + HexLaneId::try_convert(HexLaneId::from_str("00000001").unwrap()), + Ok(LegacyLaneId([0, 0, 0, 1])) ); } } diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 3786976bed9..9261dc43753 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -31,25 +31,30 @@ pub mod relay_to_relay; pub mod relay_to_parachain; use async_trait::async_trait; -use std::{marker::PhantomData, sync::Arc}; +use codec::{Codec, EncodeLike}; +use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use structopt::StructOpt; use futures::{FutureExt, TryFutureExt}; use crate::{ - cli::{bridge::MessagesCliBridge, DefaultClient, HexLaneId, PrometheusParams}, + cli::{ + bridge::{MessagesCliBridge, MessagesLaneIdOf}, + DefaultClient, HexLaneId, PrometheusParams, + }, messages::{MessagesRelayLimits, MessagesRelayParams}, on_demand::OnDemandRelay, HeadersToRelay, TaggedAccount, TransactionParams, }; -use bp_messages::LaneId; use bp_runtime::BalanceOf; +use messages_relay::Labeled; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithMessages, ChainWithRuntimeVersion, ChainWithTransactions, }; use relay_utils::metrics::MetricsParams; use sp_core::Pair; +use sp_runtime::traits::TryConvert; /// Parameters that have the same names across all bridges. #[derive(Debug, PartialEq, StructOpt)] @@ -163,7 +168,7 @@ where &self, source_to_target_headers_relay: Arc>, target_to_source_headers_relay: Arc>, - lane_id: LaneId, + lane_id: MessagesLaneIdOf, maybe_limits: Option, ) -> MessagesRelayParams, DefaultClient> { MessagesRelayParams { @@ -234,9 +239,20 @@ where + ChainWithRuntimeVersion; /// Left to Right bridge. - type L2R: MessagesCliBridge; + type L2R: MessagesCliBridge; /// Right to Left bridge - type R2L: MessagesCliBridge; + type R2L: MessagesCliBridge; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; /// Construct new bridge. fn new(params: ::Params) -> anyhow::Result; @@ -287,30 +303,29 @@ where self.mut_base().start_on_demand_headers_relayers().await?; // add balance-related metrics - let lanes = self + let lanes: Vec = self .base() .common() .shared .lane .iter() .cloned() - .map(Into::into) - .collect::>(); + .map(HexLaneId::try_convert) + .collect::, HexLaneId>>() + .expect(""); { let common = self.mut_base().mut_common(); - crate::messages::metrics::add_relay_balances_metrics::<_, Self::Right>( - common.left.client.clone(), - &common.metrics_params, - &common.left.accounts, - &lanes, - ) + crate::messages::metrics::add_relay_balances_metrics::< + _, + Self::Right, + MessagesLaneIdOf, + >(common.left.client.clone(), &common.metrics_params, &common.left.accounts, &lanes) .await?; - crate::messages::metrics::add_relay_balances_metrics::<_, Self::Left>( - common.right.client.clone(), - &common.metrics_params, - &common.right.accounts, - &lanes, - ) + crate::messages::metrics::add_relay_balances_metrics::< + _, + Self::Left, + MessagesLaneIdOf, + >(common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes) .await?; } @@ -359,8 +374,6 @@ mod tests { use crate::{cli::chain_schema::RuntimeVersionType, declare_chain_cli_schema}; use relay_substrate_client::{ChainRuntimeVersion, Parachain, SimpleRuntimeVersion}; - use sp_core::H256; - use sp_runtime::Either; #[test] // We need `#[allow(dead_code)]` because some of the methods generated by the macros @@ -434,7 +447,7 @@ mod tests { res, BridgeHubKusamaBridgeHubPolkadotHeadersAndMessages { shared: HeadersAndMessagesSharedParams { - lane: vec![HexLaneId(Either::Left(H256::from([0x00u8; 32])))], + lane: vec![HexLaneId(vec![0x00u8; 32])], only_mandatory_headers: false, only_free_headers: false, prometheus_params: PrometheusParams { diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs index 34d5226e90c..3878b081d6c 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs @@ -33,6 +33,7 @@ use relay_substrate_client::{ ChainWithTransactions, Client, }; use relay_utils::UniqueSaturatedInto; +use sp_runtime::traits::TryConvert; /// Messages relaying params. #[derive(StructOpt)] @@ -116,6 +117,9 @@ where let target_client = data.target.into_client::().await?; let target_sign = data.target_sign.to_keypair::()?; let target_transactions_mortality = data.target_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; crate::messages::run::(MessagesRelayParams { source_client, @@ -130,7 +134,7 @@ where }, source_to_target_headers_relay: None, target_to_source_headers_relay: None, - lane_id: data.lane.into(), + lane_id, limits: Self::maybe_messages_limits(), metrics_params: data.prometheus_params.into_metrics_params()?, }) @@ -146,6 +150,9 @@ where let source_transactions_mortality = data.source_sign.transactions_mortality()?; let target_sign = data.target_sign.to_keypair::()?; let target_transactions_mortality = data.target_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; let at_source_block = source_client .header_by_number(data.at_source_block.unique_saturated_into()) @@ -167,7 +174,7 @@ where TransactionParams { signer: source_sign, mortality: source_transactions_mortality }, TransactionParams { signer: target_sign, mortality: target_transactions_mortality }, at_source_block, - data.lane.into(), + lane_id, data.messages_start..=data.messages_end, data.outbound_state_proof_required, ) @@ -182,6 +189,9 @@ where let target_client = data.target.into_client::().await?; let source_sign = data.source_sign.to_keypair::()?; let source_transactions_mortality = data.source_sign.transactions_mortality()?; + let lane_id = HexLaneId::try_convert(data.lane).map_err(|invalid_lane_id| { + anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) + })?; let at_target_block = target_client .header_by_number(data.at_target_block.unique_saturated_into()) @@ -202,7 +212,7 @@ where target_client, TransactionParams { signer: source_sign, mortality: source_transactions_mortality }, at_target_block, - data.lane.into(), + lane_id, ) .await } diff --git a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs index 8845f43dcb6..9d45a4b3d66 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs @@ -18,11 +18,11 @@ use crate::TaggedAccount; -use bp_messages::LaneId; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::StorageDoubleMapKeyProvider; -use codec::Decode; +use codec::{Decode, EncodeLike}; use frame_system::AccountInfo; +use messages_relay::Labeled; use pallet_balances::AccountData; use relay_substrate_client::{ metrics::{FloatStorageValue, FloatStorageValueMetric}, @@ -35,7 +35,7 @@ use sp_runtime::{FixedPointNumber, FixedU128}; use std::{fmt::Debug, marker::PhantomData}; /// Add relay accounts balance metrics. -pub async fn add_relay_balances_metrics( +pub async fn add_relay_balances_metrics( client: impl Client, metrics: &MetricsParams, relay_accounts: &Vec>>, @@ -43,6 +43,7 @@ pub async fn add_relay_balances_metrics anyhow::Result<()> where BalanceOf: Into + std::fmt::Debug, + LaneId: Clone + Copy + Decode + EncodeLike + Send + Sync + Labeled, { if relay_accounts.is_empty() { return Ok(()) @@ -86,25 +87,25 @@ where FloatStorageValueMetric::new( AccountBalance:: { token_decimals, _phantom: Default::default() }, client.clone(), - bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf, LaneId>::final_key( relayers_pallet_name, account.id(), &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::ThisChain), ), - format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), - format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + format!("at_{}_relay_{}_reward_for_msgs_from_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()), + format!("Reward of the {} relay account at {} for delivering messages from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()), )?.register_and_spawn(&metrics.registry)?; FloatStorageValueMetric::new( AccountBalance:: { token_decimals, _phantom: Default::default() }, client.clone(), - bp_relayers::RelayerRewardsKeyProvider::, BalanceOf>::final_key( + bp_relayers::RelayerRewardsKeyProvider::, BalanceOf, LaneId>::final_key( relayers_pallet_name, account.id(), &RewardsAccountParams::new(*lane, BC::ID, RewardsAccountOwner::BridgedChain), ), - format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, hex::encode(lane.as_ref())), - format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane), + format!("at_{}_relay_{}_reward_for_msgs_to_{}_on_lane_{}", C::NAME, account.tag(), BC::NAME, lane.label()), + format!("Reward of the {} relay account at {} for delivering messages confirmations from {} on lane {:?}", account.tag(), C::NAME, BC::NAME, lane.label()), )?.register_and_spawn(&metrics.registry)?; } } diff --git a/bridges/relays/lib-substrate-relay/src/messages/mod.rs b/bridges/relays/lib-substrate-relay/src/messages/mod.rs index 28bc5c7f5e8..f7031648bc3 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/mod.rs @@ -27,19 +27,17 @@ use crate::{ use async_std::sync::Arc; use bp_messages::{ - target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, LaneId, MessageNonce, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, MessageNonce, }; -use bp_runtime::{ - AccountIdOf, Chain as _, EncodedOrDecodedCall, HeaderIdOf, TransactionEra, WeightExtraOps, -}; -use codec::Encode; +use bp_runtime::{AccountIdOf, EncodedOrDecodedCall, HeaderIdOf, TransactionEra, WeightExtraOps}; +use codec::{Codec, Encode, EncodeLike}; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; -use messages_relay::{message_lane::MessageLane, message_lane_loop::BatchTransaction}; +use messages_relay::{message_lane::MessageLane, message_lane_loop::BatchTransaction, Labeled}; use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig}; use relay_substrate_client::{ transaction_stall_timeout, AccountKeyPairOf, BalanceOf, BlockNumberOf, CallOf, Chain, - ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf, SignParam, - UnsignedTransaction, + ChainBase, ChainWithMessages, ChainWithTransactions, Client, Error as SubstrateError, HashOf, + SignParam, UnsignedTransaction, }; use relay_utils::{ metrics::{GlobalMetrics, MetricsParams, StandaloneMetric}, @@ -60,6 +58,18 @@ pub trait SubstrateMessageLane: 'static + Clone + Debug + Send + Sync { /// Messages from the `SourceChain` are dispatched on this chain. type TargetChain: ChainWithMessages + ChainWithTransactions; + /// Lane identifier type. + type LaneId: Clone + + Copy + + Debug + + Codec + + EncodeLike + + Send + + Sync + + Labeled + + TryFrom> + + Default; + /// How receive messages proof call is built? type ReceiveMessagesProofCallBuilder: ReceiveMessagesProofCallBuilder; /// How receive messages delivery proof call is built? @@ -81,8 +91,10 @@ impl MessageLane for MessageLaneAdapter

{ const SOURCE_NAME: &'static str = P::SourceChain::NAME; const TARGET_NAME: &'static str = P::TargetChain::NAME; - type MessagesProof = SubstrateMessagesProof; - type MessagesReceivingProof = SubstrateMessagesDeliveryProof; + type LaneId = P::LaneId; + + type MessagesProof = SubstrateMessagesProof; + type MessagesReceivingProof = SubstrateMessagesDeliveryProof; type SourceChainBalance = BalanceOf; type SourceHeaderNumber = BlockNumberOf; @@ -109,7 +121,7 @@ pub struct MessagesRelayParams pub target_to_source_headers_relay: Option>>, /// Identifier of lane that needs to be served. - pub lane_id: LaneId, + pub lane_id: P::LaneId, /// Messages relay limits. If not provided, the relay tries to determine it automatically, /// using `TransactionPayment` pallet runtime API. pub limits: Option, @@ -293,7 +305,7 @@ pub async fn relay_messages_range( source_transaction_params: TransactionParams>, target_transaction_params: TransactionParams>, at_source_block: HeaderIdOf, - lane_id: LaneId, + lane_id: P::LaneId, range: RangeInclusive, outbound_state_proof_required: bool, ) -> anyhow::Result<()> @@ -335,7 +347,7 @@ pub async fn relay_messages_delivery_confirmation( target_client: impl Client, source_transaction_params: TransactionParams>, at_target_block: HeaderIdOf, - lane_id: LaneId, + lane_id: P::LaneId, ) -> anyhow::Result<()> where AccountIdOf: From< as Pair>::Public>, @@ -372,7 +384,7 @@ pub trait ReceiveMessagesProofCallBuilder { /// messages module at the target chain. fn build_receive_messages_proof_call( relayer_id_at_source: AccountIdOf, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, messages_count: u32, dispatch_weight: Weight, trace_call: bool, @@ -388,7 +400,7 @@ pub struct DirectReceiveMessagesProofCallBuilder { impl ReceiveMessagesProofCallBuilder

for DirectReceiveMessagesProofCallBuilder where P: SubstrateMessageLane, - R: BridgeMessagesConfig, + R: BridgeMessagesConfig, I: 'static, R::BridgedChain: bp_runtime::Chain, Hash = HashOf>, @@ -396,7 +408,7 @@ where { fn build_receive_messages_proof_call( relayer_id_at_source: AccountIdOf, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, messages_count: u32, dispatch_weight: Weight, trace_call: bool, @@ -444,7 +456,8 @@ macro_rules! generate_receive_message_proof_call_builder { <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain >, proof: $crate::messages::source::SubstrateMessagesProof< - <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain + <$pipeline as $crate::messages::SubstrateMessageLane>::SourceChain, + <$pipeline as $crate::messages::SubstrateMessageLane>::LaneId >, messages_count: u32, dispatch_weight: bp_messages::Weight, @@ -470,7 +483,7 @@ pub trait ReceiveMessagesDeliveryProofCallBuilder { /// Given messages delivery proof, build call of `receive_messages_delivery_proof` function of /// bridge messages module at the source chain. fn build_receive_messages_delivery_proof_call( - proof: SubstrateMessagesDeliveryProof, + proof: SubstrateMessagesDeliveryProof, trace_call: bool, ) -> CallOf; } @@ -485,13 +498,13 @@ impl ReceiveMessagesDeliveryProofCallBuilder

for DirectReceiveMessagesDeliveryProofCallBuilder where P: SubstrateMessageLane, - R: BridgeMessagesConfig, + R: BridgeMessagesConfig, I: 'static, R::BridgedChain: bp_runtime::Chain>, CallOf: From> + GetDispatchInfo, { fn build_receive_messages_delivery_proof_call( - proof: SubstrateMessagesDeliveryProof, + proof: SubstrateMessagesDeliveryProof, trace_call: bool, ) -> CallOf { let call: CallOf = @@ -533,7 +546,8 @@ macro_rules! generate_receive_message_delivery_proof_call_builder { { fn build_receive_messages_delivery_proof_call( proof: $crate::messages::target::SubstrateMessagesDeliveryProof< - <$pipeline as $crate::messages::SubstrateMessageLane>::TargetChain + <$pipeline as $crate::messages::SubstrateMessageLane>::TargetChain, + <$pipeline as $crate::messages::SubstrateMessageLane>::LaneId >, _trace_call: bool, ) -> relay_substrate_client::CallOf< @@ -644,7 +658,7 @@ where FromBridgedChainMessagesProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: P::LaneId::default(), nonces_start: 1, nonces_end: messages as u64, }, @@ -674,7 +688,7 @@ where mod tests { use super::*; use bp_messages::{ - source_chain::FromBridgedChainMessagesDeliveryProof, UnrewardedRelayersState, + source_chain::FromBridgedChainMessagesDeliveryProof, LaneIdType, UnrewardedRelayersState, }; use relay_substrate_client::calls::{UtilityCall as MockUtilityCall, UtilityCall}; @@ -687,8 +701,8 @@ mod tests { } pub type CodegenBridgeMessagesCall = bp_messages::BridgeMessagesCall< u64, - Box>, - FromBridgedChainMessagesDeliveryProof, + Box>, + FromBridgedChainMessagesDeliveryProof, >; impl From> for RuntimeCall { @@ -706,7 +720,7 @@ mod tests { let receive_messages_proof = FromBridgedChainMessagesProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: mock::TestLaneIdType::try_new(1, 2).unwrap(), nonces_start: 0, nonces_end: 0, }; @@ -761,7 +775,7 @@ mod tests { let receive_messages_delivery_proof = FromBridgedChainMessagesDeliveryProof { bridged_header_hash: Default::default(), storage_proof: Default::default(), - lane: LaneId::new(1, 2), + lane: mock::TestLaneIdType::try_new(1, 2).unwrap(), }; let relayers_state = UnrewardedRelayersState { unrewarded_relayer_entries: 0, @@ -808,7 +822,7 @@ mod tests { // mock runtime with `pallet_bridge_messages` mod mock { use super::super::*; - use bp_messages::target_chain::ForbidInboundMessages; + use bp_messages::{target_chain::ForbidInboundMessages, HashedLaneId}; use bp_runtime::ChainId; use frame_support::derive_impl; use sp_core::H256; @@ -819,6 +833,9 @@ mod tests { type Block = frame_system::mocking::MockBlock; pub type SignedBlock = generic::SignedBlock; + /// Lane identifier type used for tests. + pub type TestLaneIdType = HashedLaneId; + frame_support::construct_runtime! { pub enum TestRuntime { @@ -840,10 +857,11 @@ mod tests { type BridgedHeaderChain = BridgedHeaderChain; type OutboundPayload = Vec; type InboundPayload = Vec; + type LaneId = TestLaneIdType; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); type OnMessagesDelivered = (); - type MessageDispatch = ForbidInboundMessages>; + type MessageDispatch = ForbidInboundMessages, Self::LaneId>; } pub struct ThisUnderlyingChain; @@ -1005,6 +1023,7 @@ mod tests { impl SubstrateMessageLane for ThisChainToBridgedChainMessageLane { type SourceChain = ThisChain; type TargetChain = BridgedChain; + type LaneId = mock::TestLaneIdType; type ReceiveMessagesProofCallBuilder = ThisChainToBridgedChainMessageLaneReceiveMessagesProofCallBuilder; type ReceiveMessagesDeliveryProofCallBuilder = diff --git a/bridges/relays/lib-substrate-relay/src/messages/source.rs b/bridges/relays/lib-substrate-relay/src/messages/source.rs index 2c49df3452a..b560867a235 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/source.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/source.rs @@ -34,7 +34,7 @@ use async_trait::async_trait; use bp_messages::{ storage_keys::{operating_mode_key, outbound_lane_data_key}, target_chain::FromBridgedChainMessagesProof, - ChainWithMessages as _, InboundMessageDetails, LaneId, MessageNonce, MessagePayload, + ChainWithMessages as _, InboundMessageDetails, MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, }; use bp_runtime::{BasicOperatingMode, HeaderIdProvider, RangeInclusiveExt}; @@ -60,14 +60,14 @@ use std::ops::RangeInclusive; /// Intermediate message proof returned by the source Substrate node. Includes everything /// required to submit to the target node: cumulative dispatch weight of bundled messages and /// the proof itself. -pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof>); +pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof, L>); type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>; /// Substrate client as Substrate messages source. pub struct SubstrateMessagesSource { source_client: SourceClnt, target_client: TargetClnt, - lane_id: LaneId, + lane_id: P::LaneId, transaction_params: TransactionParams>, target_to_source_headers_relay: Option>>, } @@ -79,7 +79,7 @@ impl, TargetClnt> pub fn new( source_client: SourceClnt, target_client: TargetClnt, - lane_id: LaneId, + lane_id: P::LaneId, transaction_params: TransactionParams>, target_to_source_headers_relay: Option< Arc>, @@ -256,8 +256,11 @@ where } let best_target_header_hash = self.target_client.best_header_hash().await?; - for mut msgs_to_refine_batch in - split_msgs_to_refine::(self.lane_id, msgs_to_refine)? + for mut msgs_to_refine_batch in split_msgs_to_refine::< + P::SourceChain, + P::TargetChain, + P::LaneId, + >(self.lane_id, msgs_to_refine)? { let in_msgs_details = self .target_client @@ -542,7 +545,7 @@ fn validate_out_msgs_details( Ok(()) } -fn split_msgs_to_refine( +fn split_msgs_to_refine( lane_id: LaneId, msgs_to_refine: MessagesToRefine, ) -> Result, SubstrateError> { @@ -578,8 +581,12 @@ fn split_msgs_to_refine( #[cfg(test)] mod tests { use super::*; + use bp_messages::{HashedLaneId, LaneIdType}; use relay_substrate_client::test_chain::TestChain; + /// Lane identifier type used for tests. + type TestLaneIdType = HashedLaneId; + fn message_details_from_rpc( nonces: RangeInclusive, ) -> Vec { @@ -660,8 +667,10 @@ mod tests { msgs_to_refine.push((payload, out_msg_details)); } - let maybe_batches = - split_msgs_to_refine::(LaneId::new(1, 2), msgs_to_refine); + let maybe_batches = split_msgs_to_refine::( + TestLaneIdType::try_new(1, 2).unwrap(), + msgs_to_refine, + ); match expected_batches { Ok(expected_batches) => { let batches = maybe_batches.unwrap(); diff --git a/bridges/relays/lib-substrate-relay/src/messages/target.rs b/bridges/relays/lib-substrate-relay/src/messages/target.rs index a6bf169cffb..0d1aac88a32 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/target.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/target.rs @@ -36,7 +36,7 @@ use async_std::sync::Arc; use async_trait::async_trait; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, storage_keys::inbound_lane_data_key, - ChainWithMessages as _, InboundLaneData, LaneId, MessageNonce, UnrewardedRelayersState, + ChainWithMessages as _, InboundLaneData, MessageNonce, UnrewardedRelayersState, }; use messages_relay::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, @@ -51,14 +51,14 @@ use sp_core::Pair; use std::{convert::TryFrom, ops::RangeInclusive}; /// Message receiving proof returned by the target Substrate node. -pub type SubstrateMessagesDeliveryProof = - (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof>); +pub type SubstrateMessagesDeliveryProof = + (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof, L>); /// Substrate client as Substrate messages target. pub struct SubstrateMessagesTarget { target_client: TargetClnt, source_client: SourceClnt, - lane_id: LaneId, + lane_id: P::LaneId, relayer_id_at_source: AccountIdOf, transaction_params: Option>>, source_to_target_headers_relay: Option>>, @@ -73,7 +73,7 @@ where pub fn new( target_client: TargetClnt, source_client: SourceClnt, - lane_id: LaneId, + lane_id: P::LaneId, relayer_id_at_source: AccountIdOf, transaction_params: Option>>, source_to_target_headers_relay: Option< @@ -308,7 +308,7 @@ where fn make_messages_delivery_call( relayer_id_at_source: AccountIdOf, nonces: RangeInclusive, - proof: SubstrateMessagesProof, + proof: SubstrateMessagesProof, trace_call: bool, ) -> CallOf { let messages_count = nonces.end() - nonces.start() + 1; diff --git a/bridges/relays/messages/Cargo.toml b/bridges/relays/messages/Cargo.toml index c7a132bb3ba..f9df73507c7 100644 --- a/bridges/relays/messages/Cargo.toml +++ b/bridges/relays/messages/Cargo.toml @@ -26,3 +26,6 @@ finality-relay = { workspace = true } relay-utils = { workspace = true } sp-arithmetic = { workspace = true, default-features = true } + +[dev-dependencies] +sp-core = { workspace = true } diff --git a/bridges/relays/messages/src/lib.rs b/bridges/relays/messages/src/lib.rs index 78a3237ba4f..f5e09f4d468 100644 --- a/bridges/relays/messages/src/lib.rs +++ b/bridges/relays/messages/src/lib.rs @@ -38,3 +38,4 @@ mod message_race_strategy; pub use message_race_delivery::relay_messages_range; pub use message_race_receiving::relay_messages_delivery_confirmation; +pub use metrics::Labeled; diff --git a/bridges/relays/messages/src/message_lane.rs b/bridges/relays/messages/src/message_lane.rs index 5c9728ad93a..84c1e57ba4e 100644 --- a/bridges/relays/messages/src/message_lane.rs +++ b/bridges/relays/messages/src/message_lane.rs @@ -19,6 +19,7 @@ //! 1) relay new messages from source to target node; //! 2) relay proof-of-delivery from target to source node. +use crate::metrics::Labeled; use num_traits::{SaturatingAdd, Zero}; use relay_utils::{BlockNumberBase, HeaderId}; use sp_arithmetic::traits::AtLeast32BitUnsigned; @@ -31,6 +32,9 @@ pub trait MessageLane: 'static + Clone + Send + Sync { /// Name of the messages target. const TARGET_NAME: &'static str; + /// Lane identifier type. + type LaneId: Clone + Send + Sync + Labeled; + /// Messages proof. type MessagesProof: Clone + Debug + Send + Sync; /// Messages receiving proof. diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 995499092c3..36de637f04c 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -29,7 +29,7 @@ use std::{collections::BTreeMap, fmt::Debug, future::Future, ops::RangeInclusive use async_trait::async_trait; use futures::{channel::mpsc::unbounded, future::FutureExt, stream::StreamExt}; -use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState, Weight}; +use bp_messages::{MessageNonce, UnrewardedRelayersState, Weight}; use relay_utils::{ interval, metrics::MetricsParams, process_future_result, relay_loop::Client as RelayClient, retry_backoff, FailedClient, TransactionTracker, @@ -39,12 +39,12 @@ use crate::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, message_race_delivery::run as run_message_delivery_race, message_race_receiving::run as run_message_receiving_race, - metrics::MessageLaneLoopMetrics, + metrics::{Labeled, MessageLaneLoopMetrics}, }; /// Message lane loop configuration params. #[derive(Debug, Clone)] -pub struct Params { +pub struct Params { /// Id of lane this loop is servicing. pub lane: LaneId, /// Interval at which we ask target node about its updates. @@ -275,13 +275,13 @@ pub struct ClientsState { /// Return prefix that will be used by default to expose Prometheus metrics of the finality proofs /// sync loop. -pub fn metrics_prefix(lane: &LaneId) -> String { - format!("{}_to_{}_MessageLane_{:?}", P::SOURCE_NAME, P::TARGET_NAME, lane) +pub fn metrics_prefix(lane: &P::LaneId) -> String { + format!("{}_to_{}_MessageLane_{}", P::SOURCE_NAME, P::TARGET_NAME, lane.label()) } /// Run message lane service loop. pub async fn run( - params: Params, + params: Params, source_client: impl SourceClient

, target_client: impl TargetClient

, metrics_params: MetricsParams, @@ -309,7 +309,7 @@ pub async fn run( /// Run one-way message delivery loop until connection with target or source node is lost, or exit /// signal is received. async fn run_until_connection_lost, TC: TargetClient

>( - params: Params, + params: Params, source_client: SC, target_client: TC, metrics_msg: Option, @@ -471,9 +471,9 @@ async fn run_until_connection_lost, TC: Targ pub(crate) mod tests { use std::sync::Arc; + use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId}; use futures::stream::StreamExt; use parking_lot::Mutex; - use relay_utils::{HeaderId, MaybeConnectionError, TrackedTransactionStatus}; use super::*; @@ -504,6 +504,9 @@ pub(crate) mod tests { } } + /// Lane identifier type used for tests. + pub type TestLaneIdType = HashedLaneId; + #[derive(Clone)] pub struct TestMessageLane; @@ -520,6 +523,8 @@ pub(crate) mod tests { type TargetHeaderNumber = TestTargetHeaderNumber; type TargetHeaderHash = TestTargetHeaderHash; + + type LaneId = TestLaneIdType; } #[derive(Clone, Debug)] @@ -957,7 +962,7 @@ pub(crate) mod tests { }; let _ = run( Params { - lane: LaneId::new(1, 2), + lane: TestLaneIdType::try_new(1, 2).unwrap(), source_tick: Duration::from_millis(100), target_tick: Duration::from_millis(100), reconnect_delay: Duration::from_millis(0), @@ -1278,7 +1283,31 @@ pub(crate) mod tests { #[test] fn metrics_prefix_is_valid() { assert!(MessageLaneLoopMetrics::new(Some(&metrics_prefix::( - &LaneId::new(1, 2) + &HashedLaneId::try_new(1, 2).unwrap() + ))) + .is_ok()); + + // with LegacyLaneId + #[derive(Clone)] + pub struct LegacyTestMessageLane; + impl MessageLane for LegacyTestMessageLane { + const SOURCE_NAME: &'static str = "LegacyTestSource"; + const TARGET_NAME: &'static str = "LegacyTestTarget"; + + type MessagesProof = TestMessagesProof; + type MessagesReceivingProof = TestMessagesReceivingProof; + + type SourceChainBalance = TestSourceChainBalance; + type SourceHeaderNumber = TestSourceHeaderNumber; + type SourceHeaderHash = TestSourceHeaderHash; + + type TargetHeaderNumber = TestTargetHeaderNumber; + type TargetHeaderHash = TestTargetHeaderHash; + + type LaneId = LegacyLaneId; + } + assert!(MessageLaneLoopMetrics::new(Some(&metrics_prefix::( + &LegacyLaneId([0, 0, 0, 1]) ))) .is_ok()); } diff --git a/bridges/relays/messages/src/metrics.rs b/bridges/relays/messages/src/metrics.rs index 69d80d178de..2ca10e56d74 100644 --- a/bridges/relays/messages/src/metrics.rs +++ b/bridges/relays/messages/src/metrics.rs @@ -21,7 +21,7 @@ use crate::{ message_lane_loop::{SourceClientState, TargetClientState}, }; -use bp_messages::MessageNonce; +use bp_messages::{HashedLaneId, LegacyLaneId, MessageNonce}; use finality_relay::SyncLoopMetrics; use relay_utils::metrics::{ metric_name, register, GaugeVec, Metric, Opts, PrometheusError, Registry, U64, @@ -146,3 +146,32 @@ impl Metric for MessageLaneLoopMetrics { Ok(()) } } + +/// Provides a label for metrics. +pub trait Labeled { + /// Returns a label. + fn label(&self) -> String; +} + +/// `Labeled` implementation for `LegacyLaneId`. +impl Labeled for LegacyLaneId { + fn label(&self) -> String { + hex::encode(self.0) + } +} + +/// `Labeled` implementation for `HashedLaneId`. +impl Labeled for HashedLaneId { + fn label(&self) -> String { + format!("{:?}", self.inner()) + } +} + +#[test] +fn lane_to_label_works() { + assert_eq!( + "0x0101010101010101010101010101010101010101010101010101010101010101", + HashedLaneId::from_inner(sp_core::H256::from([1u8; 32])).label(), + ); + assert_eq!("00000001", LegacyLaneId([0, 0, 0, 1]).label()); +} diff --git a/bridges/testing/README.md b/bridges/testing/README.md index 158dfd73b1a..89a07c421e3 100644 --- a/bridges/testing/README.md +++ b/bridges/testing/README.md @@ -22,7 +22,7 @@ Prerequisites for running the tests locally: - copy the `substrate-relay` binary, built in the previous step, to `~/local_bridge_testing/bin/substrate-relay`; After that, any test can be run using the `run-test.sh` command. -Example: `./run-new-test.sh 0001-asset-transfer` +Example: `./run-test.sh 0001-asset-transfer` Hopefully, it'll show the "All tests have completed successfully" message in the end. Otherwise, it'll print paths to zombienet diff --git a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 54633449134..e7848fe7163 100755 --- a/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -53,66 +53,66 @@ ASSET_HUB_ROCOCO_SOVEREIGN_ACCOUNT_AT_BRIDGE_HUB_ROCOCO="5Eg2fntNprdN3FgH4sfEaaZ # Expected sovereign accounts for rewards on BridgeHubs. # # Generated by: -# #[test] -# fn generate_sovereign_accounts_for_rewards() { -# use bp_messages::LaneId; -# use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; -# use sp_core::crypto::Ss58Codec; +##[test] +#fn generate_sovereign_accounts_for_rewards() { +# use bp_messages::LegacyLaneId; +# use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; +# use sp_core::crypto::Ss58Codec; # -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhwd", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhwd", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhwd", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); # -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhro", -# RewardsAccountOwner::ThisChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# // SS58=42 -# println!( -# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"", -# frame_support::sp_runtime::AccountId32::new( -# PayRewardFromAccount::<[u8; 32], [u8; 32]>::rewards_account(RewardsAccountParams::new( -# LaneId([0, 0, 0, 2]), -# *b"bhro", -# RewardsAccountOwner::BridgedChain -# )) -# ) -# .to_ss58check_with_version(42_u16.into()) -# ); -# } -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5BhiSGP5hbdsoVGtzi2sQVgpDNToTxLYeQvKoMPEm" -ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5BhiSGP5hbdt5EJSapXYbxEv678jyWHEUskCXcjqo" -ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5BhiSGP5h9Rg8sgUJqoLym3iEaWUiboT8S9AT5xFh" -ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi" +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::ThisChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +# // SS58=42 +# println!( +# "ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain=\"{}\"", +# frame_support::sp_runtime::AccountId32::new( +# PayRewardFromAccount::<[u8; 32], [u8; 32], LegacyLaneId>::rewards_account(RewardsAccountParams::new( +# LegacyLaneId([0, 0, 0, 2]), +# *b"bhro", +# RewardsAccountOwner::BridgedChain +# )) +# ) +# .to_ss58check_with_version(42_u16.into()) +# ); +#} +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_ThisChain="5EHnXaT5GApse1euZWj9hycMbgjKBCNQL9WEwScL8QDx6mhK" +ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain="5EHnXaT5Tnt4A8aiP9CsuAFRhKPjKZJXRrj4a3mtihFvKpTi" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXaT5GApry9tS6yd1FVusPq8o8bQJGCKyvXTFCoEKk5Z9" +ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5Tnt3VGpEvc6jSgYwVToDGxLRMuYoZ8coo6GHyWbR" LANE_ID="00000002" XCM_VERSION=3 diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index f3c0799ad0f..266d743ca0c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -16,6 +16,12 @@ workspace = true sp-core = { workspace = true } frame-support = { workspace = true } +# Polkadot Dependencies +xcm = { workspace = true } + +# Bridge dependencies +bp-messages = { workspace = true } + # Cumulus parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index 3786d529ea6..b9c0c01101c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -21,6 +21,7 @@ use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, }; use parachains_common::Balance; +use xcm::latest::prelude::*; pub const ASSETHUB_PARA_ID: u32 = 1000; pub const PARA_ID: u32 = 1013; @@ -66,6 +67,17 @@ pub fn genesis() -> Storage { owner: Some(get_account_id_from_seed::(accounts::BOB)), ..Default::default() }, + xcm_over_bridge_hub_westend: bridge_hub_rococo_runtime::XcmOverBridgeHubWestendConfig { + opened_bridges: vec![ + // open AHR -> AHW bridge + ( + Location::new(1, [Parachain(1000)]), + Junctions::from([Westend.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + ), + ], + ..Default::default() + }, ethereum_system: bridge_hub_rococo_runtime::EthereumSystemConfig { para_id: PARA_ID.into(), asset_hub_para_id: ASSETHUB_PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index ebcec9641e7..88d7348f50f 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -16,6 +16,12 @@ workspace = true sp-core = { workspace = true } frame-support = { workspace = true } +# Polkadot Dependencies +xcm = { workspace = true } + +# Bridge dependencies +bp-messages = { workspace = true } + # Cumulus parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index f38f385db65..3ffe3d86b2a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -21,6 +21,7 @@ use emulated_integration_tests_common::{ accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, }; use parachains_common::Balance; +use xcm::latest::prelude::*; pub const PARA_ID: u32 = 1002; pub const ASSETHUB_PARA_ID: u32 = 1000; @@ -66,6 +67,17 @@ pub fn genesis() -> Storage { owner: Some(get_account_id_from_seed::(accounts::BOB)), ..Default::default() }, + xcm_over_bridge_hub_rococo: bridge_hub_westend_runtime::XcmOverBridgeHubRococoConfig { + opened_bridges: vec![ + // open AHW -> AHR bridge + ( + Location::new(1, [Parachain(1000)]), + Junctions::from([Rococo.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + ), + ], + ..Default::default() + }, ethereum_system: bridge_hub_westend_runtime::EthereumSystemConfig { para_id: PARA_ID.into(), asset_hub_para_id: ASSETHUB_PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index 559a16379bb..c0d42cf2758 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -61,10 +61,10 @@ pub use xcm_emulator::{ // Bridges use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - LaneId, MessageKey, OutboundLaneData, + MessageKey, OutboundLaneData, }; pub use bp_xcm_bridge_hub::XcmBridgeHubCall; -use pallet_bridge_messages::{Config as BridgeMessagesConfig, OutboundLanes, Pallet}; +use pallet_bridge_messages::{Config as BridgeMessagesConfig, LaneIdOf, OutboundLanes, Pallet}; pub use pallet_bridge_messages::{ Instance1 as BridgeMessagesInstance1, Instance2 as BridgeMessagesInstance2, Instance3 as BridgeMessagesInstance3, @@ -75,14 +75,14 @@ pub struct BridgeHubMessageHandler { _marker: std::marker::PhantomData<(S, SI, T, TI)>, } -struct LaneIdWrapper(LaneId); -impl From for BridgeLaneId { - fn from(lane_id: LaneIdWrapper) -> BridgeLaneId { +struct LaneIdWrapper(LaneId); +impl From> for BridgeLaneId { + fn from(lane_id: LaneIdWrapper) -> BridgeLaneId { lane_id.0.encode() } } -impl From for LaneIdWrapper { - fn from(id: BridgeLaneId) -> LaneIdWrapper { +impl From for LaneIdWrapper { + fn from(id: BridgeLaneId) -> LaneIdWrapper { LaneIdWrapper(LaneId::decode(&mut &id[..]).expect("decodable")) } } @@ -154,7 +154,7 @@ where } fn notify_source_message_delivery(lane_id: BridgeLaneId) { - let lane_id = LaneIdWrapper::from(lane_id).0; + let lane_id: LaneIdOf = LaneIdWrapper::from(lane_id).0; let data = OutboundLanes::::get(lane_id).unwrap(); let new_data = OutboundLaneData { oldest_unpruned_nonce: data.oldest_unpruned_nonce + 1, 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 b540f55642a..a989881fef0 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 @@ -231,17 +231,6 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmOverBridgeHubWestend( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); // open AHW -> AHR BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); @@ -255,15 +244,4 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::XcmOverBridgeHubRococo( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); } 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 699641d3328..f037a05a827 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 @@ -246,17 +246,6 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmOverBridgeHubWestend( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); // open AHW -> AHR BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); @@ -270,15 +259,4 @@ pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { )), )), ); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::XcmOverBridgeHubRococo( - pallet_xcm_bridge_hub::Event::BridgeOpened { .. } - ) => {}, - ] - ); - }); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 779cc537ee9..5dca45d326b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -64,11 +64,37 @@ impl pallet_bridge_parachains::Config for Runtim } /// Allows collect and claim rewards for relayers -impl pallet_bridge_relayers::Config for Runtime { +pub type RelayersForLegacyLaneIdsMessagesInstance = (); +impl pallet_bridge_relayers::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Reward = Balance; - type PaymentProcedure = - bp_relayers::PayRewardFromAccount, AccountId>; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; + type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< + AccountId, + BlockNumber, + Balances, + RelayerStakeReserveId, + RequiredStakeForStakeAndSlash, + RelayerStakeLease, + >; + type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = bp_messages::LegacyLaneId; +} + +/// Allows collect and claim rewards for relayers +pub type RelayersForPermissionlessLanesInstance = pallet_bridge_relayers::Instance2; +impl pallet_bridge_relayers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reward = Balance; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< AccountId, BlockNumber, @@ -78,6 +104,7 @@ impl pallet_bridge_relayers::Config for Runtime { RelayerStakeLease, >; type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = bp_messages::HashedLaneId; } /// Add GRANDPA bridge pallet to track Rococo Bulletin chain. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 00d902486c8..c971fa59c68 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -20,13 +20,14 @@ //! are reusing Polkadot Bulletin chain primitives everywhere here. use crate::{ - weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, - BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverRococoBulletin, XcmRouter, + bridge_common_config::RelayersForPermissionlessLanesInstance, weights, + xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoBulletinGrandpa, + BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, + XcmOverRococoBulletin, XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, HashedLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; @@ -34,11 +35,11 @@ use frame_support::{ parameter_types, traits::{Equals, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; use pallet_xcm_bridge_hub::XcmAsPlainPayload; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::rococo::currency::UNITS as ROC; @@ -78,11 +79,11 @@ parameter_types! { } /// Proof of messages, coming from Rococo Bulletin chain. -pub type FromRococoBulletinMessagesProof = - FromBridgedChainMessagesProof; +pub type FromRococoBulletinMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Rococo Bulletin messages. -pub type ToRococoBulletinMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToRococoBulletinMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge. type FromRococoBulletinMessageBlobDispatcher = BridgeBlobDispatcher< @@ -99,8 +100,10 @@ pub type OnBridgeHubRococoRefundRococoBulletinMessages = BridgeRelayersSignedExt StrOnBridgeHubRococoRefundRococoBulletinMessages, Runtime, WithRococoBulletinMessagesInstance, + RelayersForPermissionlessLanesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundRococoBulletinMessages); @@ -116,10 +119,10 @@ impl pallet_bridge_messages::Config for Runt type BridgedHeaderChain = BridgeRococoBulletinGrandpa; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = HashedLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = (); type MessageDispatch = XcmOverRococoBulletin; @@ -139,9 +142,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from sibling People parachain to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm>; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -230,14 +233,20 @@ mod tests { } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -248,7 +257,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -261,17 +270,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -279,13 +283,16 @@ pub(crate) fn open_bridge_for_benchmarks( /// Contains the migration for the PeopleRococo<>RococoBulletin bridge. pub mod migration { use super::*; - use bp_messages::LaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub RococoPeopleToRococoBulletinMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 0])); pub BulletinRococoLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); + pub RococoPeopleToRococoBulletinMessagesLane: HashedLaneId = pallet_xcm_bridge_hub::Pallet::< Runtime, XcmOverPolkadotBulletinInstance >::bridge_locations( + PeopleRococoLocation::get(), + BulletinRococoLocation::get() + ) + .unwrap() + .calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); } /// Ensure that the existing lanes for the People<>Bulletin bridge are correctly configured. 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 fc52413a909..8fe04572310 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 @@ -17,7 +17,10 @@ //! Bridge definitions used on BridgeHubRococo for bridging to BridgeHubWestend. use crate::{ - bridge_common_config::{BridgeParachainWestendInstance, DeliveryRewardInBalance}, + bridge_common_config::{ + BridgeParachainWestendInstance, DeliveryRewardInBalance, + RelayersForLegacyLaneIdsMessagesInstance, + }, weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, @@ -25,20 +28,18 @@ use crate::{ }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; use pallet_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::{parameter_types, traits::PalletInfoAccess}; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; -use parachains_common::xcm_config::{ - AllSiblingSystemParachains, ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, -}; +use parachains_common::xcm_config::{AllSiblingSystemParachains, RelayOrOtherSystemParachains}; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::rococo::currency::UNITS as ROC; use xcm::{ @@ -73,11 +74,11 @@ parameter_types! { } /// Proof of messages, coming from Westend. -pub type FromWestendBridgeHubMessagesProof = - FromBridgedChainMessagesProof; +pub type FromWestendBridgeHubMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. -pub type ToWestendBridgeHubMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToWestendBridgeHubMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge type FromWestendMessageBlobDispatcher = @@ -90,8 +91,10 @@ pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = BridgeRelayersSignedE StrOnBridgeHubRococoRefundBridgeHubWestendMessages, Runtime, WithBridgeHubWestendMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundBridgeHubWestendMessages); @@ -110,10 +113,10 @@ impl pallet_bridge_messages::Config for Ru >; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = LegacyLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubWestendMessagesInstance, @@ -124,7 +127,8 @@ impl pallet_bridge_messages::Config for Ru type OnMessagesDelivered = XcmOverBridgeHubWestend; } -/// Add support for the export and dispatch of XCM programs. +/// Add support for the export and dispatch of XCM programs withing +/// `WithBridgeHubWestendMessagesInstance`. pub type XcmOverBridgeHubWestendInstance = pallet_xcm_bridge_hub::Instance1; impl pallet_xcm_bridge_hub::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -137,9 +141,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from relay chains and sibling parachains to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance with `LegacyLaneId`. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -157,14 +161,20 @@ impl pallet_xcm_bridge_hub::Config for Runtime } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -174,7 +184,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -187,17 +197,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -297,12 +302,10 @@ mod tests { /// Contains the migration for the AssetHubRococo<>AssetHubWestend bridge. pub mod migration { use super::*; - use bp_messages::LaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub AssetHubRococoToAssetHubWestendMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 2])); + pub AssetHubRococoToAssetHubWestendMessagesLane: LegacyLaneId = LegacyLaneId([0, 0, 0, 2]); pub AssetHubRococoLocation: Location = Location::new(1, [Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)]); pub AssetHubWestendUniversalLocation: InteriorLocation = [GlobalConsensus(WestendGlobalConsensusNetwork::get()), Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)].into(); } @@ -318,4 +321,75 @@ pub mod migration { AssetHubRococoLocation, AssetHubWestendUniversalLocation, >; + + mod v1_wrong { + use bp_messages::{LaneState, MessageNonce, UnrewardedRelayer}; + use bp_runtime::AccountIdOf; + use codec::{Decode, Encode}; + use pallet_bridge_messages::BridgedChainOf; + use sp_std::collections::vec_deque::VecDeque; + + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct StoredInboundLaneData, I: 'static>( + pub(crate) InboundLaneData>>, + ); + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct InboundLaneData { + pub state: LaneState, + pub(crate) relayers: VecDeque>, + pub(crate) last_confirmed_nonce: MessageNonce, + } + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct OutboundLaneData { + pub state: LaneState, + pub(crate) oldest_unpruned_nonce: MessageNonce, + pub(crate) latest_received_nonce: MessageNonce, + pub(crate) latest_generated_nonce: MessageNonce, + } + } + + mod v1 { + pub use bp_messages::{InboundLaneData, LaneState, OutboundLaneData}; + pub use pallet_bridge_messages::{InboundLanes, OutboundLanes, StoredInboundLaneData}; + } + + /// Fix for v1 migration - corrects data for OutboundLaneData/InboundLaneData (it is needed only + /// for Rococo/Westend). + pub struct FixMessagesV1Migration(sp_std::marker::PhantomData<(T, I)>); + + impl, I: 'static> frame_support::traits::OnRuntimeUpgrade + for FixMessagesV1Migration + { + fn on_runtime_upgrade() -> Weight { + use sp_core::Get; + let mut weight = T::DbWeight::get().reads(1); + + // `InboundLanes` - add state to the old structs + let translate_inbound = + |pre: v1_wrong::StoredInboundLaneData| -> Option> { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::StoredInboundLaneData(v1::InboundLaneData { + state: v1::LaneState::Opened, + relayers: pre.0.relayers, + last_confirmed_nonce: pre.0.last_confirmed_nonce, + })) + }; + v1::InboundLanes::::translate_values(translate_inbound); + + // `OutboundLanes` - add state to the old structs + let translate_outbound = + |pre: v1_wrong::OutboundLaneData| -> Option { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::OutboundLaneData { + state: v1::LaneState::Opened, + oldest_unpruned_nonce: pre.oldest_unpruned_nonce, + latest_received_nonce: pre.latest_received_nonce, + latest_generated_nonce: pre.latest_generated_nonce, + }) + }; + v1::OutboundLanes::::translate_values(translate_outbound); + + weight + } + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index e5d61598564..07048d54ab1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -31,6 +31,7 @@ fn bridge_hub_rococo_genesis( id: ParaId, bridges_pallet_owner: Option, asset_hub_para_id: ParaId, + opened_bridges: Vec<(Location, InteriorLocation, Option)>, ) -> serde_json::Value { let config = RuntimeGenesisConfig { balances: BalancesConfig { @@ -71,6 +72,10 @@ fn bridge_hub_rococo_genesis( owner: bridges_pallet_owner.clone(), ..Default::default() }, + xcm_over_bridge_hub_westend: XcmOverBridgeHubWestendConfig { + opened_bridges, + ..Default::default() + }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id, @@ -114,6 +119,11 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![( + Location::new(1, [Parachain(1000)]), + Junctions::from([Westend.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + )], ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_rococo_genesis( // initial collators. @@ -144,6 +154,7 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![], ), _ => return None, }; 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 177978cee10..bf9bd58669b 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 @@ -43,6 +43,7 @@ use bridge_runtime_common::extensions::{ CheckAndBoostBridgeGrandpaTransactions, CheckAndBoostBridgeParachainsTransactions, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use pallet_bridge_messages::LaneIdOf; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -130,10 +131,7 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, - ), + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, frame_metadata_hash_extension::CheckMetadataHash, ); @@ -163,6 +161,10 @@ pub type Migrations = ( Runtime, bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, >, + bridge_to_westend_config::migration::FixMessagesV1Migration< + Runtime, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, + >, bridge_to_westend_config::migration::StaticToDynamicLanes, bridge_to_bulletin_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< @@ -175,6 +177,7 @@ pub type Migrations = ( OutboundLanesCongestedSignalsKey, RocksDbWeight, >, + pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -593,6 +596,9 @@ construct_runtime!( // With-Rococo Bulletin bridge hub pallet. XcmOverPolkadotBulletin: pallet_xcm_bridge_hub:: = 62, + // Bridge relayers pallet, used by several bridges here (another instance). + BridgeRelayersForPermissionlessLanes: pallet_bridge_relayers:: = 63, + EthereumInboundQueue: snowbridge_pallet_inbound_queue = 80, EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, @@ -662,7 +668,8 @@ mod benches { [pallet_bridge_parachains, WithinWestend] [pallet_bridge_messages, RococoToWestend] [pallet_bridge_messages, RococoToRococoBulletin] - [pallet_bridge_relayers, BridgeRelayersBench::] + [pallet_bridge_relayers, Legacy] + [pallet_bridge_relayers, PermissionlessLanes] // Ethereum Bridge [snowbridge_pallet_inbound_queue, EthereumInboundQueue] [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] @@ -671,6 +678,11 @@ mod benches { ); } +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} + impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { @@ -914,7 +926,7 @@ impl_runtime_apis! { // This is exposed by BridgeHubRococo impl bp_bridge_hub_westend::FromBridgeHubWestendInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -927,7 +939,7 @@ impl_runtime_apis! { // This is exposed by BridgeHubRococo impl bp_bridge_hub_westend::ToBridgeHubWestendOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -957,7 +969,7 @@ impl_runtime_apis! { impl bp_polkadot_bulletin::FromPolkadotBulletinInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -969,7 +981,7 @@ impl_runtime_apis! { impl bp_polkadot_bulletin::ToPolkadotBulletinOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -1039,6 +1051,8 @@ impl_runtime_apis! { type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; + type Legacy = BridgeRelayersBench::; + type PermissionlessLanes = BridgeRelayersBench::; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1248,15 +1262,20 @@ impl_runtime_apis! { ); // open bridge - let origin = RuntimeOrigin::from(pallet_xcm::Origin::Xcm(sibling_parachain_location.clone())); - XcmOverBridgeHubWestend::open_bridge( - origin.clone(), - Box::new(VersionedInteriorLocation::from([GlobalConsensus(NetworkId::Westend), Parachain(8765)])), + let bridge_destination_universal_location: InteriorLocation = [GlobalConsensus(NetworkId::Westend), Parachain(8765)].into(); + let locations = XcmOverBridgeHubWestend::bridge_locations( + sibling_parachain_location.clone(), + bridge_destination_universal_location.clone(), + )?; + XcmOverBridgeHubWestend::do_open_bridge( + locations, + bp_messages::LegacyLaneId([1, 2, 3, 4]), + true, ).map_err(|e| { log::error!( "Failed to `XcmOverBridgeHubWestend::open_bridge`({:?}, {:?})`, error: {:?}", - origin, - [GlobalConsensus(NetworkId::Westend), Parachain(8765)], + sibling_parachain_location, + bridge_destination_universal_location, e ); BenchmarkError::Stop("Bridge was not opened!") @@ -1283,6 +1302,8 @@ impl_runtime_apis! { type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; + type Legacy = BridgeRelayersBench::; + type PermissionlessLanes = BridgeRelayersBench::; use bridge_runtime_common::messages_benchmarking::{ prepare_message_delivery_proof_from_grandpa_chain, @@ -1313,12 +1334,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_westend_config::FromWestendBridgeHubMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_westend_config::FromWestendBridgeHubMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_westend_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_westend_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_westend_config::XcmOverBridgeHubWestendInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_parachain::< Runtime, bridge_common_config::BridgeGrandpaWestendInstance, @@ -1327,9 +1352,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_westend_config::ToWestendBridgeHubMessagesDeliveryProof { - let _ = bridge_to_westend_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_westend_config::ToWestendBridgeHubMessagesDeliveryProof { + let _ = bridge_to_westend_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_westend_config::XcmOverBridgeHubWestendInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_parachain::< Runtime, bridge_common_config::BridgeGrandpaWestendInstance, @@ -1350,12 +1379,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_bulletin_config::FromRococoBulletinMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_bulletin_config::FromRococoBulletinMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_bulletin_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_bulletin_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_bulletin_config::XcmOverPolkadotBulletinInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_grandpa_chain::< Runtime, bridge_common_config::BridgeGrandpaRococoBulletinInstance, @@ -1364,9 +1397,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { - let _ = bridge_to_bulletin_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { + let _ = bridge_to_bulletin_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_bulletin_config::XcmOverPolkadotBulletinInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_grandpa_chain::< Runtime, bridge_common_config::BridgeGrandpaRococoBulletinInstance, @@ -1411,16 +1448,36 @@ impl_runtime_apis! { } } - impl BridgeRelayersConfig for Runtime { + impl BridgeRelayersConfig for Runtime { fn prepare_rewards_account( - account_params: bp_relayers::RewardsAccountParams, + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, reward: Balance, ) { let rewards_account = bp_relayers::PayRewardFromAccount::< Balances, - AccountId + AccountId, + >::LaneId, >::rewards_account(account_params); - Self::deposit_account(rewards_account, reward); + >::deposit_account(rewards_account, reward); + } + + fn deposit_account(account: AccountId, balance: Balance) { + use frame_support::traits::fungible::Mutate; + Balances::mint_into(&account, balance.saturating_add(ExistentialDeposit::get())).unwrap(); + } + } + + impl BridgeRelayersConfig for Runtime { + fn prepare_rewards_account( + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, + reward: Balance, + ) { + let rewards_account = bp_relayers::PayRewardFromAccount::< + Balances, + AccountId, + >::LaneId, + >::rewards_account(account_params); + >::deposit_account(rewards_account, reward); } fn deposit_account(account: AccountId, balance: Balance) { @@ -1465,11 +1522,6 @@ impl_runtime_apis! { } } -cumulus_pallet_parachain_system::register_validate_block! { - Runtime = Runtime, - BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, -} - #[cfg(test)] mod tests { use super::*; @@ -1497,7 +1549,6 @@ mod tests { BridgeRejectObsoleteHeadersAndMessages, ( bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), ), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::new(false), 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 c7b5850f9ff..7a0f1462e7a 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 @@ -18,7 +18,6 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, @@ -183,10 +182,7 @@ fn construct_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), - ( - OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - OnBridgeHubRococoRefundRococoBulletinMessages::default(), - ), + (OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::::new(false), ); 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 982c9fec663..002e31174cb 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 @@ -62,10 +62,7 @@ fn construct_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), - ), + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), frame_metadata_hash_extension::CheckMetadataHash::new(false), ); @@ -145,8 +142,10 @@ fn change_required_stake_by_governance_works() { mod bridge_hub_westend_tests { use super::*; + use bp_messages::LegacyLaneId; use bridge_common_config::{ BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, DeliveryRewardInBalance, + RelayersForLegacyLaneIdsMessagesInstance, }; use bridge_hub_test_utils::test_cases::from_parachain; use bridge_to_westend_config::{ @@ -174,6 +173,7 @@ mod bridge_hub_westend_tests { BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, WithBridgeHubWestendMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; #[test] @@ -338,7 +338,16 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()).1 + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + } + ).1 }, ) } @@ -393,10 +402,20 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -417,10 +436,20 @@ mod bridge_hub_westend_tests { XcmOverBridgeHubWestendInstance, LocationToAccountId, TokenLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubWestendInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } @@ -482,30 +511,13 @@ mod bridge_hub_westend_tests { ), ) } - - #[test] - fn open_and_close_bridge_works() { - let origins = [SiblingParachainLocation::get(), SiblingSystemParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverBridgeHubWestendInstance, - LocationToAccountId, - TokenLocation, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - origin, - BridgedUniversalLocation::get(), - ) - } - } } mod bridge_hub_bulletin_tests { use super::*; + use bp_messages::{HashedLaneId, LaneIdType}; use bridge_common_config::BridgeGrandpaRococoBulletinInstance; + use bridge_hub_rococo_runtime::bridge_common_config::RelayersForPermissionlessLanesInstance; use bridge_hub_test_utils::test_cases::from_grandpa_chain; use bridge_to_bulletin_config::{ RococoBulletinGlobalConsensusNetwork, RococoBulletinGlobalConsensusNetworkLocation, @@ -527,6 +539,7 @@ mod bridge_hub_bulletin_tests { AllPalletsWithoutSystem, BridgeGrandpaRococoBulletinInstance, WithRococoBulletinMessagesInstance, + RelayersForPermissionlessLanesInstance, >; #[test] @@ -589,7 +602,16 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()).1 + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + } + ).1 }, ) } @@ -643,10 +665,20 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()) + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance, + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } @@ -666,29 +698,20 @@ mod bridge_hub_bulletin_tests { XcmOverPolkadotBulletinInstance, LocationToAccountId, TokenLocation, - >(SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get()) + >( + SiblingPeopleParachainLocation::get(), + BridgedBulletinLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverPolkadotBulletinInstance, + >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + }, + ) .1 }, construct_and_apply_extrinsic, + false, ) } - - #[test] - fn open_and_close_bridge_works() { - let origins = [SiblingPeopleParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverPolkadotBulletinInstance, - LocationToAccountId, - TokenLocation, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - origin, - BridgedBulletinLocation::get(), - ) - } - } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs index 9bae106395a..0872d0498f8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_common_config.rs @@ -22,6 +22,7 @@ //! GRANDPA tracking pallet only needs to be aware of one chain. use super::{weights, AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent}; +use bp_messages::LegacyLaneId; use frame_support::parameter_types; parameter_types! { @@ -33,11 +34,15 @@ parameter_types! { } /// Allows collect and claim rewards for relayers -impl pallet_bridge_relayers::Config for Runtime { +pub type RelayersForLegacyLaneIdsMessagesInstance = (); +impl pallet_bridge_relayers::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Reward = Balance; - type PaymentProcedure = - bp_relayers::PayRewardFromAccount, AccountId>; + type PaymentProcedure = bp_relayers::PayRewardFromAccount< + pallet_balances::Pallet, + AccountId, + Self::LaneId, + >; type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< AccountId, BlockNumber, @@ -47,4 +52,5 @@ impl pallet_bridge_relayers::Config for Runtime { RelayerStakeLease, >; type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; + type LaneId = LegacyLaneId; } 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 2d9e8f66427..e45654bc62b 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 @@ -17,13 +17,15 @@ //! Bridge definitions used on BridgeHub with the Westend flavor. use crate::{ - bridge_common_config::DeliveryRewardInBalance, weights, xcm_config::UniversalLocation, + bridge_common_config::{DeliveryRewardInBalance, RelayersForLegacyLaneIdsMessagesInstance}, + weights, + xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; @@ -33,14 +35,12 @@ use frame_support::{ parameter_types, traits::{ConstU32, PalletInfoAccess}, }; -use frame_system::EnsureRoot; +use frame_system::{EnsureNever, EnsureRoot}; +use pallet_bridge_messages::LaneIdOf; use pallet_bridge_relayers::extension::{ BridgeRelayersSignedExtension, WithMessagesExtensionConfig, }; -use pallet_xcm::EnsureXcm; -use parachains_common::xcm_config::{ - AllSiblingSystemParachains, ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, -}; +use parachains_common::xcm_config::{AllSiblingSystemParachains, RelayOrOtherSystemParachains}; use polkadot_parachain_primitives::primitives::Sibling; use testnet_parachains_constants::westend::currency::UNITS as WND; use xcm::{ @@ -81,11 +81,11 @@ parameter_types! { } /// Proof of messages, coming from Rococo. -pub type FromRococoBridgeHubMessagesProof = - FromBridgedChainMessagesProof; +pub type FromRococoBridgeHubMessagesProof = + FromBridgedChainMessagesProof>; /// Messages delivery proof for Rococo Bridge Hub -> Westend Bridge Hub messages. -pub type ToRococoBridgeHubMessagesDeliveryProof = - FromBridgedChainMessagesDeliveryProof; +pub type ToRococoBridgeHubMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof>; /// Dispatches received XCM messages from other bridge type FromRococoMessageBlobDispatcher = @@ -98,8 +98,10 @@ pub type OnBridgeHubWestendRefundBridgeHubRococoMessages = BridgeRelayersSignedE StrOnBridgeHubWestendRefundBridgeHubRococoMessages, Runtime, WithBridgeHubRococoMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, PriorityBoostPerMessage, >, + LaneIdOf, >; bp_runtime::generate_static_str_provider!(OnBridgeHubWestendRefundBridgeHubRococoMessages); @@ -142,10 +144,10 @@ impl pallet_bridge_messages::Config for Run >; type OutboundPayload = XcmAsPlainPayload; - type InboundPayload = XcmAsPlainPayload; - type DeliveryPayments = (); + type LaneId = LegacyLaneId; + type DeliveryPayments = (); type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubRococoMessagesInstance, @@ -168,9 +170,9 @@ impl pallet_xcm_bridge_hub::Config for Runtime { type MessageExportPrice = (); type DestinationVersion = XcmVersionOfDestAndRemoteBridge; - type AdminOrigin = EnsureRoot; - // Only allow calls from relay chains and sibling parachains to directly open the bridge. - type OpenBridgeOrigin = EnsureXcm; + type ForceOrigin = EnsureRoot; + // We don't want to allow creating bridges for this instance with `LegacyLaneId`. + type OpenBridgeOrigin = EnsureNever; // Converter aligned with `OpenBridgeOrigin`. type BridgeOriginAccountIdConverter = (ParentIsPreset, SiblingParachainConvertsVia); @@ -188,14 +190,20 @@ impl pallet_xcm_bridge_hub::Config for Runtime { } #[cfg(feature = "runtime-benchmarks")] -pub(crate) fn open_bridge_for_benchmarks( - with: bp_messages::LaneId, +pub(crate) fn open_bridge_for_benchmarks( + with: pallet_xcm_bridge_hub::LaneIdOf, sibling_para_id: u32, -) -> InteriorLocation { +) -> InteriorLocation +where + R: pallet_xcm_bridge_hub::Config, + XBHI: 'static, + C: xcm_executor::traits::ConvertLocation< + bp_runtime::AccountIdOf>, + >, +{ use pallet_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; use sp_runtime::traits::Zero; use xcm::VersionedInteriorLocation; - use xcm_executor::traits::ConvertLocation; // insert bridge metadata let lane_id = with; @@ -205,7 +213,7 @@ pub(crate) fn open_bridge_for_benchmarks( let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes - pallet_xcm_bridge_hub::Bridges::::insert( + pallet_xcm_bridge_hub::Bridges::::insert( bridge_id, Bridge { bridge_origin_relative_location: alloc::boxed::Box::new( @@ -218,17 +226,12 @@ pub(crate) fn open_bridge_for_benchmarks( VersionedInteriorLocation::from(universal_destination), ), state: BridgeState::Opened, - bridge_owner_account: crate::xcm_config::LocationToAccountId::convert_location( - &sibling_parachain, - ) - .expect("valid AccountId"), - deposit: Balance::zero(), + bridge_owner_account: C::convert_location(&sibling_parachain).expect("valid AccountId"), + deposit: Zero::zero(), lane_id, }, ); - pallet_xcm_bridge_hub::LaneToBridge::::insert( - lane_id, bridge_id, - ); + pallet_xcm_bridge_hub::LaneToBridge::::insert(lane_id, bridge_id); universal_source } @@ -327,12 +330,11 @@ mod tests { /// Contains the migration for the AssetHubWestend<>AssetHubRococo bridge. pub mod migration { use super::*; - use bp_messages::LaneId; + use bp_messages::LegacyLaneId; use frame_support::traits::ConstBool; - use sp_runtime::Either; parameter_types! { - pub AssetHubWestendToAssetHubRococoMessagesLane: LaneId = LaneId::from_inner(Either::Right([0, 0, 0, 2])); + pub AssetHubWestendToAssetHubRococoMessagesLane: LegacyLaneId = LegacyLaneId([0, 0, 0, 2]); pub AssetHubWestendLocation: Location = Location::new(1, [Parachain(bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID)]); pub AssetHubRococoUniversalLocation: InteriorLocation = [GlobalConsensus(RococoGlobalConsensusNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID)].into(); } @@ -348,4 +350,75 @@ pub mod migration { AssetHubWestendLocation, AssetHubRococoUniversalLocation, >; + + mod v1_wrong { + use bp_messages::{LaneState, MessageNonce, UnrewardedRelayer}; + use bp_runtime::AccountIdOf; + use codec::{Decode, Encode}; + use pallet_bridge_messages::BridgedChainOf; + use sp_std::collections::vec_deque::VecDeque; + + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct StoredInboundLaneData, I: 'static>( + pub(crate) InboundLaneData>>, + ); + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct InboundLaneData { + pub state: LaneState, + pub(crate) relayers: VecDeque>, + pub(crate) last_confirmed_nonce: MessageNonce, + } + #[derive(Encode, Decode, Clone, PartialEq, Eq)] + pub(crate) struct OutboundLaneData { + pub state: LaneState, + pub(crate) oldest_unpruned_nonce: MessageNonce, + pub(crate) latest_received_nonce: MessageNonce, + pub(crate) latest_generated_nonce: MessageNonce, + } + } + + mod v1 { + pub use bp_messages::{InboundLaneData, LaneState, OutboundLaneData}; + pub use pallet_bridge_messages::{InboundLanes, OutboundLanes, StoredInboundLaneData}; + } + + /// Fix for v1 migration - corrects data for OutboundLaneData/InboundLaneData (it is needed only + /// for Rococo/Westend). + pub struct FixMessagesV1Migration(sp_std::marker::PhantomData<(T, I)>); + + impl, I: 'static> frame_support::traits::OnRuntimeUpgrade + for FixMessagesV1Migration + { + fn on_runtime_upgrade() -> Weight { + use sp_core::Get; + let mut weight = T::DbWeight::get().reads(1); + + // `InboundLanes` - add state to the old structs + let translate_inbound = + |pre: v1_wrong::StoredInboundLaneData| -> Option> { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::StoredInboundLaneData(v1::InboundLaneData { + state: v1::LaneState::Opened, + relayers: pre.0.relayers, + last_confirmed_nonce: pre.0.last_confirmed_nonce, + })) + }; + v1::InboundLanes::::translate_values(translate_inbound); + + // `OutboundLanes` - add state to the old structs + let translate_outbound = + |pre: v1_wrong::OutboundLaneData| -> Option { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + Some(v1::OutboundLaneData { + state: v1::LaneState::Opened, + oldest_unpruned_nonce: pre.oldest_unpruned_nonce, + latest_received_nonce: pre.latest_received_nonce, + latest_generated_nonce: pre.latest_generated_nonce, + }) + }; + v1::OutboundLanes::::translate_values(translate_outbound); + + weight + } + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs index 4c948656d9f..0b270e58433 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs @@ -31,6 +31,7 @@ fn bridge_hub_westend_genesis( id: ParaId, bridges_pallet_owner: Option, asset_hub_para_id: ParaId, + opened_bridges: Vec<(Location, InteriorLocation, Option)>, ) -> serde_json::Value { let config = RuntimeGenesisConfig { balances: BalancesConfig { @@ -71,6 +72,10 @@ fn bridge_hub_westend_genesis( owner: bridges_pallet_owner.clone(), ..Default::default() }, + xcm_over_bridge_hub_rococo: XcmOverBridgeHubRococoConfig { + opened_bridges, + ..Default::default() + }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id, @@ -114,6 +119,11 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![( + Location::new(1, [Parachain(1000)]), + Junctions::from([Rococo.into(), Parachain(1000)]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 2])), + )], ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => bridge_hub_westend_genesis( // initial collators. @@ -144,6 +154,7 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option("Bob")), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), + vec![], ), _ => return None, }; 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 5c40506442d..87152d30977 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 @@ -82,6 +82,7 @@ use xcm_runtime_apis::{ }; use bp_runtime::HeaderId; +use pallet_bridge_messages::LaneIdOf; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -147,12 +148,17 @@ pub type Migrations = ( Runtime, bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, >, + bridge_to_rococo_config::migration::FixMessagesV1Migration< + Runtime, + bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, + >, bridge_to_rococo_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< BridgeRococoMessagesPalletName, OutboundLanesCongestedSignalsKey, RocksDbWeight, >, + pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, snowbridge_pallet_system::migration::v0::InitializeOnUpgrade< @@ -851,7 +857,7 @@ impl_runtime_apis! { impl bp_bridge_hub_rococo::FromBridgeHubRococoInboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, ) -> Vec { bridge_runtime_common::messages_api::inbound_message_details::< @@ -863,7 +869,7 @@ impl_runtime_apis! { impl bp_bridge_hub_rococo::ToBridgeHubRococoOutboundLaneApi for Runtime { fn message_details( - lane: bp_messages::LaneId, + lane: LaneIdOf, begin: bp_messages::MessageNonce, end: bp_messages::MessageNonce, ) -> Vec { @@ -1141,15 +1147,20 @@ impl_runtime_apis! { ); // open bridge - let origin = RuntimeOrigin::from(pallet_xcm::Origin::Xcm(sibling_parachain_location.clone())); - XcmOverBridgeHubRococo::open_bridge( - origin.clone(), - alloc::boxed::Box::new(VersionedInteriorLocation::from([GlobalConsensus(NetworkId::Rococo), Parachain(8765)])), + let bridge_destination_universal_location: InteriorLocation = [GlobalConsensus(NetworkId::Rococo), Parachain(8765)].into(); + let locations = XcmOverBridgeHubRococo::bridge_locations( + sibling_parachain_location.clone(), + bridge_destination_universal_location.clone(), + )?; + XcmOverBridgeHubRococo::do_open_bridge( + locations, + bp_messages::LegacyLaneId([1, 2, 3, 4]), + true, ).map_err(|e| { log::error!( "Failed to `XcmOverBridgeHubRococo::open_bridge`({:?}, {:?})`, error: {:?}", - origin, - [GlobalConsensus(NetworkId::Rococo), Parachain(8765)], + sibling_parachain_location, + bridge_destination_universal_location, e ); BenchmarkError::Stop("Bridge was not opened!") @@ -1203,12 +1214,16 @@ impl_runtime_apis! { } fn prepare_message_proof( - params: MessageProofParams, - ) -> (bridge_to_rococo_config::FromRococoBridgeHubMessagesProof, Weight) { + params: MessageProofParams>, + ) -> (bridge_to_rococo_config::FromRococoBridgeHubMessagesProof, Weight) { use cumulus_primitives_core::XcmpMessageSource; assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); - let universal_source = bridge_to_rococo_config::open_bridge_for_benchmarks(params.lane, 42); + let universal_source = bridge_to_rococo_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_rococo_config::XcmOverBridgeHubRococoInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_proof_from_parachain::< Runtime, bridge_to_rococo_config::BridgeGrandpaRococoInstance, @@ -1217,9 +1232,13 @@ impl_runtime_apis! { } fn prepare_message_delivery_proof( - params: MessageDeliveryProofParams, - ) -> bridge_to_rococo_config::ToRococoBridgeHubMessagesDeliveryProof { - let _ = bridge_to_rococo_config::open_bridge_for_benchmarks(params.lane, 42); + params: MessageDeliveryProofParams>, + ) -> bridge_to_rococo_config::ToRococoBridgeHubMessagesDeliveryProof { + let _ = bridge_to_rococo_config::open_bridge_for_benchmarks::< + Runtime, + bridge_to_rococo_config::XcmOverBridgeHubRococoInstance, + xcm_config::LocationToAccountId, + >(params.lane, 42); prepare_message_delivery_proof_from_parachain::< Runtime, bridge_to_rococo_config::BridgeGrandpaRococoInstance, @@ -1264,14 +1283,15 @@ impl_runtime_apis! { } } - impl BridgeRelayersConfig for Runtime { + impl BridgeRelayersConfig for Runtime { fn prepare_rewards_account( - account_params: bp_relayers::RewardsAccountParams, + account_params: bp_relayers::RewardsAccountParams<>::LaneId>, reward: Balance, ) { let rewards_account = bp_relayers::PayRewardFromAccount::< Balances, - AccountId + AccountId, + >::LaneId, >::rewards_account(account_params); Self::deposit_account(rewards_account, reward); } 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 4391b069cf0..4ff388f4ba2 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 @@ -16,8 +16,12 @@ #![cfg(test)] +use bp_messages::LegacyLaneId; use bp_polkadot_core::Signature; -use bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; +use bridge_common_config::{ + DeliveryRewardInBalance, RelayersForLegacyLaneIdsMessagesInstance, + RequiredStakeForStakeAndSlash, +}; use bridge_hub_test_utils::{test_cases::from_parachain, SlotDurations}; use bridge_hub_westend_runtime::{ bridge_common_config, bridge_to_rococo_config, @@ -63,6 +67,7 @@ type RuntimeTestsAdapter = from_parachain::WithRemoteParachainHelperAdapter< BridgeGrandpaRococoInstance, BridgeParachainRococoInstance, WithBridgeHubRococoMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; parameter_types! { @@ -235,7 +240,15 @@ fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()).1 + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, XcmOverBridgeHubRococoInstance + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + } + ).1 }, ) } @@ -288,10 +301,16 @@ fn relayed_incoming_message_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -312,10 +331,16 @@ fn free_relay_extrinsic_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get()) + >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }) .1 }, construct_and_apply_extrinsic, + true, ) } @@ -377,22 +402,3 @@ pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { ), ) } - -#[test] -fn open_and_close_bridge_works() { - let origins = [SiblingParachainLocation::get(), SiblingSystemParachainLocation::get()]; - - for origin in origins { - bridge_hub_test_utils::test_cases::open_and_close_bridge_works::< - Runtime, - XcmOverBridgeHubRococoInstance, - LocationToAccountId, - WestendLocation, - >( - collator_session_keys(), - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - origin, - BridgedUniversalLocation::get(), - ) - } -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 8c048a0d2db..915b3090092 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -37,6 +37,7 @@ parachains-runtimes-test-utils = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +pallet-xcm = { workspace = true } # Bridges bp-header-chain = { workspace = true } @@ -81,6 +82,7 @@ std = [ "pallet-timestamp/std", "pallet-utility/std", "pallet-xcm-bridge-hub/std", + "pallet-xcm/std", "parachains-common/std", "parachains-runtimes-test-utils/std", "sp-core/std", 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 b8d6d87051c..bc28df0eb82 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -24,7 +24,9 @@ extern crate alloc; pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; use sp_runtime::Perbill; -pub use test_cases::helpers::ensure_opened_bridge; +pub use test_cases::helpers::{ + ensure_opened_bridge, open_bridge_with_extrinsic, open_bridge_with_storage, +}; /// 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 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 72743eaa41d..320f3030b60 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 @@ -24,12 +24,12 @@ use crate::{ use alloc::{boxed::Box, vec}; use bp_header_chain::ChainWithGrandpa; -use bp_messages::{LaneId, UnrewardedRelayersState}; +use bp_messages::UnrewardedRelayersState; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_bridge_messages::{BridgedChainOf, ThisChainOf}; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; @@ -50,7 +50,7 @@ pub trait WithRemoteGrandpaChainHelper { Self::MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config; + > + pallet_bridge_relayers::Config>; /// All pallets of this chain, excluding system pallet. type AllPalletsWithoutSystem: OnInitialize> + OnFinalize>; @@ -58,15 +58,18 @@ pub trait WithRemoteGrandpaChainHelper { type GPI: 'static; /// Instance of the `pallet-bridge-messages`, used to bridge with remote GRANDPA chain. type MPI: 'static; + /// Instance of the `pallet-bridge-relayers`, used to collect rewards from messages `MPI` + /// instance. + type RPI: 'static; } /// Adapter struct that implements [`WithRemoteGrandpaChainHelper`]. -pub struct WithRemoteGrandpaChainHelperAdapter( - core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, MPI)>, +pub struct WithRemoteGrandpaChainHelperAdapter( + core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, MPI, RPI)>, ); -impl WithRemoteGrandpaChainHelper - for WithRemoteGrandpaChainHelperAdapter +impl WithRemoteGrandpaChainHelper + for WithRemoteGrandpaChainHelperAdapter where Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config @@ -75,16 +78,18 @@ where MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config, + > + pallet_bridge_relayers::Config>, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, GPI: 'static, MPI: 'static, + RPI: 'static, { type Runtime = Runtime; type AllPalletsWithoutSystem = AllPalletsWithoutSystem; type GPI = GPI; type MPI = MPI; + type RPI = RPI; } /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, @@ -96,11 +101,12 @@ pub fn relayed_incoming_message_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteGrandpaChainHelper, AccountIdOf: From, @@ -140,6 +146,7 @@ pub fn relayed_incoming_message_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -172,14 +179,18 @@ pub fn relayed_incoming_message_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -197,11 +208,12 @@ pub fn free_relay_extrinsic_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteGrandpaChainHelper, RuntimeHelper::Runtime: pallet_balances::Config, @@ -263,6 +275,7 @@ pub fn free_relay_extrinsic_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -301,14 +314,18 @@ pub fn free_relay_extrinsic_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -325,7 +342,7 @@ pub fn complex_relay_extrinsic_works( runtime_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, RuntimeCallOf, @@ -372,6 +389,7 @@ pub fn complex_relay_extrinsic_works( test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -382,9 +400,10 @@ pub fn complex_relay_extrinsic_works( ); let relay_chain_header_hash = relay_chain_header.hash(); - vec![( - pallet_utility::Call::::batch_all { - calls: vec![ + vec![ + ( + pallet_utility::Call::::batch_all { + calls: vec![ BridgeGrandpaCall::::submit_finality_proof { finality_target: Box::new(relay_chain_header), justification: grandpa_justification, @@ -396,27 +415,33 @@ pub fn complex_relay_extrinsic_works( dispatch_weight: Weight::from_parts(1000000000, 0), }.into(), ], - } - .into(), - Box::new(( - helpers::VerifySubmitGrandpaFinalityProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::GPI, - >::expect_best_header_hash(relay_chain_header_hash), - helpers::VerifySubmitMessagesProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::MPI, - >::expect_last_delivered_nonce(lane_id, 1), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, + } + .into(), + Box::new( + ( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::< + RuntimeHelper::Runtime, + RuntimeHelper::RPI, + >::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), ), ), - )), - )] + ), + ] }, ); } @@ -446,8 +471,9 @@ where test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -502,8 +528,9 @@ where BridgedChainOf, ThisChainOf, (), + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1u32.into(), AccountId32::from(Alice.public()).into(), unrewarded_relayers.clone(), @@ -550,8 +577,9 @@ where test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -602,8 +630,9 @@ where BridgedChainOf, ThisChainOf, (), + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1u32.into(), AccountId32::from(Alice.public()).into(), unrewarded_relayers.clone(), 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 82edcacdcab..1da901e0bcd 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 @@ -24,14 +24,14 @@ use crate::{ use alloc::{boxed::Box, vec}; use bp_header_chain::ChainWithGrandpa; -use bp_messages::{LaneId, UnrewardedRelayersState}; +use bp_messages::UnrewardedRelayersState; use bp_polkadot_core::parachains::ParaHash; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::{Chain, Parachain}; use bp_xcm_bridge_hub::XcmAsPlainPayload; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; -use pallet_bridge_messages::{BridgedChainOf, ThisChainOf}; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf, ThisChainOf}; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; @@ -53,7 +53,7 @@ pub trait WithRemoteParachainHelper { Self::MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config; + > + pallet_bridge_relayers::Config>; /// All pallets of this chain, excluding system pallet. type AllPalletsWithoutSystem: OnInitialize> + OnFinalize>; @@ -63,15 +63,18 @@ pub trait WithRemoteParachainHelper { type PPI: 'static; /// Instance of the `pallet-bridge-messages`, used to bridge with remote parachain. type MPI: 'static; + /// Instance of the `pallet-bridge-relayers`, used to collect rewards from messages `MPI` + /// instance. + type RPI: 'static; } /// Adapter struct that implements `WithRemoteParachainHelper`. -pub struct WithRemoteParachainHelperAdapter( - core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, PPI, MPI)>, +pub struct WithRemoteParachainHelperAdapter( + core::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, PPI, MPI, RPI)>, ); -impl WithRemoteParachainHelper - for WithRemoteParachainHelperAdapter +impl WithRemoteParachainHelper + for WithRemoteParachainHelperAdapter where Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config @@ -81,19 +84,20 @@ where MPI, InboundPayload = XcmAsPlainPayload, OutboundPayload = XcmAsPlainPayload, - > + pallet_bridge_relayers::Config, + > + pallet_bridge_relayers::Config>, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, GPI: 'static, PPI: 'static, MPI: 'static, - // MB: MessageBridge, + RPI: 'static, { type Runtime = Runtime; type AllPalletsWithoutSystem = AllPalletsWithoutSystem; type GPI = GPI; type PPI = PPI; type MPI = MPI; + type RPI = RPI; } /// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, @@ -106,11 +110,12 @@ pub fn relayed_incoming_message_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteParachainHelper, AccountIdOf: From, @@ -161,6 +166,7 @@ pub fn relayed_incoming_message_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -208,14 +214,18 @@ pub fn relayed_incoming_message_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -234,11 +244,12 @@ pub fn free_relay_extrinsic_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, + expect_rewards: bool, ) where RuntimeHelper: WithRemoteParachainHelper, RuntimeHelper::Runtime: pallet_balances::Config, @@ -312,6 +323,7 @@ pub fn free_relay_extrinsic_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -353,10 +365,10 @@ pub fn free_relay_extrinsic_works( bridged_para_id, parachain_head_hash, ), - /*helpers::VerifyRelayerBalance::::expect_relayer_balance( + helpers::VerifyRelayerBalance::::expect_relayer_balance( relayer_id_at_this_chain.clone(), initial_relayer_balance, - ),*/ + ), )), ), ( @@ -371,14 +383,18 @@ pub fn free_relay_extrinsic_works( lane_id, 1, ), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ), - ), + if expect_rewards { + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ) + } else { + Box::new(()) + } )), ), ] @@ -396,7 +412,7 @@ pub fn complex_relay_extrinsic_works( bridged_para_id: u32, sibling_parachain_id: u32, local_relay_chain_id: NetworkId, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( sp_keyring::AccountKeyring, ::RuntimeCall, @@ -454,6 +470,7 @@ pub fn complex_relay_extrinsic_works( >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( lane_id, xcm.into(), @@ -468,9 +485,10 @@ pub fn complex_relay_extrinsic_works( let parachain_head_hash = parachain_head.hash(); let relay_chain_header_hash = relay_chain_header.hash(); let relay_chain_header_number = *relay_chain_header.number(); - vec![( - pallet_utility::Call::::batch_all { - calls: vec![ + vec![ + ( + pallet_utility::Call::::batch_all { + calls: vec![ BridgeGrandpaCall::::submit_finality_proof { finality_target: Box::new(relay_chain_header), justification: grandpa_justification, @@ -487,31 +505,37 @@ pub fn complex_relay_extrinsic_works( dispatch_weight: Weight::from_parts(1000000000, 0), }.into(), ], - } - .into(), - Box::new(( - helpers::VerifySubmitGrandpaFinalityProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::GPI, - >::expect_best_header_hash(relay_chain_header_hash), - helpers::VerifySubmitParachainHeaderProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::PPI, - >::expect_best_header_hash(bridged_para_id, parachain_head_hash), - helpers::VerifySubmitMessagesProofOutcome::< - RuntimeHelper::Runtime, - RuntimeHelper::MPI, - >::expect_last_delivered_nonce(lane_id, 1), - helpers::VerifyRelayerRewarded::::expect_relayer_reward( - relayer_id_at_this_chain, - RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, + } + .into(), + Box::new( + ( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitParachainHeaderProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::PPI, + >::expect_best_header_hash(bridged_para_id, parachain_head_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::< + RuntimeHelper::Runtime, + RuntimeHelper::RPI, + >::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), ), ), - )), - )] + ), + ] }, ); } @@ -551,8 +575,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -621,8 +646,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1, 5, 1_000, @@ -683,8 +709,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), vec![Instruction::<()>::ClearOrigin; 1_024].into(), 1, [GlobalConsensus(Polkadot), Parachain(1_000)].into(), @@ -738,8 +765,9 @@ where >::BridgedChain, BridgedChainOf, ThisChainOf, + LaneIdOf, >( - LaneId::new(1, 2), + LaneIdOf::::default(), 1, 5, 1_000, 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 c343e9b3e09..aac60bba0b5 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 @@ -19,7 +19,7 @@ use crate::test_cases::{bridges_prelude::*, run_test, RuntimeHelper}; use asset_test_utils::BasicParachainRuntime; -use bp_messages::{LaneId, MessageNonce}; +use bp_messages::MessageNonce; use bp_polkadot_core::parachains::{ParaHash, ParaId}; use bp_relayers::RewardsAccountParams; use bp_runtime::Chain; @@ -33,7 +33,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader}; -use pallet_bridge_messages::BridgedChainOf; +use pallet_bridge_messages::{BridgedChainOf, LaneIdOf}; use parachains_common::AccountId; use parachains_runtimes_test_utils::{ mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -132,8 +132,8 @@ where } /// Checks that the latest delivered nonce in the bridge messages pallet equals to given one. -pub struct VerifySubmitMessagesProofOutcome { - lane: LaneId, +pub struct VerifySubmitMessagesProofOutcome, MPI: 'static> { + lane: LaneIdOf, expected_nonce: MessageNonce, _marker: PhantomData<(Runtime, MPI)>, } @@ -145,7 +145,7 @@ where { /// Expect given delivered nonce to be the latest after transaction. pub fn expect_last_delivered_nonce( - lane: LaneId, + lane: LaneIdOf, expected_nonce: MessageNonce, ) -> Box { Box::new(Self { lane, expected_nonce, _marker: PhantomData }) @@ -167,30 +167,32 @@ where } /// Verifies that relayer is rewarded at this chain. -pub struct VerifyRelayerRewarded { +pub struct VerifyRelayerRewarded, RPI: 'static> { relayer: Runtime::AccountId, - reward_params: RewardsAccountParams, + reward_params: RewardsAccountParams, } -impl VerifyRelayerRewarded +impl VerifyRelayerRewarded where - Runtime: pallet_bridge_relayers::Config, + Runtime: pallet_bridge_relayers::Config, + RPI: 'static, { /// Expect given delivered nonce to be the latest after transaction. pub fn expect_relayer_reward( relayer: Runtime::AccountId, - reward_params: RewardsAccountParams, + reward_params: RewardsAccountParams, ) -> Box { Box::new(Self { relayer, reward_params }) } } -impl VerifyTransactionOutcome for VerifyRelayerRewarded +impl VerifyTransactionOutcome for VerifyRelayerRewarded where - Runtime: pallet_bridge_relayers::Config, + Runtime: pallet_bridge_relayers::Config, + RPI: 'static, { fn verify_outcome(&self) { - assert!(pallet_bridge_relayers::RelayerRewards::::get( + assert!(pallet_bridge_relayers::RelayerRewards::::get( &self.relayer, &self.reward_params, ) @@ -388,7 +390,12 @@ fn execute_and_verify_calls( /// Helper function to open the bridge/lane for `source` and `destination` while ensuring all /// required balances are placed into the SA of the source. -pub fn ensure_opened_bridge(source: Location, destination: InteriorLocation) -> (BridgeLocations, LaneId) +pub fn ensure_opened_bridge< + Runtime, + XcmOverBridgePalletInstance, + LocationToAccountId, + TokenLocation> +(source: Location, destination: InteriorLocation, bridge_opener: impl Fn(BridgeLocations, Asset)) -> (BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf) where Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig, XcmOverBridgePalletInstance: 'static, @@ -425,34 +432,74 @@ TokenLocation: Get{ let _ = >::mint_into(&source_account_id, balance_needed) .expect("mint_into passes"); + // call the bridge opener + bridge_opener(*locations.clone(), buy_execution_fee); + + // check opened bridge + let bridge = pallet_xcm_bridge_hub::Bridges::::get( + locations.bridge_id(), + ) + .expect("opened bridge"); + + // check state + assert_ok!( + pallet_xcm_bridge_hub::Pallet::::do_try_state() + ); + + // return locations + (*locations, bridge.lane_id) +} + +/// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic. +pub fn open_bridge_with_extrinsic( + locations: BridgeLocations, + buy_execution_fee: Asset, +) where + Runtime: frame_system::Config + + pallet_xcm_bridge_hub::Config + + cumulus_pallet_parachain_system::Config + + pallet_xcm::Config, + XcmOverBridgePalletInstance: 'static, + ::RuntimeCall: + GetDispatchInfo + From>, +{ // open bridge with `Transact` call let open_bridge_call = RuntimeCallOf::::from(BridgeXcmOverBridgeCall::< Runtime, XcmOverBridgePalletInstance, >::open_bridge { - bridge_destination_universal_location: Box::new(destination.into()), + bridge_destination_universal_location: Box::new( + locations.bridge_destination_universal_location().clone().into(), + ), }); // execute XCM as source origin would do with `Transact -> Origin::Xcm` assert_ok!(RuntimeHelper::::execute_as_origin_xcm( + locations.bridge_origin_relative_location().clone(), open_bridge_call, - source.clone(), buy_execution_fee ) .ensure_complete()); +} - let bridge = pallet_xcm_bridge_hub::Bridges::::get( - locations.bridge_id(), - ) - .expect("opened bridge"); - - // check state +/// Utility for opening bridge directly inserting data to the storage (used only for legacy +/// purposes). +pub fn open_bridge_with_storage( + locations: BridgeLocations, + _buy_execution_fee: Asset, + lane_id: pallet_xcm_bridge_hub::LaneIdOf, +) where + Runtime: pallet_xcm_bridge_hub::Config, + XcmOverBridgePalletInstance: 'static, +{ + // insert bridge data directly to the storage assert_ok!( - pallet_xcm_bridge_hub::Pallet::::do_try_state() + pallet_xcm_bridge_hub::Pallet::::do_open_bridge( + Box::new(locations), + lane_id, + true + ) ); - - // return locations - (*locations, bridge.lane_id) } /// Helper function to close the bridge/lane for `source` and `destination`. @@ -504,8 +551,8 @@ TokenLocation: Get{ // execute XCM as source origin would do with `Transact -> Origin::Xcm` assert_ok!(RuntimeHelper::::execute_as_origin_xcm( - close_bridge_call, source.clone(), + close_bridge_call, buy_execution_fee ) .ensure_complete()); 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 de117982b26..663558f5fd5 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 @@ -29,7 +29,7 @@ use crate::{test_cases::bridges_prelude::*, test_data}; use asset_test_utils::BasicParachainRuntime; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, - LaneId, LaneState, MessageKey, MessagesOperatingMode, OutboundLaneData, + LaneState, MessageKey, MessagesOperatingMode, OutboundLaneData, }; use bp_runtime::BasicOperatingMode; use bp_xcm_bridge_hub::{Bridge, BridgeState, XcmAsPlainPayload}; @@ -71,11 +71,13 @@ pub(crate) mod bridges_prelude { // Re-export test_case from assets pub use asset_test_utils::include_teleports_for_native_asset_works; +use pallet_bridge_messages::LaneIdOf; pub type RuntimeHelper = parachains_runtimes_test_utils::RuntimeHelper; // Re-export test_case from `parachains-runtimes-test-utils` +use crate::test_cases::helpers::open_bridge_with_extrinsic; pub use parachains_runtimes_test_utils::test_cases::{ change_storage_constant_by_governance_works, set_storage_keys_by_governance_works, }; @@ -326,7 +328,7 @@ pub fn handle_export_message_from_system_parachain_to_outbound_queue_works< export_message_instruction: fn() -> Instruction, existential_deposit: Option, maybe_paid_export_message: Option, - prepare_configuration: impl Fn() -> LaneId, + prepare_configuration: impl Fn() -> LaneIdOf, ) where Runtime: BasicParachainRuntime + BridgeMessagesConfig, XcmConfig: xcm_executor::Config, @@ -469,7 +471,7 @@ pub fn message_dispatch_routing_works< run_test::(collator_session_key, runtime_para_id, vec![], || { prepare_configuration(); - let dummy_lane_id = LaneId::new(1, 2); + let dummy_lane_id = LaneIdOf::::default(); let mut alice = [0u8; 32]; alice[0] = 1; @@ -714,7 +716,11 @@ pub fn open_and_close_bridge_works(source.clone(), destination.clone()) + >( + source.clone(), + destination.clone(), + open_bridge_with_extrinsic:: + ) .0 .bridge_id(), locations.bridge_id() diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs index 2940c4e00f4..7461085330f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -20,8 +20,8 @@ use crate::test_data::prepare_inbound_xcm; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneId, LaneState, - MessageNonce, UnrewardedRelayersState, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneState, MessageNonce, + UnrewardedRelayersState, }; use bp_runtime::{AccountIdOf, BlockNumberOf, Chain, HeaderOf, UnverifiedStorageProofParams}; use bp_test_utils::make_default_justification; @@ -40,7 +40,7 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, + BridgedChainOf, LaneIdOf, }; use sp_runtime::DigestItem; @@ -48,7 +48,10 @@ use sp_runtime::DigestItem; pub fn make_complex_relayer_delivery_batch( bridged_header: BridgedHeader, bridged_justification: GrandpaJustification>, - message_proof: FromBridgedChainMessagesProof>>, + message_proof: FromBridgedChainMessagesProof< + HashOf>, + LaneIdOf, + >, relayer_id_at_bridged_chain: InboundRelayerId, ) -> pallet_utility::Call where @@ -82,6 +85,7 @@ pub fn make_complex_relayer_confirmation_batch( bridged_justification: GrandpaJustification>, message_delivery_proof: FromBridgedChainMessagesDeliveryProof< HashOf>, + LaneIdOf, >, relayers_state: UnrewardedRelayersState, ) -> pallet_utility::Call @@ -111,7 +115,10 @@ where /// Prepare a call with message proof. pub fn make_standalone_relayer_delivery_call( - message_proof: FromBridgedChainMessagesProof>>, + message_proof: FromBridgedChainMessagesProof< + HashOf>, + LaneIdOf, + >, relayer_id_at_bridged_chain: InboundRelayerId, ) -> Runtime::RuntimeCall where @@ -134,6 +141,7 @@ where pub fn make_standalone_relayer_confirmation_call( message_delivery_proof: FromBridgedChainMessagesDeliveryProof< HashOf>, + LaneIdOf, >, relayers_state: UnrewardedRelayersState, ) -> Runtime::RuntimeCall @@ -152,7 +160,7 @@ where } /// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain. -pub fn make_complex_relayer_delivery_proofs( +pub fn make_complex_relayer_delivery_proofs( lane_id: LaneId, xcm_message: Xcm<()>, message_nonce: MessageNonce, @@ -162,17 +170,18 @@ pub fn make_complex_relayer_delivery_proofs ) -> ( HeaderOf, GrandpaJustification>, - FromBridgedChainMessagesProof>, + FromBridgedChainMessagesProof, LaneId>, ) where BridgedChain: ChainWithGrandpa, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare message let message_payload = prepare_inbound_xcm(xcm_message, message_destination); // prepare storage proof containing message let (state_root, storage_proof) = - prepare_messages_storage_proof::( + prepare_messages_storage_proof::( lane_id, message_nonce..=message_nonce, None, @@ -206,6 +215,7 @@ pub fn make_complex_relayer_confirmation_proofs< BridgedChain, ThisChainWithMessages, InnerXcmRuntimeCall, + LaneId, >( lane_id: LaneId, header_number: BlockNumberOf, @@ -214,15 +224,16 @@ pub fn make_complex_relayer_confirmation_proofs< ) -> ( HeaderOf, GrandpaJustification>, - FromBridgedChainMessagesDeliveryProof>, + FromBridgedChainMessagesDeliveryProof, LaneId>, ) where BridgedChain: ChainWithGrandpa, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare storage proof containing message delivery proof let (state_root, storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane_id, InboundLaneData { state: LaneState::Opened, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs index aefbc0dbd0a..a6659b8241d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -20,7 +20,7 @@ use super::{from_grandpa_chain::make_complex_bridged_grandpa_header_proof, prepa use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneId, LaneState, + target_chain::FromBridgedChainMessagesProof, ChainWithMessages, LaneState, UnrewardedRelayersState, Weight, }; use bp_parachains::{RelayBlockHash, RelayBlockNumber}; @@ -43,7 +43,7 @@ use pallet_bridge_messages::{ encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, prepare_messages_storage_proof, }, - BridgedChainOf, + BridgedChainOf, LaneIdOf, }; use sp_runtime::SaturatedConversion; @@ -53,7 +53,7 @@ pub fn make_complex_relayer_delivery_batch( grandpa_justification: GrandpaJustification>, parachain_heads: Vec<(ParaId, ParaHash)>, para_heads_proof: ParaHeadsProof, - message_proof: FromBridgedChainMessagesProof, + message_proof: FromBridgedChainMessagesProof>, relayer_id_at_bridged_chain: InboundRelayerId, ) -> pallet_utility::Call where @@ -106,7 +106,7 @@ pub fn make_complex_relayer_confirmation_batch( grandpa_justification: GrandpaJustification>, parachain_heads: Vec<(ParaId, ParaHash)>, para_heads_proof: ParaHeadsProof, - message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof>, relayers_state: UnrewardedRelayersState, ) -> pallet_utility::Call where @@ -154,7 +154,7 @@ where /// Prepare a call with message proof. pub fn make_standalone_relayer_delivery_call( - message_proof: FromBridgedChainMessagesProof, + message_proof: FromBridgedChainMessagesProof>, relayer_id_at_bridged_chain: InboundRelayerId, ) -> Runtime::RuntimeCall where @@ -174,7 +174,7 @@ where /// Prepare a call with message delivery proof. pub fn make_standalone_relayer_confirmation_call( - message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof>, relayers_state: UnrewardedRelayersState, ) -> Runtime::RuntimeCall where @@ -195,6 +195,7 @@ pub fn make_complex_relayer_delivery_proofs< BridgedRelayChain, BridgedParachain, ThisChainWithMessages, + LaneId, >( lane_id: LaneId, xcm_message: Xcm<()>, @@ -210,19 +211,20 @@ pub fn make_complex_relayer_delivery_proofs< ParaHead, Vec<(ParaId, ParaHash)>, ParaHeadsProof, - FromBridgedChainMessagesProof, + FromBridgedChainMessagesProof, ) where BridgedRelayChain: bp_runtime::Chain + ChainWithGrandpa, BridgedParachain: bp_runtime::Chain + Parachain, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare message let message_payload = prepare_inbound_xcm(xcm_message, message_destination); // prepare para storage proof containing message let (para_state_root, para_storage_proof) = - prepare_messages_storage_proof::( + prepare_messages_storage_proof::( lane_id, message_nonce..=message_nonce, None, @@ -266,6 +268,7 @@ pub fn make_complex_relayer_confirmation_proofs< BridgedRelayChain, BridgedParachain, ThisChainWithMessages, + LaneId, >( lane_id: LaneId, para_header_number: u32, @@ -279,17 +282,18 @@ pub fn make_complex_relayer_confirmation_proofs< ParaHead, Vec<(ParaId, ParaHash)>, ParaHeadsProof, - FromBridgedChainMessagesDeliveryProof, + FromBridgedChainMessagesDeliveryProof, ) where BridgedRelayChain: bp_runtime::Chain + ChainWithGrandpa, BridgedParachain: bp_runtime::Chain + Parachain, ThisChainWithMessages: ChainWithMessages, + LaneId: Copy + Encode, { // prepare para storage proof containing message delivery proof let (para_state_root, para_storage_proof) = - prepare_message_delivery_storage_proof::( + prepare_message_delivery_storage_proof::( lane_id, InboundLaneData { state: LaneState::Opened, diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs index 106eacd799c..c34188af506 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs @@ -21,7 +21,7 @@ pub mod from_parachain; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData}, - LaneId, MessageKey, + MessageKey, }; use codec::Encode; use frame_support::traits::Get; @@ -65,11 +65,11 @@ pub(crate) fn dummy_xcm() -> Xcm<()> { vec![Trap(42)].into() } -pub(crate) fn dispatch_message( +pub(crate) fn dispatch_message( lane_id: LaneId, nonce: MessageNonce, payload: Vec, -) -> DispatchMessage> { +) -> DispatchMessage, LaneId> { DispatchMessage { key: MessageKey { lane_id, nonce }, data: DispatchMessageData { payload: Ok(payload) }, diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index fe75b2b6e72..3b38eee244f 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -465,8 +465,8 @@ impl< } pub fn execute_as_origin_xcm( - call: Call, origin: Location, + call: Call, buy_execution_fee: Asset, ) -> Outcome { // prepare `Transact` xcm diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index ad88be60d74..d52bc639b3b 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -57,6 +57,9 @@ xcm = { workspace = true, default-features = true } # Cumulus cumulus-primitives-core = { workspace = true, default-features = true } +# Bridges +bp-messages = { workspace = true, default-features = true } + [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index 55b9156e6a0..2198da13a4b 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -1,7 +1,7 @@ # 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:v1.6.8 +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.6.10 # metadata ARG VCS_REF diff --git a/polkadot/xcm/xcm-executor/src/traits/export.rs b/polkadot/xcm/xcm-executor/src/traits/export.rs index 78aa68ce264..b356e0da7df 100644 --- a/polkadot/xcm/xcm-executor/src/traits/export.rs +++ b/polkadot/xcm/xcm-executor/src/traits/export.rs @@ -20,7 +20,7 @@ use xcm::latest::prelude::*; /// spoofed origin. This essentially defines the behaviour of the `ExportMessage` XCM instruction. /// /// This is quite different to `SendXcm`; `SendXcm` assumes that the local side's location will be -/// preserved to be represented as the value of the Origin register in the messages execution. +/// preserved to be represented as the value of the Origin register during the message's execution. /// /// This trait on the other hand assumes that we do not necessarily want the Origin register to /// contain the local (i.e. the caller chain's) location, since it will generally be exporting a @@ -44,8 +44,8 @@ pub trait ExportXcm { /// The `destination` and `message` must be `Some` (or else an error will be returned) and they /// may only be consumed if the `Err` is not `NotApplicable`. /// - /// If it is not a destination which can be reached with this type but possibly could by others, - /// then this *MUST* return `NotApplicable`. Any other error will cause the tuple + /// If it is not a destination that can be reached with this type, but possibly could be with + /// others, then this *MUST* return `NotApplicable`. Any other error will cause the tuple /// implementation (used to compose routing systems from different delivery agents) to exit /// early without trying alternative means of delivery. fn validate( diff --git a/prdoc/pr_5649.prdoc b/prdoc/pr_5649.prdoc new file mode 100644 index 00000000000..1f4c97aa175 --- /dev/null +++ b/prdoc/pr_5649.prdoc @@ -0,0 +1,49 @@ +title: "Bridges lane id agnostic for backwards compatibility" + +doc: +- audience: Runtime Dev + description: | + This PR improves support for handling `LaneId` backwards compatibility with the previously merged [PR](https://github.com/paritytech/polkadot-sdk/pull/4949). + If `pallet_bridge_messages` or `pallet_bridge_relayers` used `LaneId([u8; 4])` previously, they should now set `type LaneId = LegacyLaneId;`. + +crates: +- name: bridge-runtime-common + bump: patch +- name: bp-runtime + bump: patch +- name: staging-xcm-executor + bump: none +- name: parachains-runtimes-test-utils + bump: patch +- name: bp-messages + bump: major +- name: bp-relayers + bump: major +- name: bp-xcm-bridge-hub + bump: major +- name: pallet-bridge-messages + bump: patch +- name: pallet-bridge-relayers + bump: patch +- name: pallet-xcm-bridge-hub + bump: major +- name: emulated-integration-tests-common + bump: patch +- name: bp-bridge-hub-kusama + bump: patch +- name: bp-bridge-hub-polkadot + bump: patch +- name: bp-bridge-hub-rococo + bump: patch +- name: bp-bridge-hub-westend + bump: patch +- name: bp-polkadot-bulletin + bump: patch +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: patch +- name: polkadot-parachain-bin + bump: none +- name: bridge-hub-test-utils + bump: major -- GitLab From 286af23bc29da7d1e95b7d97a0a76547dba52189 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Wed, 25 Sep 2024 17:09:41 +0300 Subject: [PATCH 006/127] approval-voting: remove dead test code (#5815) Minor cleanup. --------- Signed-off-by: Andrei Sandu --- polkadot/node/core/approval-voting/src/tests.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 65aa4f894c2..8aa78df5495 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -39,7 +39,7 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; -use polkadot_overseer::{HeadSupportsParachains, SpawnGlue}; +use polkadot_overseer::SpawnGlue; use polkadot_primitives::{ ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex, DisputeStatement, GroupIndex, Header, Id as ParaId, IndexedVec, NodeFeatures, ValidDisputeStatementKind, ValidationCode, @@ -48,7 +48,6 @@ use polkadot_primitives::{ use std::{cmp::max, time::Duration}; use assert_matches::assert_matches; -use async_trait::async_trait; use parking_lot::Mutex; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use sp_keystore::Keystore; @@ -131,15 +130,6 @@ pub mod test_constants { pub(crate) const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_approval_data: DATA_COL }; } -struct MockSupportsParachains; - -#[async_trait] -impl HeadSupportsParachains for MockSupportsParachains { - async fn head_supports_parachains(&self, _head: &Hash) -> bool { - true - } -} - fn slot_to_tick(t: impl Into) -> Tick { slot_number_to_tick(SLOT_DURATION_MILLIS, t.into()) } -- GitLab From cc6a5130bed92d4ddc073b0ea1d006360e98ddd5 Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 26 Sep 2024 00:38:21 +1000 Subject: [PATCH 007/127] MBM `try-runtime` support (#4251) # MBM try-runtime support This MR adds support to the try-runtime trait such that the try-runtime-CLI will be able to support MBM testing [here](https://github.com/paritytech/try-runtime-cli/pull/90). It mainly adds two feature-gated hooks to the `SteppedMigration` hook to facilitate testing. These hooks are named `pre_upgrade` and `post_upgrade` and have the same signature and implications as for single-block migrations. ## Integration To make use of this in your Multi-Block-Migration, just implement the two new hooks and test pre- and post-conditions in them: ```rust #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, frame_support::sp_runtime::TryRuntimeError> { // ... } #[cfg(feature = "try-runtime")] fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { // ... } ``` You may return an error or panic in these functions to indicate failure. This will then show up in the try-runtime-CLI and can be used in CI for testing. Changes: - Adds `try-runtime` gated methods `pre_upgrade` and `post_upgrade` on `SteppedMigration` - Adds `try-runtime` gated methods `nth_pre_upgrade` and `nth_post_upgrade` on `SteppedMigrations` - Modifies `pallet_migrations` implementation to run pre_upgrade and post_upgrade steps at the appropriate times, and panic in the event of migration failure. --------- Signed-off-by: Oliver Tale-Yazdi Signed-off-by: georgepisaltu Co-authored-by: Oliver Tale-Yazdi Co-authored-by: claravanstaden Co-authored-by: ggwpez Co-authored-by: georgepisaltu --- .gitlab/pipeline/test.yml | 24 +++++ Cargo.lock | 1 + .../runtimes/people/people-rococo/src/lib.rs | 1 + prdoc/pr_4251.prdoc | 79 +++++++++++++++ .../src/migrations/v1/mod.rs | 43 ++++++++ substrate/frame/migrations/Cargo.toml | 1 + substrate/frame/migrations/src/lib.rs | 64 ++++++++++-- .../frame/migrations/src/mock_helpers.rs | 33 +++++++ substrate/frame/migrations/src/tests.rs | 98 ++++++++++++++++++- substrate/frame/support/src/migrations.rs | 96 +++++++++++++++++- .../support/src/traits/try_runtime/mod.rs | 4 +- 11 files changed, 432 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_4251.prdoc diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 00a0aa2c977..6fb35c61e48 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -110,6 +110,30 @@ test-linux-stable-codecov: codecovcli -v do-upload -f target/coverage/result/report-${CI_NODE_INDEX}.lcov --disable-search -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --fail-on-error --git-service github; fi +# some tests do not run with `try-runtime` feature enabled +# https://github.com/paritytech/polkadot-sdk/pull/4251#discussion_r1624282143 +test-linux-stable-no-try-runtime: + stage: test + extends: + - .docker-env + - .common-refs + - .run-immediately + - .pipeline-stopper-artifacts + variables: + RUST_TOOLCHAIN: stable + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + script: + - > + time cargo nextest run \ + --workspace \ + --locked \ + --release \ + --no-fail-fast \ + --cargo-quiet \ + --features experimental,riscv,ci-only-tests + test-doc: stage: test extends: diff --git a/Cargo.lock b/Cargo.lock index 69eabeb04e2..61f485bcecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11840,6 +11840,7 @@ dependencies = [ name = "pallet-migrations" version = "1.0.0" dependencies = [ + "cfg-if", "docify", "frame-benchmarking", "frame-executive", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 150152964b9..16e023ad3dc 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -111,6 +111,7 @@ pub type UncheckedExtrinsic = /// Migrations to apply on runtime upgrade. pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2, + cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/prdoc/pr_4251.prdoc b/prdoc/pr_4251.prdoc new file mode 100644 index 00000000000..4d4fcd73469 --- /dev/null +++ b/prdoc/pr_4251.prdoc @@ -0,0 +1,79 @@ +title: MBM `try-runtime` support +doc: +- audience: Runtime Dev + description: | + # MBM try-runtime support + + This MR adds support to the try-runtime + trait such that the try-runtime-CLI will be able to support MBM testing [here](https://github.com/paritytech/try-runtime-cli/pull/90). + It mainly adds two feature-gated hooks to the `SteppedMigration` hook to facilitate + testing. These hooks are named `pre_upgrade` and `post_upgrade` and have the + same signature and implications as for single-block migrations. + + ## Integration + + To make use of this in your Multi-Block-Migration, just implement the two new hooks and test pre- and post-conditions in them: + + ```rust + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, frame_support::sp_runtime::TryRuntimeError> + { + // ... + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { + // ... + } + ``` + + You may return an error or panic in these functions to indicate failure. + This will then show up in the try-runtime-CLI and can be used in CI for testing. + + + Changes: + - Adds `try-runtime` gated methods `pre_upgrade` and `post_upgrade` + on `SteppedMigration` + - Adds `try-runtime` gated methods `nth_pre_upgrade` + and `nth_post_upgrade` on `SteppedMigrations` + - Modifies `pallet_migrations` + implementation to run pre_upgrade and post_upgrade steps at the appropriate times, and panic in the event of migration failure. +crates: +- name: asset-hub-rococo-runtime + bump: minor +- name: asset-hub-westend-runtime + bump: minor +- name: bridge-hub-rococo-runtime + bump: minor +- name: bridge-hub-westend-runtime + bump: minor +- name: collectives-westend-runtime + bump: minor +- name: contracts-rococo-runtime + bump: minor +- name: coretime-rococo-runtime + bump: minor +- name: coretime-westend-runtime + bump: minor +- name: people-rococo-runtime + bump: minor +- name: people-westend-runtime + bump: minor +- name: penpal-runtime + bump: minor +- name: polkadot-parachain-bin + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: frame-executive + bump: minor +- name: pallet-migrations + bump: minor +- name: frame-support + bump: minor +- name: frame-system + bump: minor +- name: frame-try-runtime + bump: minor diff --git a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs index 2016b03de45..6243846d86b 100644 --- a/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs +++ b/substrate/frame/examples/multi-block-migrations/src/migrations/v1/mod.rs @@ -21,6 +21,8 @@ //! [`v0::MyMap`](`crate::migrations::v1::v0::MyMap`) storage map, transforms them, //! and inserts them into the [`MyMap`](`crate::pallet::MyMap`) storage map. +extern crate alloc; + use super::PALLET_MIGRATIONS_ID; use crate::pallet::{Config, MyMap}; use frame_support::{ @@ -29,6 +31,12 @@ use frame_support::{ weights::WeightMeter, }; +#[cfg(feature = "try-runtime")] +use alloc::collections::btree_map::BTreeMap; + +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; + mod benchmarks; mod tests; pub mod weights; @@ -115,4 +123,39 @@ impl SteppedMigration for LazyMigrationV1 Result, frame_support::sp_runtime::TryRuntimeError> { + use codec::Encode; + + // Return the state of the storage before the migration. + Ok(v0::MyMap::::iter().collect::>().encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { + use codec::Decode; + + // Check the state of the storage after the migration. + let prev_map = BTreeMap::::decode(&mut &prev[..]) + .expect("Failed to decode the previous storage state"); + + // Check the len of prev and post are the same. + assert_eq!( + MyMap::::iter().count(), + prev_map.len(), + "Migration failed: the number of items in the storage after the migration is not the same as before" + ); + + for (key, value) in prev_map { + let new_value = + MyMap::::get(key).expect("Failed to get the value after the migration"); + assert_eq!( + value as u64, new_value, + "Migration failed: the value after the migration is not the same as before" + ); + } + + Ok(()) + } } diff --git a/substrate/frame/migrations/Cargo.toml b/substrate/frame/migrations/Cargo.toml index 5fbed74a440..a32e48e6528 100644 --- a/substrate/frame/migrations/Cargo.toml +++ b/substrate/frame/migrations/Cargo.toml @@ -12,6 +12,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } +cfg-if = { workspace = true } docify = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true, default-features = true } diff --git a/substrate/frame/migrations/src/lib.rs b/substrate/frame/migrations/src/lib.rs index 1823e5a2f95..d9490e7dcfe 100644 --- a/substrate/frame/migrations/src/lib.rs +++ b/substrate/frame/migrations/src/lib.rs @@ -70,21 +70,26 @@ //! points to the currently active migration and stores its inner cursor. The inner cursor can then //! be used by the migration to store its inner state and advance. Each time when the migration //! returns `Some(cursor)`, it signals the pallet that it is not done yet. +//! //! The cursor is reset on each runtime upgrade. This ensures that it starts to execute at the //! first migration in the vector. The pallets cursor is only ever incremented or set to `Stuck` //! once it encounters an error (Goal 4). Once in the stuck state, the pallet will stay stuck until //! it is fixed through manual governance intervention. +//! //! As soon as the cursor of the pallet becomes `Some(_)`; [`MultiStepMigrator::ongoing`] returns //! `true` (Goal 2). This can be used by upstream code to possibly pause transactions. //! In `on_initialize` the pallet will load the current migration and check whether it was already //! executed in the past by checking for membership of its ID in the [`Historic`] set. Historic //! migrations are skipped without causing an error. Each successfully executed migration is added //! to this set (Goal 5). +//! //! This proceeds until no more migrations remain. At that point, the event `UpgradeCompleted` is //! emitted (Goal 1). +//! //! The execution of each migration happens by calling [`SteppedMigration::transactional_step`]. //! This function wraps the inner `step` function into a transactional layer to allow rollback in //! the error case (Goal 6). +//! //! Weight limits must be checked by the migration itself. The pallet provides a [`WeightMeter`] for //! that purpose. The pallet may return [`SteppedMigrationError::InsufficientWeight`] at any point. //! In that scenario, one of two things will happen: if that migration was exclusively executed @@ -156,11 +161,15 @@ use core::ops::ControlFlow; use frame_support::{ defensive, defensive_assert, migrations::*, + pallet_prelude::*, traits::Get, weights::{Weight, WeightMeter}, BoundedVec, }; -use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, *}, + Pallet as System, +}; use sp_runtime::Saturating; /// Points to the next migration to execute. @@ -262,6 +271,7 @@ pub type IdentifierOf = BoundedVec::IdentifierMaxLen>; pub type ActiveCursorOf = ActiveCursor, BlockNumberFor>; /// Trait for a tuple of No-OP migrations with one element. +#[impl_trait_for_tuples::impl_for_tuples(30)] pub trait MockedMigrations: SteppedMigrations { /// The migration should fail after `n` steps. fn set_fail_after(n: u32); @@ -269,11 +279,24 @@ pub trait MockedMigrations: SteppedMigrations { fn set_success_after(n: u32); } +#[cfg(feature = "try-runtime")] +/// Wrapper for pre-upgrade bytes, allowing us to impl MEL on it. +/// +/// For `try-runtime` testing only. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, scale_info::TypeInfo, Default)] +struct PreUpgradeBytesWrapper(pub Vec); + +/// Data stored by the pre-upgrade hook of the MBMs. Only used for `try-runtime` testing. +/// +/// Define this outside of the pallet so it is not confused with actual storage. +#[cfg(feature = "try-runtime")] +#[frame_support::storage_alias] +type PreUpgradeBytes = + StorageMap, Twox64Concat, IdentifierOf, PreUpgradeBytesWrapper, ValueQuery>; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -701,6 +724,16 @@ impl Pallet { } let max_steps = T::Migrations::nth_max_steps(cursor.index); + + // If this is the first time running this migration, exec the pre-upgrade hook. + #[cfg(feature = "try-runtime")] + if !PreUpgradeBytes::::contains_key(&bounded_id) { + let bytes = T::Migrations::nth_pre_upgrade(cursor.index) + .expect("Invalid cursor.index") + .expect("Pre-upgrade failed"); + PreUpgradeBytes::::insert(&bounded_id, PreUpgradeBytesWrapper(bytes)); + } + let next_cursor = T::Migrations::nth_transactional_step( cursor.index, cursor.inner_cursor.clone().map(|c| c.into_inner()), @@ -735,6 +768,16 @@ impl Pallet { }, Ok(None) => { // A migration is done when it returns cursor `None`. + + // Run post-upgrade checks. + #[cfg(feature = "try-runtime")] + T::Migrations::nth_post_upgrade( + cursor.index, + PreUpgradeBytes::::get(&bounded_id).0, + ) + .expect("Invalid cursor.index.") + .expect("Post-upgrade failed."); + Self::deposit_event(Event::MigrationCompleted { index: cursor.index, took }); Historic::::insert(&bounded_id, ()); cursor.goto_next_migration(System::::block_number()); @@ -759,14 +802,21 @@ impl Pallet { } /// Fail the current runtime upgrade, caused by `migration`. + /// + /// When the `try-runtime` feature is enabled, this function will panic. + // Allow unreachable code so it can compile without warnings when `try-runtime` is enabled. fn upgrade_failed(migration: Option) { use FailedMigrationHandling::*; Self::deposit_event(Event::UpgradeFailed); - match T::FailedMigrationHandler::failed(migration) { - KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), - ForceUnstuck => Cursor::::kill(), - Ignore => {}, + if cfg!(feature = "try-runtime") { + panic!("Migration with index {:?} failed.", migration); + } else { + match T::FailedMigrationHandler::failed(migration) { + KeepStuck => Cursor::::set(Some(MigrationCursor::Stuck)), + ForceUnstuck => Cursor::::kill(), + Ignore => {}, + } } } diff --git a/substrate/frame/migrations/src/mock_helpers.rs b/substrate/frame/migrations/src/mock_helpers.rs index 9d3b4d1193f..a03c70051d3 100644 --- a/substrate/frame/migrations/src/mock_helpers.rs +++ b/substrate/frame/migrations/src/mock_helpers.rs @@ -43,6 +43,12 @@ pub enum MockedMigrationKind { /// Cause an [`SteppedMigrationError::InsufficientWeight`] error after its number of steps /// elapsed. HighWeightAfter(Weight), + /// PreUpgrade should fail. + #[cfg(feature = "try-runtime")] + PreUpgradeFail, + /// PostUpgrade should fail. + #[cfg(feature = "try-runtime")] + PostUpgradeFail, } use MockedMigrationKind::*; // C style @@ -99,6 +105,8 @@ impl SteppedMigrations for MockedMigrations { Err(SteppedMigrationError::Failed) }, TimeoutAfter => unreachable!(), + #[cfg(feature = "try-runtime")] + PreUpgradeFail | PostUpgradeFail => Ok(None), }) } @@ -115,6 +123,31 @@ impl SteppedMigrations for MockedMigrations { MIGRATIONS::get().get(n as usize).map(|(_, s)| Some(*s)) } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + let (kind, _) = MIGRATIONS::get()[n as usize]; + + if let PreUpgradeFail = kind { + return Some(Err("Some pre-upgrade error".into())) + } + + Some(Ok(vec![])) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade( + n: u32, + _state: Vec, + ) -> Option> { + let (kind, _) = MIGRATIONS::get()[n as usize]; + + if let PostUpgradeFail = kind { + return Some(Err("Some post-upgrade error".into())) + } + + Some(Ok(())) + } + fn cursor_max_encoded_len() -> usize { 65_536 } diff --git a/substrate/frame/migrations/src/tests.rs b/substrate/frame/migrations/src/tests.rs index 73ca2a9a09c..55f212bcf37 100644 --- a/substrate/frame/migrations/src/tests.rs +++ b/substrate/frame/migrations/src/tests.rs @@ -17,12 +17,13 @@ #![cfg(test)] +use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; + use crate::{ mock::{Test as T, *}, mock_helpers::{MockedMigrationKind::*, *}, Cursor, Event, FailedMigrationHandling, MigrationCursor, }; -use frame_support::{pallet_prelude::Weight, traits::OnRuntimeUpgrade}; #[docify::export] #[test] @@ -86,6 +87,7 @@ fn simple_multiple_works() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn failing_migration_sets_cursor_to_stuck() { test_closure(|| { FailedUpgradeResponse::set(FailedMigrationHandling::KeepStuck); @@ -116,6 +118,7 @@ fn failing_migration_sets_cursor_to_stuck() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn failing_migration_force_unstuck_works() { test_closure(|| { FailedUpgradeResponse::set(FailedMigrationHandling::ForceUnstuck); @@ -148,6 +151,7 @@ fn failing_migration_force_unstuck_works() { /// A migration that reports not getting enough weight errors if it is the first one to run in that /// block. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_singular_fails() { test_closure(|| { MockedMigrations::set(vec![(HighWeightAfter(Weight::zero()), 2)]); @@ -176,6 +180,7 @@ fn high_weight_migration_singular_fails() { /// A migration that reports of not getting enough weight is retried once, if it is not the first /// one to run in a block. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_retries_once() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::zero()), 0)]); @@ -205,6 +210,7 @@ fn high_weight_migration_retries_once() { // Note: Same as `high_weight_migration_retries_once` but with different required weight for the // migration. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn high_weight_migration_permanently_overweight_fails() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 0), (HighWeightAfter(Weight::MAX), 0)]); @@ -300,6 +306,7 @@ fn historic_skipping_works() { /// When another upgrade happens while a migration is still running, it should set the cursor to /// stuck. #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn upgrade_fails_when_migration_active() { test_closure(|| { MockedMigrations::set(vec![(SucceedAfter, 10)]); @@ -326,6 +333,7 @@ fn upgrade_fails_when_migration_active() { } #[test] +#[cfg_attr(feature = "try-runtime", should_panic)] fn migration_timeout_errors() { test_closure(|| { MockedMigrations::set(vec![(TimeoutAfter, 3)]); @@ -358,3 +366,91 @@ fn migration_timeout_errors() { assert_eq!(upgrades_started_completed_failed(), (0, 0, 1)); }); } + +#[cfg(feature = "try-runtime")] +#[test] +fn try_runtime_success_case() { + use Event::*; + test_closure(|| { + // Add three migrations, each taking one block longer than the previous. + MockedMigrations::set(vec![(SucceedAfter, 0), (SucceedAfter, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + run_to_block(10); + + // Check that we got all events. + assert_events(vec![ + UpgradeStarted { migrations: 3 }, + MigrationCompleted { index: 0, took: 1 }, + MigrationAdvanced { index: 1, took: 0 }, + MigrationCompleted { index: 1, took: 1 }, + MigrationAdvanced { index: 2, took: 0 }, + MigrationAdvanced { index: 2, took: 1 }, + MigrationCompleted { index: 2, took: 2 }, + UpgradeCompleted, + ]); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_pre_upgrade_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (PreUpgradeFail, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_post_upgrade_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (PostUpgradeFail, 1), (SucceedAfter, 2)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +#[cfg(feature = "try-runtime")] +#[should_panic] +fn try_runtime_migration_failure() { + test_closure(|| { + // Add three migrations, it should fail after the second one. + MockedMigrations::set(vec![(SucceedAfter, 0), (FailAfter, 5), (SucceedAfter, 10)]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + // should panic + run_to_block(10); + }); +} + +#[test] +fn try_runtime_no_migrations() { + test_closure(|| { + MockedMigrations::set(vec![]); + + System::set_block_number(1); + Migrations::on_runtime_upgrade(); + + run_to_block(10); + + assert_eq!(System::events().len(), 0); + }); +} diff --git a/substrate/frame/support/src/migrations.rs b/substrate/frame/support/src/migrations.rs index 0eabf9d0ee1..905d6143e4f 100644 --- a/substrate/frame/support/src/migrations.rs +++ b/substrate/frame/support/src/migrations.rs @@ -529,6 +529,25 @@ pub trait SteppedMigration { }) .map_err(|()| SteppedMigrationError::Failed)? } + + /// Hook for testing that is run before the migration is started. + /// + /// Returns some bytes which are passed into `post_upgrade` after the migration is completed. + /// This is not run for the real migration, so panicking is not an issue here. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok(Vec::new()) + } + + /// Hook for testing that is run after the migration is completed. + /// + /// Should be used to verify the state of the chain after the migration. The `state` parameter + /// is the return value from `pre_upgrade`. This is not run for the real migration, so panicking + /// is not an issue here. + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + Ok(()) + } } /// Error that can occur during a [`SteppedMigration`]. @@ -700,6 +719,19 @@ pub trait SteppedMigrations { meter: &mut WeightMeter, ) -> Option>, SteppedMigrationError>>; + /// Call the pre-upgrade hooks of the `n`th migration. + /// + /// Returns `None` if the index is out of bounds. + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>>; + + /// Call the post-upgrade hooks of the `n`th migration. + /// + /// Returns `None` if the index is out of bounds. + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, _state: Vec) + -> Option>; + /// The maximal encoded length across all cursors. fn cursor_max_encoded_len() -> usize; @@ -763,6 +795,19 @@ impl SteppedMigrations for () { None } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(_n: u32) -> Option, sp_runtime::TryRuntimeError>> { + Some(Ok(Vec::new())) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade( + _n: u32, + _state: Vec, + ) -> Option> { + Some(Ok(())) + } + fn cursor_max_encoded_len() -> usize { 0 } @@ -792,11 +837,11 @@ impl SteppedMigrations for T { } fn nth_step( - _n: u32, + n: u32, cursor: Option>, meter: &mut WeightMeter, ) -> Option>, SteppedMigrationError>> { - if !_n.is_zero() { + if !n.is_zero() { defensive!("nth_step should only be called with n==0"); return None } @@ -835,6 +880,23 @@ impl SteppedMigrations for T { ) } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + if n != 0 { + defensive!("nth_pre_upgrade should only be called with n==0"); + } + + Some(T::pre_upgrade()) + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, state: Vec) -> Option> { + if n != 0 { + defensive!("nth_post_upgrade should only be called with n==0"); + } + Some(T::post_upgrade(state)) + } + fn cursor_max_encoded_len() -> usize { T::Cursor::max_encoded_len() } @@ -900,6 +962,36 @@ impl SteppedMigrations for Tuple { None } + #[cfg(feature = "try-runtime")] + fn nth_pre_upgrade(n: u32) -> Option, sp_runtime::TryRuntimeError>> { + let mut i = 0; + + for_tuples! ( #( + if (i + Tuple::len()) > n { + return Tuple::nth_pre_upgrade(n - i) + } + + i += Tuple::len(); + )* ); + + None + } + + #[cfg(feature = "try-runtime")] + fn nth_post_upgrade(n: u32, state: Vec) -> Option> { + let mut i = 0; + + for_tuples! ( #( + if (i + Tuple::len()) > n { + return Tuple::nth_post_upgrade(n - i, state) + } + + i += Tuple::len(); + )* ); + + None + } + fn nth_max_steps(n: u32) -> Option> { let mut i = 0; diff --git a/substrate/frame/support/src/traits/try_runtime/mod.rs b/substrate/frame/support/src/traits/try_runtime/mod.rs index 09c33c01440..284ba3d7422 100644 --- a/substrate/frame/support/src/traits/try_runtime/mod.rs +++ b/substrate/frame/support/src/traits/try_runtime/mod.rs @@ -28,7 +28,7 @@ use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_runtime::TryRuntimeError; /// Which state tests to execute. -#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo)] +#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo, PartialEq)] pub enum Select { /// None of them. None, @@ -95,7 +95,7 @@ impl std::str::FromStr for Select { } /// Select which checks should be run when trying a runtime upgrade upgrade. -#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo)] +#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo, PartialEq)] pub enum UpgradeCheckSelect { /// Run no checks. None, -- GitLab From b5ac7a9d59298eddcd0b6e9470afed7cc9e403d4 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 25 Sep 2024 17:38:48 +0300 Subject: [PATCH 008/127] xcm-executor: validate destinations for ReserveWithdraw and Teleport transfers (#5660) This change adds the required validation for stronger UX guarantees when using `InitiateReserveWithdraw` or `InitiateTeleport` XCM instructions. Execution of the instructions will fail if the local chain is not configured to trust the "destination" or "reserve" chain as a reserve/trusted-teleporter for the provided "assets". With this change, misuse of `InitiateReserveWithdraw`/`InitiateTeleport` fails on origin with no overall side-effects, rather than failing on destination (with side-effects to origin's assets issuance). The commit also makes the same validations for pallet-xcm transfers, and adds regression tests. --------- Signed-off-by: Adrian Catangiu Co-authored-by: Branislav Kontur --- .../tests/assets/asset-hub-rococo/src/lib.rs | 1 + .../src/tests/hybrid_transfers.rs | 11 +++- .../src/tests/reserve_transfer.rs | 55 +++++++++++++++++++ .../asset-hub-rococo/src/tests/teleport.rs | 51 +++++++++++++++++ .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/hybrid_transfers.rs | 11 +++- .../src/tests/reserve_transfer.rs | 55 +++++++++++++++++++ .../asset-hub-westend/src/tests/teleport.rs | 51 +++++++++++++++++ .../bridges/bridge-hub-rococo/src/lib.rs | 1 + .../src/tests/asset_transfers.rs | 10 ++++ .../bridges/bridge-hub-westend/src/lib.rs | 6 +- .../src/tests/asset_transfers.rs | 10 ++++ polkadot/xcm/pallet-xcm/src/lib.rs | 20 +++++++ polkadot/xcm/xcm-builder/tests/scenarios.rs | 20 ++----- polkadot/xcm/xcm-executor/src/lib.rs | 23 ++++++-- .../example/src/relay_chain/xcm_config/mod.rs | 3 +- .../src/relay_chain/xcm_config/teleporter.rs | 26 +++++++++ prdoc/pr_5660.prdoc | 30 ++++++++++ 18 files changed, 360 insertions(+), 25 deletions(-) create mode 100644 polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs create mode 100644 prdoc/pr_5660.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index f4fe1478f3e..0e43108a417 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -51,6 +51,7 @@ mod imports { pub use rococo_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ asset_hub_rococo_runtime::{ + self, xcm_config::{ self as ahr_xcm_config, TokenLocation as RelayLocation, XcmConfig as AssetHubRococoXcmConfig, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index 7ff6d6c193c..7bb25d7cec6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -449,7 +449,16 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah); let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000; - // Configure destination chain to trust AH as reserve of WND + // Configure source and destination chains to trust AH as reserve of WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Westend)]).encode(), + )], + )); + }); PenpalB::execute_with(|| { assert_ok!(::System::set_storage( ::RuntimeOrigin::root(), 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 faff5f7660c..8aad4b392b2 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 @@ -1548,3 +1548,58 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +/// Reserve Withdraw Native Asset from AssetHub to Parachain fails. +#[test] +fn reserve_withdraw_from_untrusted_reserve_fails() { + // Init values for Parachain Origin + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubRococo::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::DestinationReserve), + bx!(fee_id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve") + }) + ); + }); + + // this should also fail + AssetHubRococo::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateReserveWithdraw { + assets: Wild(All), + reserve: destination, + xcm: Xcm::<()>::new(), + }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} 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 c8da801a14b..d6cf819118e 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 @@ -527,3 +527,54 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { system_para_to_para_transfer_assets, ); } + +/// Teleport Native Asset from AssetHub to Parachain fails. +#[test] +fn teleport_to_untrusted_chain_fails() { + // Init values for Parachain Origin + let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + let roc_to_send: Balance = ROCOCO_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubRococo::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::Teleport), + bx!(fee_id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); + + // this should also fail + AssetHubRococo::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index f568fb4101d..d0016b5a1b1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -48,6 +48,7 @@ mod imports { pub use westend_system_emulated_network::{ asset_hub_westend_emulated_chain::{ asset_hub_westend_runtime::{ + self, xcm_config::{ self as ahw_xcm_config, WestendLocation as RelayLocation, XcmConfig as AssetHubWestendXcmConfig, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index 975bacea7b4..4d6cdd9a94d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -450,7 +450,16 @@ fn transfer_foreign_assets_from_para_to_para_through_asset_hub() { let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah); let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000; - // Configure destination chain to trust AH as reserve of ROC + // Configure source and destination chains to trust AH as reserve of ROC + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Rococo)]).encode(), + )], + )); + }); PenpalB::execute_with(|| { assert_ok!(::System::set_storage( ::RuntimeOrigin::root(), 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 53b6939298d..0100e8e34ef 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 @@ -1552,3 +1552,58 @@ fn reserve_transfer_usdt_from_para_to_para_through_asset_hub() { // Receiver's balance is increased assert!(receiver_assets_after > receiver_assets_before); } + +/// Reserve Withdraw Native Asset from AssetHub to Parachain fails. +#[test] +fn reserve_withdraw_from_untrusted_reserve_fails() { + // Init values for Parachain Origin + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + let roc_to_send: Balance = WESTEND_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubWestend::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::DestinationReserve), + bx!(fee_id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve") + }) + ); + }); + + // this should also fail + AssetHubWestend::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateReserveWithdraw { + assets: Wild(All), + reserve: destination, + xcm: Xcm::<()>::new(), + }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} 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 15d39858acc..ddb82a954a8 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 @@ -530,3 +530,54 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { system_para_to_para_transfer_assets, ); } + +/// Teleport Native Asset from AssetHub to Parachain fails. +#[test] +fn teleport_to_untrusted_chain_fails() { + // Init values for Parachain Origin + let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id()); + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + let roc_to_send: Balance = WESTEND_ED * 10000; + let roc_location = RelayLocation::get(); + + // Assets to send + let assets: Vec = vec![(roc_location.clone(), roc_to_send).into()]; + let fee_id: AssetId = roc_location.into(); + + // this should fail + AssetHubWestend::execute_with(|| { + let result = ::PolkadotXcm::transfer_assets_using_type_and_then( + signed_origin.clone(), + bx!(destination.clone().into()), + bx!(assets.clone().into()), + bx!(TransferType::Teleport), + bx!(fee_id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(Xcm::<()>::new())), + Unlimited, + ); + assert_err!( + result, + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [2, 0, 0, 0], + message: Some("Filtered") + }) + ); + }); + + // this should also fail + AssetHubWestend::execute_with(|| { + let xcm: Xcm = Xcm(vec![ + WithdrawAsset(assets.into()), + InitiateTeleport { assets: Wild(All), dest: destination, xcm: Xcm::<()>::new() }, + ]); + let result = ::PolkadotXcm::execute( + signed_origin, + bx!(xcm::VersionedXcm::V4(xcm)), + Weight::MAX, + ); + assert!(result.is_err()); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 9d54d431e83..e83b2807678 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -16,6 +16,7 @@ #[cfg(test)] mod imports { // Substrate + pub use codec::Encode; pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; pub use sp_runtime::DispatchError; 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 6df51c5f704..11930798da4 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 @@ -432,6 +432,16 @@ fn send_back_wnds_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_weste ASSET_MIN_BALANCE, vec![(sender.clone(), amount * 2)], ); + // Configure source Penpal chain to trust local AH as reserve of bridged WND + PenpalA::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + wnd_at_rococo_parachains.encode(), + )], + )); + }); // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 5e0462d1488..d9b92cb11e9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -16,6 +16,7 @@ #[cfg(test)] mod imports { // Substrate + pub use codec::Encode; pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; pub use sp_runtime::DispatchError; @@ -52,7 +53,10 @@ mod imports { BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ - penpal_runtime::xcm_config::UniversalLocation as PenpalUniversalLocation, + penpal_runtime::xcm_config::{ + CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, + UniversalLocation as PenpalUniversalLocation, + }, PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet, }, westend_emulated_chain::{ 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 c3f81175da2..6492c520234 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 @@ -452,6 +452,16 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc ASSET_MIN_BALANCE, vec![(sender.clone(), amount * 2)], ); + // Configure source Penpal chain to trust local AH as reserve of bridged ROC + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + roc_at_westend_parachains.encode(), + )], + )); + }); // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 05d9046ab19..ee448c3df60 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1939,6 +1939,10 @@ impl Pallet { ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { let value = (origin, vec![fees.clone()]); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + ensure!( + ::IsReserve::contains(&fees, &dest), + Error::::InvalidAssetUnsupportedReserve + ); let context = T::UniversalLocation::get(); let reanchored_fees = fees @@ -1973,6 +1977,12 @@ impl Pallet { let value = (origin, assets); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + for asset in assets.iter() { + ensure!( + ::IsReserve::contains(&asset, &dest), + Error::::InvalidAssetUnsupportedReserve + ); + } // max assets is `assets` (+ potentially separately handled fee) let max_assets = @@ -2079,6 +2089,10 @@ impl Pallet { ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { let value = (origin, vec![fees.clone()]); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); + ensure!( + ::IsTeleporter::contains(&fees, &dest), + Error::::Filtered + ); let context = T::UniversalLocation::get(); let reanchored_fees = fees @@ -2134,6 +2148,12 @@ impl Pallet { let value = (origin, assets); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + for asset in assets.iter() { + ensure!( + ::IsTeleporter::contains(&asset, &dest), + Error::::Filtered + ); + } // max assets is `assets` (+ potentially separately handled fee) let max_assets = diff --git a/polkadot/xcm/xcm-builder/tests/scenarios.rs b/polkadot/xcm/xcm-builder/tests/scenarios.rs index ee1aeffbb4e..99c14f5bba1 100644 --- a/polkadot/xcm/xcm-builder/tests/scenarios.rs +++ b/polkadot/xcm/xcm-builder/tests/scenarios.rs @@ -22,7 +22,7 @@ use mock::{ }; use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::traits::AccountIdConversion; -use xcm::latest::prelude::*; +use xcm::latest::{prelude::*, Error::UntrustedTeleportLocation}; use xcm_executor::XcmExecutor; pub const ALICE: AccountId = AccountId::new([0u8; 32]); @@ -217,7 +217,7 @@ fn teleport_to_asset_hub_works() { ]; let weight = BaseXcmWeight::get() * 3; - // teleports are allowed to community chains, even in the absence of trust from their side. + // teleports are not allowed to other chains, in the absence of trust from their side let message = Xcm(vec![ WithdrawAsset((Here, amount).into()), buy_execution(), @@ -235,16 +235,7 @@ fn teleport_to_asset_hub_works() { weight, Weight::zero(), ); - assert_eq!(r, Outcome::Complete { used: weight }); - let expected_msg = Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin] - .into_iter() - .chain(teleport_effects.clone().into_iter()) - .collect()); - let expected_hash = fake_message_hash(&expected_msg); - assert_eq!( - mock::sent_xcm(), - vec![(Parachain(other_para_id).into(), expected_msg, expected_hash,)] - ); + assert_eq!(r, Outcome::Incomplete { used: weight, error: UntrustedTeleportLocation }); // teleports are allowed from asset hub to kusama. let message = Xcm(vec![ @@ -274,10 +265,7 @@ fn teleport_to_asset_hub_works() { let expected_hash = fake_message_hash(&expected_msg); assert_eq!( mock::sent_xcm(), - vec![ - (Parachain(other_para_id).into(), expected_msg.clone(), expected_hash,), - (Parachain(asset_hub_id).into(), expected_msg, expected_hash,) - ] + vec![(Parachain(asset_hub_id).into(), expected_msg, expected_hash,)] ); }); } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index a8110ca3d19..14920f36445 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -1002,13 +1002,18 @@ impl XcmExecutor { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { + let assets = self.holding.saturating_take(assets); + // Must ensure that we recognise the assets as being managed by the destination. + #[cfg(not(feature = "runtime-benchmarks"))] + for asset in assets.assets_iter() { + ensure!( + Config::IsReserve::contains(&asset, &reserve), + XcmError::UntrustedReserveLocation + ); + } // Note that here we are able to place any assets which could not be reanchored // back into Holding. - let assets = Self::reanchored( - self.holding.saturating_take(assets), - &reserve, - Some(&mut self.holding), - ); + let assets = Self::reanchored(assets, &reserve, Some(&mut self.holding)); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; @@ -1024,6 +1029,14 @@ impl XcmExecutor { let result = (|| -> Result<(), XcmError> { // We must do this first in order to resolve wildcards. let assets = self.holding.saturating_take(assets); + // Must ensure that we have teleport trust with destination for these assets. + #[cfg(not(feature = "runtime-benchmarks"))] + for asset in assets.assets_iter() { + ensure!( + Config::IsTeleporter::contains(&asset, &dest), + XcmError::UntrustedTeleportLocation + ); + } for asset in assets.assets_iter() { // We should check that the asset can actually be teleported out (for this // to be in error, there would need to be an accounting violation by diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs index c5d5fa66732..6218915cd12 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/mod.rs @@ -19,6 +19,7 @@ pub mod barrier; pub mod constants; pub mod location_converter; pub mod origin_converter; +pub mod teleporter; pub mod weigher; use crate::relay_chain::{RuntimeCall, XcmPallet}; @@ -36,7 +37,7 @@ impl Config for XcmConfig { type AssetTransactor = asset_transactor::AssetTransactor; type OriginConverter = origin_converter::OriginConverter; type IsReserve = (); - type IsTeleporter = (); + type IsTeleporter = teleporter::TrustedTeleporters; type UniversalLocation = constants::UniversalLocation; type Barrier = barrier::Barrier; type Weigher = weigher::Weigher; diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs new file mode 100644 index 00000000000..92e5065044e --- /dev/null +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain/xcm_config/teleporter.rs @@ -0,0 +1,26 @@ +// 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 frame_support::parameter_types; +use xcm::latest::prelude::*; + +parameter_types! { + pub NftCollectionOnRelay: AssetFilter + = Wild(AllOf { fun: WildNonFungible, id: AssetId(GeneralIndex(1).into()) }); + pub NftCollectionForChild: (AssetFilter, Location) + = (NftCollectionOnRelay::get(), Parachain(1).into()); +} +pub type TrustedTeleporters = xcm_builder::Case; diff --git a/prdoc/pr_5660.prdoc b/prdoc/pr_5660.prdoc new file mode 100644 index 00000000000..fce791cebb6 --- /dev/null +++ b/prdoc/pr_5660.prdoc @@ -0,0 +1,30 @@ +# 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: "xcm-executor: validate destinations for ReserveWithdraw and Teleport transfers" + +doc: + - audience: + - Runtime User + - Runtime Dev + description: | + This change adds the required validation for stronger UX guarantees when using + `InitiateReserveWithdraw` or `InitiateTeleport` XCM instructions. Execution of + the instructions will fail if the local chain is not configured to trust the + "destination" or "reserve" chain as a reserve/trusted-teleporter for the provided + "assets". + With this change, misuse of `InitiateReserveWithdraw`/`InitiateTeleport` fails on + origin with no overall side-effects, rather than failing on destination (with + side-effects to origin's assets issuance). + The commit also makes the same validations for pallet-xcm transfers, and adds + regression tests. + +crates: + - name: staging-xcm-executor + bump: patch + - name: staging-xcm-builder + bump: patch + - name: pallet-xcm + bump: patch + - name: xcm-simulator-example + bump: patch -- GitLab From c77095f51119d2eccdc54d2f3518bed0ffbd6d53 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 25 Sep 2024 18:28:59 +0200 Subject: [PATCH 009/127] [pallet-revive] last call return data API (#5779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces 2 new syscalls: `return_data_size` and `return_data_copy`, resembling the semantics of the EVM `RETURNDATASIZE` and `RETURNDATACOPY` opcodes. The ownership of `ExecReturnValue` (the return data) has moved to the `Frame`. This allows implementing the new contract API functionality in ext with no additional copies. Returned data is passed via contract memory, memory is (will be) metered, hence the amount of returned data can not be statically known, so we should avoid storing copies of the returned data if we can. By moving the ownership of the exectuables return value into the `Frame` struct we achieve this. A zero-copy implementation of those APIs would be technically possible without that internal change by making the callsite in the runtime responsible for moving the returned data into the frame after any call. However, resetting the stored output needs to be handled in ext, since plain transfers will _not_ affect the stored return data (and we don't want to handle this special call case inside the `runtime` API). This has drawbacks: - It can not be tested easily in the mock. - It introduces an inconsistency where resetting the stored output is handled in ext, but the runtime API is responsible to store it back correctly after any calls made. Instead, with ownership of the data in `Frame`, both can be handled in a single place. Handling both in `fn run()` is more natural and leaves less room for runtime API bugs. The returned output is reset each time _before_ running any executable in a nested stack. This change should not incur any overhead to the overall memory usage as _only_ the returned data from the last executed frame will be kept around at any time. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: PG Herveou --- prdoc/pr_5779.prdoc | 38 ++ .../fixtures/contracts/return_data_api.rs | 166 +++++++++ substrate/frame/revive/src/exec.rs | 348 +++++++++++++----- substrate/frame/revive/src/tests.rs | 29 ++ substrate/frame/revive/src/wasm/runtime.rs | 144 +++++--- substrate/frame/revive/uapi/src/host.rs | 14 + .../frame/revive/uapi/src/host/riscv32.rs | 14 + 7 files changed, 619 insertions(+), 134 deletions(-) create mode 100644 prdoc/pr_5779.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/return_data_api.rs diff --git a/prdoc/pr_5779.prdoc b/prdoc/pr_5779.prdoc new file mode 100644 index 00000000000..659a3a19f69 --- /dev/null +++ b/prdoc/pr_5779.prdoc @@ -0,0 +1,38 @@ +title: "[pallet-revive] last call return data API" + +doc: + - audience: Runtime Dev + description: | + This PR introduces 2 new syscall: `return_data_size` and `return_data_copy`, + resembling the semantics of the EVM `RETURNDATASIZE` and `RETURNDATACOPY` opcodes. + + The ownership of `ExecReturnValue` (the return data) has moved to the `Frame`. + This allows implementing the new contract API surface functionality in ext with no additional copies. + Returned data is passed via contract memory, memory is (will be) metered, + hence the amount of returned data can not be statically known, + so we should avoid storing copies of the returned data if we can. + By moving the ownership of the exectuables return value into the `Frame` struct we achieve this. + + A zero-copy implementation of those APIs would be technically possible without that internal change by making + the callsite in the runtime responsible for moving the returned data into the frame after any call. + However, resetting the stored output needs to be handled in ext, since plain transfers will _not_ affect the + stored return data (and we don't want to handle this special call case inside the `runtime` API). + This has drawbacks: + - It can not be tested easily in the mock. + - It introduces an inconsistency where resetting the stored output is handled in ext, + but the runtime API is responsible to store it back correctly after any calls made. + Instead, with ownership of the data in `Frame`, both can be handled in a single place. + Handling both in `fn run()` is more natural and leaves less room for runtime API bugs. + + The returned output is reset each time _before_ running any executable in a nested stack. + This change should not incur any overhead to the overall memory usage as _only_ the returned data from the last + executed frame will be kept around at any time. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-uapi + bump: minor + \ No newline at end of file diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs new file mode 100644 index 00000000000..846396b0944 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -0,0 +1,166 @@ +// 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 tests that the `return_data_size` and `return_data_copy` APIs work. +//! +//! It does so by calling and instantiating the "return_with_data" fixture, +//! which always echoes back the input[4..] regardless of the call outcome. +//! +//! We also check that the saved return data is properly reset after a trap +//! and unaffected by plain transfers. + +#![no_std] +#![no_main] + +use common::{input, u256_bytes}; +use uapi::{HostFn, HostFnImpl as api}; + +const INPUT_BUF_SIZE: usize = 128; +static INPUT_DATA: [u8; INPUT_BUF_SIZE] = [0xFF; INPUT_BUF_SIZE]; +/// The "return_with_data" fixture echoes back 4 bytes less than the input +const OUTPUT_BUF_SIZE: usize = INPUT_BUF_SIZE - 4; +static OUTPUT_DATA: [u8; OUTPUT_BUF_SIZE] = [0xEE; OUTPUT_BUF_SIZE]; + +fn assert_return_data_after_call(input: &[u8]) { + assert_return_data_size_of(OUTPUT_BUF_SIZE as u64); + assert_plain_transfer_does_not_reset(OUTPUT_BUF_SIZE as u64); + assert_return_data_copy(&input[4..]); + reset_return_data(); +} + +/// Assert that what we get from [api::return_data_copy] matches `whole_return_data`, +/// either fully or partially with an offset and limited size. +fn assert_return_data_copy(whole_return_data: &[u8]) { + // The full return data should match + let mut buf = OUTPUT_DATA; + let mut full = &mut buf[..whole_return_data.len()]; + api::return_data_copy(&mut full, 0); + assert_eq!(whole_return_data, full); + + // Partial return data should match + let mut buf = OUTPUT_DATA; + let offset = 5; // we just pick some offset + let size = 32; // we just pick some size + let mut partial = &mut buf[offset..offset + size]; + api::return_data_copy(&mut partial, offset as u32); + assert_eq!(*partial, whole_return_data[offset..offset + size]); +} + +/// This function panics in a recursive contract call context. +fn recursion_guard() -> [u8; 20] { + let mut caller_address = [0u8; 20]; + api::caller(&mut caller_address); + + let mut own_address = [0u8; 20]; + api::address(&mut own_address); + + assert_ne!(caller_address, own_address); + + own_address +} + +/// Call ourselves recursively, which panics the callee and thus resets the return data. +fn reset_return_data() { + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &recursion_guard(), + 0u64, + 0u64, + None, + &[0u8; 32], + &[0u8; 32], + None, + ) + .unwrap_err(); + assert_return_data_size_of(0); +} + +/// Assert [api::return_data_size] to match the `expected` value. +fn assert_return_data_size_of(expected: u64) { + let mut return_data_size = [0xff; 32]; + api::return_data_size(&mut return_data_size); + assert_eq!(return_data_size, u256_bytes(expected)); +} + +/// Assert [api::return_data_size] to match the `expected` value after a plain transfer +/// (plain transfers don't issue a call and so should not reset the return data) +fn assert_plain_transfer_does_not_reset(expected: u64) { + api::transfer(&[0; 20], &u256_bytes(128)).unwrap(); + assert_return_data_size_of(expected); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(code_hash: &[u8; 32],); + + // We didn't do anything yet; return data size should be 0 + assert_return_data_size_of(0); + + recursion_guard(); + + let mut address_buf = [0; 20]; + let construct_input = |exit_flag| { + let mut input = INPUT_DATA; + input[0] = exit_flag; + input[9] = 7; + input[17 / 2] = 127; + input[89 / 2] = 127; + input + }; + let mut instantiate = |exit_flag| { + api::instantiate( + code_hash, + 0u64, + 0u64, + None, + &[0; 32], + &construct_input(exit_flag), + Some(&mut address_buf), + None, + None, + ) + }; + let call = |exit_flag, address_buf| { + api::call( + uapi::CallFlags::empty(), + address_buf, + 0u64, + 0u64, + None, + &[0; 32], + &construct_input(exit_flag), + None, + ) + }; + + instantiate(0).unwrap(); + assert_return_data_after_call(&construct_input(0)[..]); + + instantiate(1).unwrap_err(); + assert_return_data_after_call(&construct_input(1)[..]); + + call(0, &address_buf).unwrap(); + assert_return_data_after_call(&construct_input(0)[..]); + + call(1, &address_buf).unwrap_err(); + assert_return_data_after_call(&construct_input(1)[..]); +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 233658696c8..2e48bab2925 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -189,16 +189,12 @@ pub trait Ext: sealing::Sealed { input_data: Vec, allows_reentry: bool, read_only: bool, - ) -> Result; + ) -> Result<(), ExecError>; /// Execute code in the current frame. /// /// Returns the code size of the called contract. - fn delegate_call( - &mut self, - code: H256, - input_data: Vec, - ) -> Result; + fn delegate_call(&mut self, code: H256, input_data: Vec) -> Result<(), ExecError>; /// Instantiate a contract from the given code. /// @@ -213,7 +209,7 @@ pub trait Ext: sealing::Sealed { value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - ) -> Result<(H160, ExecReturnValue), ExecError>; + ) -> Result; /// Transfer all funds to `beneficiary` and delete the contract. /// @@ -427,6 +423,12 @@ pub trait Ext: sealing::Sealed { /// Check if running in read-only context. fn is_read_only(&self) -> bool; + + /// Returns an immutable reference to the output of the last executed call frame. + fn last_frame_output(&self) -> &ExecReturnValue; + + /// Returns a mutable reference to the output of the last executed call frame. + fn last_frame_output_mut(&mut self) -> &mut ExecReturnValue; } /// Describes the different functions that can be exported by an [`Executable`]. @@ -547,6 +549,8 @@ struct Frame { read_only: bool, /// The caller of the currently executing frame which was spawned by `delegate_call`. delegate_caller: Option>, + /// The output of the last executed call frame. + last_frame_output: ExecReturnValue, } /// Used in a delegate call frame arguments in order to override the executable and caller. @@ -731,7 +735,7 @@ where value, debug_message, )? { - stack.run(executable, input_data) + stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { Self::transfer_no_contract(&origin, &dest, value) } @@ -772,7 +776,9 @@ where )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); - stack.run(executable, input_data).map(|ret| (address, ret)) + stack + .run(executable, input_data) + .map(|_| (address, stack.first_frame.last_frame_output)) } #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] @@ -865,7 +871,7 @@ where { contract } else { - return Ok(None) + return Ok(None); } }; @@ -911,6 +917,7 @@ where nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, read_only, + last_frame_output: Default::default(), }; Ok(Some((frame, executable))) @@ -965,12 +972,24 @@ where /// Run the current (top) frame. /// /// This can be either a call or an instantiate. - fn run(&mut self, executable: E, input_data: Vec) -> ExecResult { + fn run(&mut self, executable: E, input_data: Vec) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; let delegated_code_hash = if frame.delegate_caller.is_some() { Some(*executable.code_hash()) } else { None }; + // The output of the caller frame will be replaced by the output of this run. + // It is also not accessible from nested frames. + // Hence we drop it early to save the memory. + let frames_len = self.frames.len(); + if let Some(caller_frame) = match frames_len { + 0 => None, + 1 => Some(&mut self.first_frame.last_frame_output), + _ => self.frames.get_mut(frames_len - 2).map(|frame| &mut frame.last_frame_output), + } { + *caller_frame = Default::default(); + } + self.transient_storage.start_transaction(); let do_transaction = || { @@ -1109,7 +1128,9 @@ where } self.pop_frame(success); - output + output.map(|output| { + self.top_frame_mut().last_frame_output = output; + }) } /// Remove the current (top) frame from the stack. @@ -1285,7 +1306,7 @@ where input_data: Vec, allows_reentry: bool, read_only: bool, - ) -> ExecResult { + ) -> Result<(), ExecError> { // Before pushing the new frame: Protect the caller contract against reentrancy attacks. // It is important to do this before calling `allows_reentry` so that a direct recursion // is caught by it. @@ -1323,7 +1344,8 @@ where &Origin::from_account_id(self.account_id().clone()), &dest, value, - ) + )?; + Ok(()) } }; @@ -1336,11 +1358,7 @@ where result } - fn delegate_call( - &mut self, - code_hash: H256, - input_data: Vec, - ) -> Result { + fn delegate_call(&mut self, code_hash: H256, input_data: Vec) -> Result<(), ExecError> { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); @@ -1368,7 +1386,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, - ) -> Result<(H160, ExecReturnValue), ExecError> { + ) -> Result { let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let sender = &self.top_frame().account_id; let executable = self.push_frame( @@ -1385,7 +1403,7 @@ where )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) - .map(|ret| (address, ret)) + .map(|_| address) } fn terminate(&mut self, beneficiary: &H160) -> DispatchResult { @@ -1690,6 +1708,14 @@ where fn is_read_only(&self) -> bool { self.top_frame().read_only } + + fn last_frame_output(&self) -> &ExecReturnValue { + &self.top_frame().last_frame_output + } + + fn last_frame_output_mut(&mut self) -> &mut ExecReturnValue { + &mut self.top_frame_mut().last_frame_output + } } mod sealing { @@ -2353,15 +2379,17 @@ mod tests { // ALICE is the origin of the call stack assert!(ctx.ext.caller_is_origin()); // BOB calls CHARLIE - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -2447,15 +2475,17 @@ mod tests { // root is the origin of the call stack. assert!(ctx.ext.caller_is_root()); // BOB calls CHARLIE. - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -2666,6 +2696,7 @@ mod tests { vec![], Some(&[48; 32]), ) + .map(|address| (address, ctx.ext.last_frame_output().clone())) .unwrap(); *instantiated_contract_address.borrow_mut() = Some(address); @@ -2841,15 +2872,17 @@ mod tests { assert_eq!(info.storage_byte_deposit, 0); info.storage_byte_deposit = 42; assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); @@ -3095,6 +3128,7 @@ mod tests { let dest = H160::from_slice(ctx.input_data.as_ref()); ctx.ext .call(Weight::zero(), U256::zero(), &dest, U256::zero(), vec![], false, false) + .map(|_| ctx.ext.last_frame_output().clone()) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -3137,15 +3171,17 @@ mod tests { fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - false, - false, - ) + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + false, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()) } else { exec_success() } @@ -3153,15 +3189,9 @@ mod tests { // call BOB with input set to '1' let code_charlie = MockLoader::insert(Call, |ctx, _| { - ctx.ext.call( - Weight::zero(), - U256::zero(), - &BOB_ADDR, - U256::zero(), - vec![1], - true, - false, - ) + ctx.ext + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![1], true, false) + .map(|_| ctx.ext.last_frame_output().clone()) }); ExtBuilder::default().build().execute_with(|| { @@ -3360,7 +3390,7 @@ mod tests { let alice_nonce = System::account_nonce(&ALICE); assert_eq!(System::account_nonce(ctx.ext.account_id()), 0); assert_eq!(ctx.ext.caller().account_id().unwrap(), &ALICE); - let (addr, _) = ctx + let addr = ctx .ext .instantiate( Weight::zero(), @@ -3916,15 +3946,17 @@ mod tests { Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false, - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_success() ); assert_eq!(ctx.ext.get_transient_storage(storage_key_1), Some(vec![3])); @@ -4020,15 +4052,17 @@ mod tests { Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.call( - Weight::zero(), - U256::zero(), - &CHARLIE_ADDR, - U256::zero(), - vec![], - true, - false - ), + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false + ) + .map(|_| ctx.ext.last_frame_output().clone()), exec_trapped() ); assert_eq!(ctx.ext.get_transient_storage(storage_key), Some(vec![1, 2])); @@ -4102,4 +4136,148 @@ mod tests { assert_matches!(result, Ok(_)); }); } + + #[test] + fn last_frame_output_works_on_instantiate() { + let ok_ch = MockLoader::insert(Constructor, move |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] }) + }); + let revert_ch = MockLoader::insert(Constructor, move |_, _| { + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] }) + }); + let trap_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into())); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + let value = ::Currency::minimum_balance().into(); + + // Successful instantiation should set the output + let address = ctx + .ext + .instantiate(Weight::zero(), U256::zero(), ok_ch, value, vec![], None) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + // Plain transfers should not set the output + ctx.ext.transfer(&address, U256::from(1)).unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + // Reverted instantiation should set the output + ctx.ext + .instantiate(Weight::zero(), U256::zero(), revert_ch, value, vec![], None) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] } + ); + + // Trapped instantiation should clear the output + ctx.ext + .instantiate(Weight::zero(), U256::zero(), trap_ch, value, vec![], None) + .unwrap_err(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + exec_success() + } + }); + + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB_CONTRACT_ID, 100); + place_contract(&BOB, instantiator_ch); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } + + #[test] + fn last_frame_output_works_on_nested_call() { + // Call stack: BOB -> CHARLIE(revert) -> BOB' (success) + let code_bob = MockLoader::insert(Call, |ctx, _| { + if ctx.input_data.is_empty() { + // We didn't do anything yet + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .unwrap(); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] } + ); + } + + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] }) + }); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // We didn't do anything yet + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![] } + ); + + assert!(ctx + .ext + .call(Weight::zero(), U256::zero(), &BOB_ADDR, U256::zero(), vec![99], true, false) + .is_ok()); + assert_eq!( + ctx.ext.last_frame_output(), + &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } + ); + + Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70] }) + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index d06cdcfd465..5c5d144f24a 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4325,4 +4325,33 @@ mod run_tests { assert_eq!(received.result.data, chain_id.encode()); }); } + + #[test] + fn return_data_api_works() { + let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); + let (code_return_with_data, hash_return_with_data) = + compile_module("return_with_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Upload the io echoing fixture for later use + assert_ok!(Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code_return_with_data, + deposit_limit::(), + )); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_return_data_api)) + .build_and_unwrap_contract(); + + // Call the contract: It will issue calls and deploys, asserting on + assert_ok!(builder::call(addr) + .value(10 * 1024) + .data(hash_return_with_data.encode()) + .build()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index ebc407adacd..4b5a9a04eb7 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -28,7 +28,7 @@ use crate::{ }; use alloc::{boxed::Box, vec, vec::Vec}; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; -use core::{fmt, marker::PhantomData}; +use core::{fmt, marker::PhantomData, mem}; use frame_support::{ dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, traits::Get, weights::Weight, @@ -237,8 +237,8 @@ parameter_types! { const XcmExecutionFailed: ReturnErrorCode = ReturnErrorCode::XcmExecutionFailed; } -impl From for ReturnErrorCode { - fn from(from: ExecReturnValue) -> Self { +impl From<&ExecReturnValue> for ReturnErrorCode { + fn from(from: &ExecReturnValue) -> Self { if from.flags.contains(ReturnFlags::REVERT) { Self::CalleeReverted } else { @@ -769,20 +769,16 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { } } - /// Fallible conversion of a `ExecResult` to `ReturnErrorCode`. - fn exec_into_return_code(from: ExecResult) -> Result { + /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. + fn exec_error_into_return_code(from: ExecError) -> Result { use crate::exec::ErrorOrigin::Callee; - let ExecError { error, origin } = match from { - Ok(retval) => return Ok(retval.into()), - Err(err) => err, - }; - - match (error, origin) { + match (from.error, from.origin) { (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), (err, _) => Self::err_into_return_code(err), } } + fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { let res = match key_len { SENTINEL => { @@ -1036,28 +1032,32 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { }, }; - // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to - // a halt anyways without anymore code being executed. - if flags.contains(CallFlags::TAIL_CALL) { - if let Ok(return_value) = call_outcome { + match call_outcome { + // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to + // a halt anyways without anymore code being executed. + Ok(_) if flags.contains(CallFlags::TAIL_CALL) => { + let output = mem::take(self.ext.last_frame_output_mut()); return Err(TrapReason::Return(ReturnData { - flags: return_value.flags.bits(), - data: return_value.data, + flags: output.flags.bits(), + data: output.data, })); - } - } - - if let Ok(output) = &call_outcome { - self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?; + }, + Ok(_) => { + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( + memory, + output_ptr, + output_len_ptr, + &output.data, + true, + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => Ok(Self::exec_error_into_return_code(err)?), } - Ok(Self::exec_into_return_code(call_outcome)?) } fn instantiate( @@ -1086,34 +1086,40 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { let salt: [u8; 32] = memory.read_array(salt_ptr)?; Some(salt) }; - let instantiate_outcome = self.ext.instantiate( + + match self.ext.instantiate( weight, deposit_limit, code_hash, value, input_data, salt.as_ref(), - ); - if let Ok((address, output)) = &instantiate_outcome { - if !output.flags.contains(ReturnFlags::REVERT) { - self.write_fixed_sandbox_output( + ) { + Ok(address) => { + if !self.ext.last_frame_output().flags.contains(ReturnFlags::REVERT) { + self.write_fixed_sandbox_output( + memory, + address_ptr, + &address.as_bytes(), + true, + already_charged, + )?; + } + let output = mem::take(self.ext.last_frame_output_mut()); + let write_result = self.write_sandbox_output( memory, - address_ptr, - &address.as_bytes(), + output_ptr, + output_len_ptr, + &output.data, true, - already_charged, - )?; - } - self.write_sandbox_output( - memory, - output_ptr, - output_len_ptr, - &output.data, - true, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?; + |len| Some(RuntimeCosts::CopyToContract(len)), + ); + *self.ext.last_frame_output_mut() = output; + write_result?; + Ok(self.ext.last_frame_output().into()) + }, + Err(err) => Ok(Self::exec_error_into_return_code(err)?), } - Ok(Self::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?) } fn terminate(&mut self, memory: &M, beneficiary_ptr: u32) -> Result<(), TrapReason> { @@ -1993,4 +1999,44 @@ pub mod env { self.ext.unlock_delegate_dependency(&code_hash)?; Ok(()) } + + /// Stores the length of the data returned by the last call into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data_size`]. + #[api_version(0)] + fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &as_bytes(U256::from(self.ext.last_frame_output().data.len())), + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + )?) + } + + /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data`]. + #[api_version(0)] + fn return_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + let output = mem::take(self.ext.last_frame_output_mut()); + let result = if offset as usize > output.data.len() { + Err(Error::::OutOfBounds.into()) + } else { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &output.data[offset as usize..], + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + ) + }; + *self.ext.last_frame_output_mut() = output; + Ok(result?) + } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 57a03332670..816fdec3aaa 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -620,6 +620,20 @@ pub trait HostFn: private::Sealed { /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; + + /// Stores the size of the returned data of the last contract call or instantiation. + /// + /// # Parameters + /// + /// - `output`: A reference to the output buffer to write the size. + fn return_data_size(output: &mut [u8; 32]); + + /// Stores the returned data of the last contract call or contract instantiation. + /// + /// # Parameters + /// - `output`: A reference to the output buffer to write the data. + /// - `offset`: Byte offset into the returned data + fn return_data_copy(output: &mut &mut [u8], offset: u32); } mod private { diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index a60c338e8bd..d5ea94c1a91 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -131,6 +131,8 @@ mod sys { msg_len: u32, out_ptr: *mut u8, ) -> ReturnCode; + pub fn return_data_size(out_ptr: *mut u8); + pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32); } } @@ -548,4 +550,16 @@ impl HostFn for HostFnImpl { }; ret_code.into() } + + fn return_data_size(output: &mut [u8; 32]) { + unsafe { sys::return_data_size(output.as_mut_ptr()) }; + } + + fn return_data_copy(output: &mut &mut [u8], offset: u32) { + let mut output_len = output.len() as u32; + { + unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; + } + extract_from_slice(output, output_len as usize); + } } -- GitLab From 1f3e3978c4afd6f9985854a0f4327f8c8c7c71b9 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:37:42 +0300 Subject: [PATCH 010/127] chore: bump runtime api version to v11 (#5824) A change that I missed to add in https://github.com/paritytech/polkadot-sdk/pull/5525 . --------- Signed-off-by: Andrei Sandu --- .../parachains/src/runtime_api_impl/mod.rs | 2 +- .../src/runtime_api_impl/{v10.rs => v11.rs} | 43 +++++++++++++++++- .../src/runtime_api_impl/vstaging.rs | 45 ------------------- polkadot/runtime/rococo/src/lib.rs | 8 ++-- polkadot/runtime/test-runtime/src/lib.rs | 16 +++---- polkadot/runtime/westend/src/lib.rs | 8 ++-- prdoc/pr_5824.prdoc | 17 +++++++ 7 files changed, 72 insertions(+), 67 deletions(-) rename polkadot/runtime/parachains/src/runtime_api_impl/{v10.rs => v11.rs} (92%) create mode 100644 prdoc/pr_5824.prdoc diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs b/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs index ed2e95b3cfa..ad80856e239 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/mod.rs @@ -26,5 +26,5 @@ //! 2. Move methods from `vstaging` to `v3`. The new stable version should include all methods from //! `vstaging` tagged with the new version number (e.g. all `v3` methods). -pub mod v10; +pub mod v11; pub mod vstaging; diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs similarity index 92% rename from polkadot/runtime/parachains/src/runtime_api_impl/v10.rs rename to polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index ead825b38f0..51e33001609 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -14,7 +14,7 @@ //! A module exporting runtime API implementation functions for all runtime APIs using `v5` //! primitives. //! -//! Runtimes implementing the v10 runtime API are recommended to forward directly to these +//! Runtimes implementing the v11 runtime API are recommended to forward directly to these //! functions. use crate::{ @@ -22,7 +22,11 @@ use crate::{ scheduler::{self, CoreOccupied}, session_info, shared, }; -use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; +use alloc::{ + collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + vec, + vec::Vec, +}; use frame_support::traits::{GetStorageVersion, StorageVersion}; use frame_system::pallet_prelude::*; use polkadot_primitives::{ @@ -547,3 +551,38 @@ pub fn node_features() -> NodeFeatures { pub fn approval_voting_params() -> ApprovalVotingParams { configuration::ActiveConfig::::get().approval_voting_params } + +/// Returns the claimqueue from the scheduler +pub fn claim_queue() -> BTreeMap> { + let now = >::block_number() + One::one(); + + // This is needed so that the claim queue always has the right size (equal to + // scheduling_lookahead). Otherwise, if a candidate is backed in the same block where the + // previous candidate is included, the claim queue will have already pop()-ed the next item + // from the queue and the length would be `scheduling_lookahead - 1`. + >::free_cores_and_fill_claim_queue(Vec::new(), now); + let config = configuration::ActiveConfig::::get(); + // Extra sanity, config should already never be smaller than 1: + let n_lookahead = config.scheduler_params.lookahead.max(1); + + scheduler::ClaimQueue::::get() + .into_iter() + .map(|(core_index, entries)| { + // on cores timing out internal claim queue size may be temporarily longer than it + // should be as the timed out assignment might got pushed back to an already full claim + // queue: + ( + core_index, + entries.into_iter().map(|e| e.para_id()).take(n_lookahead as usize).collect(), + ) + }) + .collect() +} + +/// Returns all the candidates that are pending availability for a given `ParaId`. +/// Deprecates `candidate_pending_availability` in favor of supporting elastic scaling. +pub fn candidates_pending_availability( + para_id: ParaId, +) -> Vec> { + >::candidates_pending_availability(para_id) +} diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index a3440f686e9..d01b543630c 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,48 +15,3 @@ // along with Polkadot. If not, see . //! Put implementations of functions from staging APIs here. - -use crate::{configuration, inclusion, initializer, scheduler}; -use alloc::{ - collections::{btree_map::BTreeMap, vec_deque::VecDeque}, - vec::Vec, -}; -use polkadot_primitives::{ - vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreIndex, Id as ParaId, -}; -use sp_runtime::traits::One; - -/// Returns the claimqueue from the scheduler -pub fn claim_queue() -> BTreeMap> { - let now = >::block_number() + One::one(); - - // This is needed so that the claim queue always has the right size (equal to - // scheduling_lookahead). Otherwise, if a candidate is backed in the same block where the - // previous candidate is included, the claim queue will have already pop()-ed the next item - // from the queue and the length would be `scheduling_lookahead - 1`. - >::free_cores_and_fill_claim_queue(Vec::new(), now); - let config = configuration::ActiveConfig::::get(); - // Extra sanity, config should already never be smaller than 1: - let n_lookahead = config.scheduler_params.lookahead.max(1); - - scheduler::ClaimQueue::::get() - .into_iter() - .map(|(core_index, entries)| { - // on cores timing out internal claim queue size may be temporarily longer than it - // should be as the timed out assignment might got pushed back to an already full claim - // queue: - ( - core_index, - entries.into_iter().map(|e| e.para_id()).take(n_lookahead as usize).collect(), - ) - }) - .collect() -} - -/// Returns all the candidates that are pending availability for a given `ParaId`. -/// Deprecates `candidate_pending_availability` in favor of supporting elastic scaling. -pub fn candidates_pending_availability( - para_id: ParaId, -) -> Vec> { - >::candidates_pending_availability(para_id) -} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index e5396034a44..5fd219c193c 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -66,9 +66,7 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::{ - v10 as parachains_runtime_api_impl, vstaging as vstaging_parachains_runtime_api_impl, - }, + runtime_api_impl::v11 as parachains_runtime_api_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2057,11 +2055,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + parachains_runtime_api_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + parachains_runtime_api_impl::candidates_pending_availability::(para_id) } } diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index b0323156911..30ef2de3165 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -33,13 +33,11 @@ use pallet_transaction_payment::FungibleAdapter; use polkadot_runtime_parachains::{ assigner_parachains as parachains_assigner_parachains, configuration as parachains_configuration, - configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, - disputes as parachains_disputes, - disputes::slashing as parachains_slashing, - dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, - initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras, - paras_inherent as parachains_paras_inherent, - runtime_api_impl::{v10 as runtime_impl, vstaging as vstaging_parachains_runtime_api_impl}, + configuration::ActiveConfigHrmpChannelSizeAndCapacityRatio, disputes as parachains_disputes, + disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, + inclusion as parachains_inclusion, initializer as parachains_initializer, + origin as parachains_origin, paras as parachains_paras, + paras_inherent as parachains_paras_inherent, runtime_api_impl::v11 as runtime_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1003,11 +1001,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + runtime_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + runtime_impl::candidates_pending_availability::(para_id) } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 6719a037384..d6ed11d0787 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -83,9 +83,7 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::{ - v10 as parachains_runtime_api_impl, vstaging as vstaging_parachains_runtime_api_impl, - }, + runtime_api_impl::v11 as parachains_runtime_api_impl, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2088,11 +2086,11 @@ sp_api::impl_runtime_apis! { } fn claim_queue() -> BTreeMap> { - vstaging_parachains_runtime_api_impl::claim_queue::() + parachains_runtime_api_impl::claim_queue::() } fn candidates_pending_availability(para_id: ParaId) -> Vec> { - vstaging_parachains_runtime_api_impl::candidates_pending_availability::(para_id) + parachains_runtime_api_impl::candidates_pending_availability::(para_id) } } diff --git a/prdoc/pr_5824.prdoc b/prdoc/pr_5824.prdoc new file mode 100644 index 00000000000..136cd6bfee8 --- /dev/null +++ b/prdoc/pr_5824.prdoc @@ -0,0 +1,17 @@ +title: "Bump parachains runtime API to v11" + +doc: + - audience: [ Node Dev, Runtime Dev ] + description: | + This PR promotes all staging methods in v10 to stable and releases v11 stable runtime + APIs. + +crates: + - name: polkadot-runtime-parachains + bump: major + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: polkadot-test-runtime + bump: patch -- GitLab From f6d08e637694729df7cddb166954ee90c2da3ce1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:06:05 +0100 Subject: [PATCH 011/127] add riscv feature to /cmd bench by default (#5828) Closes #5714 --- .github/scripts/cmd/cmd.py | 2 +- .github/scripts/cmd/test_cmd.py | 30 +++++++++++++------------- .github/workflows/runtimes-matrix.json | 13 +++++++++++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index f7dd88df4bd..b5835f1f308 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -100,7 +100,7 @@ def main(): # loop over remaining runtimes to collect available pallets for runtime in runtimesMatrix.values(): - os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features runtime-benchmarks") + os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features={runtime['bench_features']}") print(f'-- listing pallets for benchmark for {runtime["name"]}') wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" output = os.popen( diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index a2f29b075da..f4e70903347 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -7,10 +7,10 @@ import argparse # Mock data for runtimes-matrix.json mock_runtimes_matrix = [ - {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs"}, - {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, - {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs"}, - {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs"}, + {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", "bench_features": "runtime-benchmarks,riscv"}, + {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, + {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, + {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks"}, ] def get_mock_bench_output(runtime, pallets, output_path, header, template = None): @@ -84,10 +84,10 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), call(get_mock_bench_output('kitchensink', 'pallet_balances', './substrate/frame/balances/src/weights.rs', os.path.abspath('substrate/HEADER-APACHE2'), "substrate/.maintain/frame-weight-template.hbs")), call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', os.path.abspath('polkadot/file_header.txt'))), @@ -118,7 +118,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), @@ -149,7 +149,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -185,8 +185,8 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), @@ -220,7 +220,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), # Westend runtime calls call(get_mock_bench_output( 'kitchensink', @@ -254,7 +254,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( 'asset-hub-westend', @@ -288,7 +288,7 @@ class TestCmd(unittest.TestCase): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( 'asset-hub-westend', diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index 102437876da..b868a410a16 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -5,6 +5,7 @@ "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", + "bench_features": "runtime-benchmarks,riscv", "uri": null, "is_relay": false }, @@ -15,6 +16,7 @@ "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-westend.polkadot.io:443", + "bench_features": "runtime-benchmarks", "is_relay": true }, { @@ -24,6 +26,7 @@ "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-rococo.polkadot.io:443", + "bench_features": "runtime-benchmarks", "is_relay": true }, { @@ -32,6 +35,7 @@ "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -49,6 +53,7 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", "is_relay": false @@ -58,6 +63,7 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", "is_relay": false @@ -67,6 +73,7 @@ "package": "collectives-westend-runtime", "path": "cumulus/parachains/runtimes/collectives/collectives-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-collectives-rpc.polkadot.io:443" }, @@ -75,6 +82,7 @@ "package": "contracts-rococo-runtime", "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-contracts-rpc.polkadot.io:443", "is_relay": false @@ -84,6 +92,7 @@ "package": "coretime-rococo-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-coretime-rpc.polkadot.io:443", "is_relay": false @@ -93,6 +102,7 @@ "package": "coretime-westend-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-coretime-rpc.polkadot.io:443", "is_relay": false @@ -102,6 +112,7 @@ "package": "glutton-westend-runtime", "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": null, "is_relay": false @@ -111,6 +122,7 @@ "package": "people-rococo-runtime", "path": "cumulus/parachains/runtimes/people/people-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-people-rpc.polkadot.io:443", "is_relay": false @@ -120,6 +132,7 @@ "package": "people-westend-runtime", "path": "cumulus/parachains/runtimes/people/people-westend", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://westend-people-rpc.polkadot.io:443", "is_relay": false -- GitLab From b16237ad6f019667a59b0e3e726f6ac20e2d0a1c Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:11:00 +0300 Subject: [PATCH 012/127] [5 / 5] Introduce approval-voting-parallel (#4849) This is the implementation of the approach described here: https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2150321612 & https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2154357547 & https://github.com/paritytech/polkadot-sdk/issues/1617#issuecomment-2154721395. ## Description of changes The end goal is to have an architecture where we have single subsystem(`approval-voting-parallel`) and multiple worker types that would full-fill the work that currently is fulfilled by the `approval-distribution` and `approval-voting` subsystems. The main loop of the new subsystem would do just the distribution of work to the workers. The new subsystem will have: - N approval-distribution workers: This would do the work that is currently being done by the approval-distribution subsystem and in addition to that will also perform the crypto-checks that an assignment is valid and that a vote is correctly signed. Work is assigned via the following formula: `worker_index = msg.validator % WORKER_COUNT`, this guarantees that all assignments and approvals from the same validator reach the same worker. - 1 approval-voting worker: This would receive an already valid message and do everything the approval-voting currently does, except the crypto-checking that has been moved already to the approval-distribution worker. On the hot path of processing messages **no** synchronisation and waiting is needed between approval-distribution and approval-voting workers. Screenshot 2024-06-07 at 11 28 08 ## Guidelines for reading The full implementation is broken in 5 PRs and all of them are self-contained and improve things incrementally even without the parallelisation being implemented/enabled, the reason this approach was taken instead of a big-bang PR, is to make things easier to review and reduced the risk of breaking this critical subsystems. After reading the full description of this PR, the changes should be read in the following order: 1. https://github.com/paritytech/polkadot-sdk/pull/4848, some other micro-optimizations for networks with a high number of validators. This change gives us a speed up by itself without any other changes. 2. https://github.com/paritytech/polkadot-sdk/pull/4845 , this contains only interface changes to decouple the subsystem from the `Context` and be able to run multiple instances of the subsystem on different threads. **No functional changes** 3. https://github.com/paritytech/polkadot-sdk/pull/4928, moving of the crypto checks from approval-voting in approval-distribution, so that the approval-distribution has no reason to wait after approval-voting anymore. This change gives us a speed up by itself without any other changes. 4. https://github.com/paritytech/polkadot-sdk/pull/4846, interface changes to make approval-voting runnable on a separate thread. **No functional changes** 5. This PR, where we instantiate an `approval-voting-parallel` subsystem that runs on different workers the logic currently in `approval-distribution` and `approval-voting`. 6. The next step after this changes get merged and deploy would be to bring all the files from approval-distribution, approval-voting, approval-voting-parallel into a single rust crate, to make it easier to maintain and understand the structure. ## Results Running subsystem-benchmarks with 1000 validators 100 fully ocuppied cores and triggering all assignments and approvals for all tranches #### Approval does not lags behind. Master ``` Chain selection approved after 72500 ms hash=0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a ``` With this PoC ``` Chain selection approved after 3500 ms hash=0x0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a ``` #### Gathering enough assignments Enough assignments are gathered in less than 500ms, so that gives un a guarantee that un-necessary work does not get triggered, on master on the same benchmark because the subsystems fall behind on work, that number goes above 32 seconds on master. Screenshot 2024-06-20 at 15 48 22 #### Cpu usage: Master ``` CPU usage, seconds total per block approval-distribution 96.9436 9.6944 approval-voting 117.4676 11.7468 test-environment 44.0092 4.4009 ``` With this PoC ``` CPU usage, seconds total per block approval-distribution 0.0014 0.0001 --- unused approval-voting 0.0437 0.0044. --- unused approval-voting-parallel 5.9560 0.5956 approval-voting-parallel-0 22.9073 2.2907 approval-voting-parallel-1 23.0417 2.3042 approval-voting-parallel-2 22.0445 2.2045 approval-voting-parallel-3 22.7234 2.2723 approval-voting-parallel-4 21.9788 2.1979 approval-voting-parallel-5 23.0601 2.3060 approval-voting-parallel-6 22.4805 2.2481 approval-voting-parallel-7 21.8330 2.1833 approval-voting-parallel-db 37.1954 3.7195. --- the approval-voting thread. ``` # Enablement strategy Because just some trivial plumbing is needed in approval-distribution and approval-voting to be able to run things in parallel and because this subsystems plays a critical part in the system this PR proposes that we keep both ways of running the approval work, as separated subsystems and just a single subsystem(`approval-voting-parallel`) which has multiple workers for the distribution work and one worker for the approval-voting work and switch between them with a comandline flag. The benefits for this is twofold. 1. With the same polkadot binary we can easily switch just a few validators to use the parallel approach and gradually make this the default way of running, if now issues arise. 2. In the worst case scenario were it becomes the default way of running things, but we discover there are critical issues with it we have the path to quickly disable it by asking validators to adjust their command line flags. # Next steps - [x] Make sure through various testing we are not missing anything - [x] Polish the implementations to make them production ready - [x] Add Unittest Tests for approval-voting-parallel. - [x] Define and implement the strategy for rolling this change, so that the blast radius is minimal(single validator) in case there are problems with the implementation. - [x] Versi long running tests. - [x] Add relevant metrics. @ordian @eskimor @sandreim @AndreiEres, let me know what you think. --------- Signed-off-by: Alexandru Gheorghe --- .gitlab/pipeline/zombienet/polkadot.yml | 8 + Cargo.lock | 46 + Cargo.toml | 2 + .../src/lib.rs | 1 + polkadot/cli/src/cli.rs | 7 + polkadot/cli/src/command.rs | 1 + .../core/approval-voting-parallel/Cargo.toml | 55 + .../core/approval-voting-parallel/src/lib.rs | 957 +++++++++++++ .../approval-voting-parallel/src/metrics.rs | 236 ++++ .../approval-voting-parallel/src/tests.rs | 1178 +++++++++++++++++ .../approval-voting-regression-bench.rs | 1 + .../dispute-coordinator/src/initialized.rs | 33 +- .../node/core/dispute-coordinator/src/lib.rs | 4 +- .../core/dispute-coordinator/src/tests.rs | 1 + .../network/approval-distribution/src/lib.rs | 9 +- .../approval-distribution/src/metrics.rs | 63 +- .../approval-distribution/src/tests.rs | 75 +- polkadot/node/network/bridge/src/rx/mod.rs | 66 +- polkadot/node/network/bridge/src/rx/tests.rs | 1 + polkadot/node/overseer/src/dummy.rs | 4 + polkadot/node/overseer/src/lib.rs | 30 +- polkadot/node/overseer/src/tests.rs | 11 +- polkadot/node/primitives/src/approval/mod.rs | 2 +- polkadot/node/service/Cargo.toml | 2 + polkadot/node/service/src/lib.rs | 13 + polkadot/node/service/src/overseer.rs | 261 +++- .../node/service/src/relay_chain_selection.rs | 65 +- polkadot/node/service/src/tests.rs | 1 + polkadot/node/subsystem-bench/Cargo.toml | 1 + .../examples/approvals_no_shows.yaml | 1 + .../examples/approvals_throughput.yaml | 1 + .../approvals_throughput_best_case.yaml | 1 + .../src/lib/approval/helpers.rs | 29 +- .../subsystem-bench/src/lib/approval/mod.rs | 170 ++- .../src/lib/availability/mod.rs | 13 +- .../node/subsystem-bench/src/lib/display.rs | 17 + .../subsystem-bench/src/lib/environment.rs | 30 +- .../subsystem-bench/src/lib/mock/dummy.rs | 1 + .../node/subsystem-bench/src/lib/mock/mod.rs | 1 + .../src/lib/mock/network_bridge.rs | 27 +- .../subsystem-bench/src/lib/statement/mod.rs | 5 +- .../node/subsystem-bench/src/lib/usage.rs | 6 +- polkadot/node/subsystem-types/src/messages.rs | 97 ++ polkadot/node/test/service/src/lib.rs | 2 + .../adder/collator/src/main.rs | 1 + .../undying/collator/src/main.rs | 1 + .../node/approval/approval-voting-parallel.md | 30 + .../0009-approval-voting-coalescing.toml | 2 +- .../0016-approval-voting-parallel.toml | 120 ++ .../0016-approval-voting-parallel.zndsl | 35 + prdoc/pr_4849.prdoc | 47 + umbrella/Cargo.toml | 7 +- umbrella/src/lib.rs | 4 + 53 files changed, 3565 insertions(+), 217 deletions(-) create mode 100644 polkadot/node/core/approval-voting-parallel/Cargo.toml create mode 100644 polkadot/node/core/approval-voting-parallel/src/lib.rs create mode 100644 polkadot/node/core/approval-voting-parallel/src/metrics.rs create mode 100644 polkadot/node/core/approval-voting-parallel/src/tests.rs create mode 100644 polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md create mode 100644 polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml create mode 100644 polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl create mode 100644 prdoc/pr_4849.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 93fc4bbb578..e25bc4ca229 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -223,6 +223,14 @@ zombienet-polkadot-functional-0015-coretime-shared-core: --local-dir="${LOCAL_DIR}/functional" --test="0015-coretime-shared-core.zndsl" +zombienet-polkadot-functional-0016-approval-voting-parallel: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0016-approval-voting-parallel.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/Cargo.lock b/Cargo.lock index 61f485bcecb..c20c8f71c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14268,6 +14268,49 @@ dependencies = [ "tracing-gum", ] +[[package]] +name = "polkadot-node-core-approval-voting-parallel" +version = "7.0.0" +dependencies = [ + "assert_matches", + "async-trait", + "futures", + "futures-timer", + "itertools 0.11.0", + "kvdb-memorydb", + "log", + "parking_lot 0.12.3", + "polkadot-approval-distribution", + "polkadot-node-core-approval-voting", + "polkadot-node-jaeger", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-test-helpers", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "polkadot-primitives-test-helpers", + "polkadot-subsystem-bench", + "rand", + "rand_chacha", + "rand_core 0.6.4", + "sc-keystore", + "schnorrkel 0.11.4", + "sp-application-crypto 30.0.0", + "sp-consensus", + "sp-consensus-babe", + "sp-consensus-slots", + "sp-core 28.0.0", + "sp-keyring", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-tracing 16.0.0", + "thiserror", + "tracing-gum", +] + [[package]] name = "polkadot-node-core-av-store" version = "7.0.0" @@ -15397,6 +15440,7 @@ dependencies = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", @@ -15736,6 +15780,7 @@ dependencies = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", @@ -15892,6 +15937,7 @@ dependencies = [ "polkadot-availability-recovery", "polkadot-erasure-coding", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-chain-api", "polkadot-node-metrics", diff --git a/Cargo.toml b/Cargo.toml index b7c9c0cdcbf..c92254242fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -158,6 +158,7 @@ members = [ "polkadot/erasure-coding/fuzzer", "polkadot/node/collation-generation", "polkadot/node/core/approval-voting", + "polkadot/node/core/approval-voting-parallel", "polkadot/node/core/av-store", "polkadot/node/core/backing", "polkadot/node/core/bitfield-signing", @@ -1032,6 +1033,7 @@ polkadot-gossip-support = { path = "polkadot/node/network/gossip-support", defau polkadot-network-bridge = { path = "polkadot/node/network/bridge", default-features = false } polkadot-node-collation-generation = { path = "polkadot/node/collation-generation", default-features = false } polkadot-node-core-approval-voting = { path = "polkadot/node/core/approval-voting", default-features = false } +polkadot-node-core-approval-voting-parallel = { path = "polkadot/node/core/approval-voting-parallel", default-features = false } polkadot-node-core-av-store = { path = "polkadot/node/core/av-store", default-features = false } polkadot-node-core-backing = { path = "polkadot/node/core/backing", default-features = false } polkadot-node-core-bitfield-signing = { path = "polkadot/node/core/bitfield-signing", default-features = false } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 4fea055203d..f0a082dce53 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -379,6 +379,7 @@ fn build_polkadot_full_node( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, )?; diff --git a/polkadot/cli/src/cli.rs b/polkadot/cli/src/cli.rs index 3e5a6ccdd3c..1445ade08e2 100644 --- a/polkadot/cli/src/cli.rs +++ b/polkadot/cli/src/cli.rs @@ -151,6 +151,13 @@ pub struct RunCmd { /// TESTING ONLY: disable the version check between nodes and workers. #[arg(long, hide = true)] pub disable_worker_version_check: bool, + + /// Enable approval-voting message processing in parallel. + /// + ///**Dangerous!** This is an experimental feature and should not be used in production, unless + /// explicitly advised to. + #[arg(long)] + pub enable_approval_voting_parallel: bool, } #[allow(missing_docs)] diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 89e21bf135b..16576e4b272 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -244,6 +244,7 @@ where execute_workers_max_num: cli.run.execute_workers_max_num, prepare_workers_hard_max_num: cli.run.prepare_workers_hard_max_num, prepare_workers_soft_max_num: cli.run.prepare_workers_soft_max_num, + enable_approval_voting_parallel: cli.run.enable_approval_voting_parallel, }, ) .map(|full| full.task_manager)?; diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml new file mode 100644 index 00000000000..e62062eab40 --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "polkadot-node-core-approval-voting-parallel" +version = "7.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +description = "Approval Voting Subsystem running approval work in parallel" + +[lints] +workspace = true + +[dependencies] +async-trait = { workspace = true } +futures = { workspace = true } +futures-timer = { workspace = true } +gum = { workspace = true } +itertools = { workspace = true } +thiserror = { workspace = true } + +polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-approval-distribution = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-jaeger = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } + +sc-keystore = { workspace = true, default-features = false } +sp-consensus = { workspace = true, default-features = false } +sp-consensus-slots = { workspace = true, default-features = false } +sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } +sp-runtime = { workspace = true, default-features = false } + +rand = { workspace = true } +rand_core = { workspace = true } +rand_chacha = { workspace = true } + +[dev-dependencies] +async-trait = { workspace = true } +parking_lot = { workspace = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-tracing = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true, default-features = true } +assert_matches = { workspace = true } +kvdb-memorydb = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true, default-features = true } +log = { workspace = true, default-features = true } +polkadot-subsystem-bench = { workspace = true, default-features = true } +schnorrkel = { workspace = true, default-features = true } diff --git a/polkadot/node/core/approval-voting-parallel/src/lib.rs b/polkadot/node/core/approval-voting-parallel/src/lib.rs new file mode 100644 index 00000000000..18c73cfba1f --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/lib.rs @@ -0,0 +1,957 @@ +// 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 . + +//! The Approval Voting Parallel Subsystem. +//! +//! This subsystem is responsible for orchestrating the work done by +//! approval-voting and approval-distribution subsystem, so they can +//! do their work in parallel, rather than serially, when they are run +//! as independent subsystems. +use itertools::Itertools; +use metrics::{Meters, MetricsWatcher}; +use polkadot_node_core_approval_voting::{Config, RealAssignmentCriteria}; +use polkadot_node_metrics::metered::{ + self, channel, unbounded, MeteredReceiver, MeteredSender, UnboundedMeteredReceiver, + UnboundedMeteredSender, +}; + +use polkadot_node_primitives::{ + approval::time::{Clock, SystemClock}, + DISPUTE_WINDOW, +}; +use polkadot_node_subsystem::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + overseer, FromOrchestra, SpawnedSubsystem, SubsystemError, SubsystemResult, +}; +use polkadot_node_subsystem_util::{ + self, + database::Database, + runtime::{Config as RuntimeInfoConfig, RuntimeInfo}, +}; +use polkadot_overseer::{OverseerSignal, Priority, SubsystemSender, TimeoutExt}; +use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex, ValidatorSignature}; +use rand::SeedableRng; + +use sc_keystore::LocalKeystore; +use sp_consensus::SyncOracle; + +use futures::{channel::oneshot, prelude::*, StreamExt}; +pub use metrics::Metrics; +use polkadot_node_core_approval_voting::{ + approval_db::common::Config as DatabaseConfig, ApprovalVotingWorkProvider, +}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, + time::Duration, +}; +use stream::{select_with_strategy, PollNext, SelectWithStrategy}; +pub mod metrics; + +#[cfg(test)] +mod tests; + +pub(crate) const LOG_TARGET: &str = "parachain::approval-voting-parallel"; +// Value rather arbitrarily: Should not be hit in practice, it exists to more easily diagnose dead +// lock issues for example. +const WAIT_FOR_SIGS_GATHER_TIMEOUT: Duration = Duration::from_millis(2000); + +/// The number of workers used for running the approval-distribution logic. +pub const APPROVAL_DISTRIBUTION_WORKER_COUNT: usize = 4; + +/// The default channel size for the workers, can be overridden by the user through +/// `overseer_channel_capacity_override` +pub const DEFAULT_WORKERS_CHANNEL_SIZE: usize = 64000 / APPROVAL_DISTRIBUTION_WORKER_COUNT; + +fn prio_right<'a>(_val: &'a mut ()) -> PollNext { + PollNext::Right +} + +/// The approval voting parallel subsystem. +pub struct ApprovalVotingParallelSubsystem { + /// `LocalKeystore` is needed for assignment keys, but not necessarily approval keys. + /// + /// We do a lot of VRF signing and need the keys to have low latency. + keystore: Arc, + db_config: DatabaseConfig, + slot_duration_millis: u64, + db: Arc, + sync_oracle: Box, + metrics: Metrics, + spawner: Arc, + clock: Arc, + overseer_message_channel_capacity_override: Option, +} + +impl ApprovalVotingParallelSubsystem { + /// Create a new approval voting subsystem with the given keystore, config, and database. + pub fn with_config( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + spawner: impl overseer::gen::Spawner + 'static + Clone, + overseer_message_channel_capacity_override: Option, + ) -> Self { + ApprovalVotingParallelSubsystem::with_config_and_clock( + config, + db, + keystore, + sync_oracle, + metrics, + Arc::new(SystemClock {}), + spawner, + overseer_message_channel_capacity_override, + ) + } + + /// Create a new approval voting subsystem with the given keystore, config, clock, and database. + pub fn with_config_and_clock( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + clock: Arc, + spawner: impl overseer::gen::Spawner + 'static, + overseer_message_channel_capacity_override: Option, + ) -> Self { + ApprovalVotingParallelSubsystem { + keystore, + slot_duration_millis: config.slot_duration_millis, + db, + db_config: DatabaseConfig { col_approval_data: config.col_approval_data }, + sync_oracle, + metrics, + spawner: Arc::new(spawner), + clock, + overseer_message_channel_capacity_override, + } + } + + /// The size of the channel used for the workers. + fn workers_channel_size(&self) -> usize { + self.overseer_message_channel_capacity_override + .unwrap_or(DEFAULT_WORKERS_CHANNEL_SIZE) + } +} + +#[overseer::subsystem(ApprovalVotingParallel, error = SubsystemError, prefix = self::overseer)] +impl ApprovalVotingParallelSubsystem { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = run::(ctx, self) + .map_err(|e| SubsystemError::with_origin("approval-voting-parallel", e)) + .boxed(); + + SpawnedSubsystem { name: "approval-voting-parallel-subsystem", future } + } +} + +// It starts worker for the approval voting subsystem and the `APPROVAL_DISTRIBUTION_WORKER_COUNT` +// workers for the approval distribution subsystem. +// +// It returns handles that can be used to send messages to the workers. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn start_workers( + ctx: &mut Context, + subsystem: ApprovalVotingParallelSubsystem, + metrics_watcher: &mut MetricsWatcher, +) -> SubsystemResult<(ToWorker, Vec>)> +where +{ + gum::info!(target: LOG_TARGET, "Starting approval distribution workers"); + + // Build approval voting handles. + let (to_approval_voting_worker, approval_voting_work_provider) = build_worker_handles( + "approval-voting-parallel-db".into(), + subsystem.workers_channel_size(), + metrics_watcher, + prio_right, + ); + let mut to_approval_distribution_workers = Vec::new(); + let slot_duration_millis = subsystem.slot_duration_millis; + + for i in 0..APPROVAL_DISTRIBUTION_WORKER_COUNT { + let mut network_sender = ctx.sender().clone(); + let mut runtime_api_sender = ctx.sender().clone(); + let mut approval_distribution_to_approval_voting = to_approval_voting_worker.clone(); + + let approval_distr_instance = + polkadot_approval_distribution::ApprovalDistribution::new_with_clock( + subsystem.metrics.approval_distribution_metrics(), + subsystem.slot_duration_millis, + subsystem.clock.clone(), + Arc::new(RealAssignmentCriteria {}), + ); + let task_name = format!("approval-voting-parallel-{}", i); + let (to_approval_distribution_worker, mut approval_distribution_work_provider) = + build_worker_handles( + task_name.clone(), + subsystem.workers_channel_size(), + metrics_watcher, + prio_right, + ); + + metrics_watcher.watch(task_name.clone(), to_approval_distribution_worker.meter()); + + subsystem.spawner.spawn_blocking( + task_name.leak(), + Some("approval-voting-parallel"), + Box::pin(async move { + let mut state = + polkadot_approval_distribution::State::with_config(slot_duration_millis); + let mut rng = rand::rngs::StdRng::from_entropy(); + let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig { + keystore: None, + session_cache_lru_size: DISPUTE_WINDOW.get(), + }); + + loop { + let message = match approval_distribution_work_provider.next().await { + Some(message) => message, + None => { + gum::info!( + target: LOG_TARGET, + "Approval distribution stream finished, most likely shutting down", + ); + break; + }, + }; + if approval_distr_instance + .handle_from_orchestra( + message, + &mut approval_distribution_to_approval_voting, + &mut network_sender, + &mut runtime_api_sender, + &mut state, + &mut rng, + &mut session_info_provider, + ) + .await + { + gum::info!( + target: LOG_TARGET, + "Approval distribution worker {}, exiting because of shutdown", i + ); + }; + } + }), + ); + to_approval_distribution_workers.push(to_approval_distribution_worker); + } + + gum::info!(target: LOG_TARGET, "Starting approval voting workers"); + + let sender = ctx.sender().clone(); + let to_approval_distribution = ApprovalVotingToApprovalDistribution(sender.clone()); + polkadot_node_core_approval_voting::start_approval_worker( + approval_voting_work_provider, + sender.clone(), + to_approval_distribution, + polkadot_node_core_approval_voting::Config { + slot_duration_millis: subsystem.slot_duration_millis, + col_approval_data: subsystem.db_config.col_approval_data, + }, + subsystem.db.clone(), + subsystem.keystore.clone(), + subsystem.sync_oracle, + subsystem.metrics.approval_voting_metrics(), + subsystem.spawner.clone(), + "approval-voting-parallel-db", + "approval-voting-parallel", + subsystem.clock.clone(), + ) + .await?; + + Ok((to_approval_voting_worker, to_approval_distribution_workers)) +} + +// The main run function of the approval parallel voting subsystem. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn run( + mut ctx: Context, + subsystem: ApprovalVotingParallelSubsystem, +) -> SubsystemResult<()> { + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + gum::info!( + target: LOG_TARGET, + "Starting workers" + ); + + let (to_approval_voting_worker, to_approval_distribution_workers) = + start_workers(&mut ctx, subsystem, &mut metrics_watcher).await?; + + gum::info!( + target: LOG_TARGET, + "Starting main subsystem loop" + ); + + run_main_loop(ctx, to_approval_voting_worker, to_approval_distribution_workers, metrics_watcher) + .await +} + +// Main loop of the subsystem, it shouldn't include any logic just dispatching of messages to +// the workers. +// +// It listens for messages from the overseer and dispatches them to the workers. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn run_main_loop( + mut ctx: Context, + mut to_approval_voting_worker: ToWorker, + mut to_approval_distribution_workers: Vec>, + metrics_watcher: MetricsWatcher, +) -> SubsystemResult<()> { + loop { + futures::select! { + next_msg = ctx.recv().fuse() => { + let next_msg = match next_msg { + Ok(msg) => msg, + Err(err) => { + gum::info!(target: LOG_TARGET, ?err, "Approval voting parallel subsystem received an error"); + return Err(err); + } + }; + + match next_msg { + FromOrchestra::Signal(msg) => { + if matches!(msg, OverseerSignal::ActiveLeaves(_)) { + metrics_watcher.collect_metrics(); + } + + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_signal(msg.clone()).await?; + } + + to_approval_voting_worker.send_signal(msg.clone()).await?; + if matches!(msg, OverseerSignal::Conclude) { + break; + } + }, + FromOrchestra::Communication { msg } => match msg { + // The message the approval voting subsystem would've handled. + ApprovalVotingParallelMessage::ApprovedAncestor(_, _,_) | + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate(_, _) => { + to_approval_voting_worker.send_message( + msg.try_into().expect( + "Message is one of ApprovedAncestor, GetApprovalSignaturesForCandidate + and that can be safely converted to ApprovalVotingMessage; qed" + ) + ).await; + }, + // Now the message the approval distribution subsystem would've handled and need to + // be forwarded to the workers. + ApprovalVotingParallelMessage::NewBlocks(msg) => { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message( + ApprovalDistributionMessage::NewBlocks(msg.clone()), + ) + .await; + } + }, + ApprovalVotingParallelMessage::DistributeAssignment(assignment, claimed) => { + let worker = assigned_worker_for_validator(assignment.validator, &mut to_approval_distribution_workers); + worker + .send_message( + ApprovalDistributionMessage::DistributeAssignment(assignment, claimed) + ) + .await; + + }, + ApprovalVotingParallelMessage::DistributeApproval(vote) => { + let worker = assigned_worker_for_validator(vote.validator, &mut to_approval_distribution_workers); + worker + .send_message( + ApprovalDistributionMessage::DistributeApproval(vote) + ).await; + + }, + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg) => { + if let polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + peer_id, + msg, + ) = msg + { + let (all_msgs_from_same_validator, messages_split_by_validator) = validator_index_for_msg(msg); + + for (validator_index, msg) in all_msgs_from_same_validator.into_iter().chain(messages_split_by_validator.into_iter().flatten()) { + let worker = assigned_worker_for_validator(validator_index, &mut to_approval_distribution_workers); + + worker + .send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + peer_id, msg, + ), + ), + ).await; + } + } else { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message_with_priority::( + ApprovalDistributionMessage::NetworkBridgeUpdate(msg.clone()), + ).await; + } + } + }, + ApprovalVotingParallelMessage::GetApprovalSignatures(indices, tx) => { + handle_get_approval_signatures(&mut ctx, &mut to_approval_distribution_workers, indices, tx).await; + }, + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag) => { + for worker in to_approval_distribution_workers.iter_mut() { + worker + .send_message( + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag) + ).await; + } + }, + }, + }; + + }, + }; + } + Ok(()) +} + +// It sends a message to all approval workers to get the approval signatures for the requested +// candidates and then merges them all together and sends them back to the requester. +#[overseer::contextbounds(ApprovalVotingParallel, prefix = self::overseer)] +async fn handle_get_approval_signatures( + ctx: &mut Context, + to_approval_distribution_workers: &mut Vec>, + requested_candidates: HashSet<(Hash, CandidateIndex)>, + result_channel: oneshot::Sender< + HashMap, ValidatorSignature)>, + >, +) { + let mut sigs = HashMap::new(); + let mut signatures_channels = Vec::new(); + for worker in to_approval_distribution_workers.iter_mut() { + let (tx, rx) = oneshot::channel(); + worker.send_unbounded_message(ApprovalDistributionMessage::GetApprovalSignatures( + requested_candidates.clone(), + tx, + )); + signatures_channels.push(rx); + } + + let gather_signatures = async move { + let Some(results) = futures::future::join_all(signatures_channels) + .timeout(WAIT_FOR_SIGS_GATHER_TIMEOUT) + .await + else { + gum::warn!( + target: LOG_TARGET, + "Waiting for approval signatures timed out - dead lock?" + ); + return; + }; + + for result in results { + let worker_sigs = match result { + Ok(sigs) => sigs, + Err(_) => { + gum::error!( + target: LOG_TARGET, + "Getting approval signatures failed, oneshot got closed" + ); + continue; + }, + }; + sigs.extend(worker_sigs); + } + + if let Err(_) = result_channel.send(sigs) { + gum::debug!( + target: LOG_TARGET, + "Sending back approval signatures failed, oneshot got closed" + ); + } + }; + + if let Err(err) = ctx.spawn("approval-voting-gather-signatures", Box::pin(gather_signatures)) { + gum::warn!(target: LOG_TARGET, "Failed to spawn gather signatures task: {:?}", err); + } +} + +// Returns the worker that should receive the message for the given validator. +fn assigned_worker_for_validator( + validator: ValidatorIndex, + to_approval_distribution_workers: &mut Vec>, +) -> &mut ToWorker { + let worker_index = validator.0 as usize % to_approval_distribution_workers.len(); + to_approval_distribution_workers + .get_mut(worker_index) + .expect("Worker index is obtained modulo len; qed") +} + +// Returns the validators that initially created this assignments/votes, the validator index +// is later used to decide which approval-distribution worker should receive the message. +// +// Because this is on the hot path and we don't want to be unnecessarily slow, it contains two logic +// paths. The ultra fast path where all messages have the same validator index and we don't do +// any cloning or allocation and the path where we need to split the messages into multiple +// messages, because they have different validator indices, where we do need to clone and allocate. +// In practice most of the message will fall on the ultra fast path. +fn validator_index_for_msg( + msg: polkadot_node_network_protocol::ApprovalDistributionMessage, +) -> ( + Option<(ValidatorIndex, polkadot_node_network_protocol::ApprovalDistributionMessage)>, + Option>, +) { + match msg { + polkadot_node_network_protocol::Versioned::V1(ref message) => match message { + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), *claimed_candidates)] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + polkadot_node_network_protocol::Versioned::V2(ref message) => match message { + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), *claimed_candidates)] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + polkadot_node_network_protocol::Versioned::V3(ref message) => match message { + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(msgs) => + if let Ok(validator) = msgs.iter().map(|(msg, _)| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|(msg, claimed_candidates)| { + ( + msg.validator, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + vec![(msg.clone(), claimed_candidates.clone())] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(msgs) => + if let Ok(validator) = msgs.iter().map(|msg| msg.validator).all_equal_value() { + (Some((validator, msg)), None) + } else { + let split = msgs + .iter() + .map(|vote| { + ( + vote.validator, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + vec![vote.clone()] + ), + ), + ) + }) + .collect_vec(); + (None, Some(split)) + }, + }, + } +} + +/// A handler object that both type of workers use for receiving work. +/// +/// In practive this is just a wrapper over two channels Receiver, that is injected into +/// approval-voting worker and approval-distribution workers. +type WorkProvider = WorkProviderImpl< + SelectWithStrategy< + MeteredReceiver>, + UnboundedMeteredReceiver>, + Clos, + State, + >, +>; + +pub struct WorkProviderImpl(T); + +impl Stream for WorkProviderImpl +where + T: Stream> + Unpin + Send, +{ + type Item = FromOrchestra; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.0.poll_next_unpin(cx) + } +} + +#[async_trait::async_trait] +impl ApprovalVotingWorkProvider for WorkProviderImpl +where + T: Stream> + Unpin + Send, +{ + async fn recv(&mut self) -> SubsystemResult> { + self.0.next().await.ok_or(SubsystemError::Context( + "ApprovalVotingWorkProviderImpl: Channel closed".to_string(), + )) + } +} + +impl WorkProvider +where + M: Send + Sync + 'static, + Clos: FnMut(&mut State) -> PollNext, + State: Default, +{ + // Constructs a work providers from the channels handles. + fn from_rx_worker(rx: RxWorker, prio: Clos) -> Self { + let prioritised = select_with_strategy(rx.0, rx.1, prio); + WorkProviderImpl(prioritised) + } +} + +/// Just a wrapper for implementing `overseer::SubsystemSender` and +/// `overseer::SubsystemSender`. +/// +/// The instance of this struct can be injected into the workers, so they can talk +/// directly with each other without intermediating in this subsystem loop. +pub struct ToWorker( + MeteredSender>, + UnboundedMeteredSender>, +); + +impl Clone for ToWorker { + fn clone(&self) -> Self { + Self(self.0.clone(), self.1.clone()) + } +} + +impl ToWorker { + async fn send_signal(&mut self, signal: OverseerSignal) -> Result<(), SubsystemError> { + self.1 + .unbounded_send(FromOrchestra::Signal(signal)) + .map_err(|err| SubsystemError::QueueError(err.into_send_error())) + } + + fn meter(&self) -> Meters { + Meters::new(self.0.meter(), self.1.meter()) + } +} + +impl overseer::SubsystemSender for ToWorker { + fn send_message<'life0, 'async_trait>( + &'life0 mut self, + msg: T, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + async { + if let Err(err) = + self.0.send(polkadot_overseer::FromOrchestra::Communication { msg }).await + { + gum::error!( + target: LOG_TARGET, + "Failed to send message to approval voting worker: {:?}, subsystem is probably shutting down.", + err + ); + } + } + .boxed() + } + + fn try_send_message(&mut self, msg: T) -> Result<(), metered::TrySendError> { + self.0 + .try_send(polkadot_overseer::FromOrchestra::Communication { msg }) + .map_err(|result| { + let is_full = result.is_full(); + let msg = match result.into_inner() { + polkadot_overseer::FromOrchestra::Signal(_) => + panic!("Cannot happen variant is never built"), + polkadot_overseer::FromOrchestra::Communication { msg } => msg, + }; + if is_full { + metered::TrySendError::Full(msg) + } else { + metered::TrySendError::Closed(msg) + } + }) + } + + fn send_messages<'life0, 'async_trait, I>( + &'life0 mut self, + msgs: I, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + I: IntoIterator + Send, + I::IntoIter: Send, + I: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + async { + for msg in msgs { + self.send_message(msg).await; + } + } + .boxed() + } + + fn send_unbounded_message(&mut self, msg: T) { + if let Err(err) = + self.1.unbounded_send(polkadot_overseer::FromOrchestra::Communication { msg }) + { + gum::error!( + target: LOG_TARGET, + "Failed to send unbounded message to approval voting worker: {:?}, subsystem is probably shutting down.", + err + ); + } + } + + fn send_message_with_priority<'life0, 'async_trait, P>( + &'life0 mut self, + msg: T, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + P: 'async_trait + Priority, + 'life0: 'async_trait, + Self: 'async_trait, + { + match P::priority() { + polkadot_overseer::PriorityLevel::Normal => self.send_message(msg), + polkadot_overseer::PriorityLevel::High => + async { self.send_unbounded_message(msg) }.boxed(), + } + } + + fn try_send_message_with_priority( + &mut self, + msg: T, + ) -> Result<(), metered::TrySendError> { + match P::priority() { + polkadot_overseer::PriorityLevel::Normal => self.try_send_message(msg), + polkadot_overseer::PriorityLevel::High => Ok(self.send_unbounded_message(msg)), + } + } +} + +/// Handles that are used by an worker to receive work. +pub struct RxWorker( + MeteredReceiver>, + UnboundedMeteredReceiver>, +); + +// Build all the necessary channels for sending messages to an worker +// and for the worker to receive them. +fn build_channels( + channel_name: String, + channel_size: usize, + metrics_watcher: &mut MetricsWatcher, +) -> (ToWorker, RxWorker) { + let (tx_work, rx_work) = channel::>(channel_size); + let (tx_work_unbounded, rx_work_unbounded) = unbounded::>(); + let to_worker = ToWorker(tx_work, tx_work_unbounded); + + metrics_watcher.watch(channel_name, to_worker.meter()); + + (to_worker, RxWorker(rx_work, rx_work_unbounded)) +} + +/// Build the worker handles used for interacting with the workers. +/// +/// `ToWorker` is used for sending messages to the workers. +/// `WorkProvider` is used by the workers for receiving the messages. +fn build_worker_handles( + channel_name: String, + channel_size: usize, + metrics_watcher: &mut MetricsWatcher, + prio_right: Clos, +) -> (ToWorker, WorkProvider) +where + M: Send + Sync + 'static, + Clos: FnMut(&mut State) -> PollNext, + State: Default, +{ + let (to_worker, rx_worker) = build_channels(channel_name, channel_size, metrics_watcher); + (to_worker, WorkProviderImpl::from_rx_worker(rx_worker, prio_right)) +} + +/// Just a wrapper for implementing `overseer::SubsystemSender`, so +/// that we can inject into the approval voting subsystem. +#[derive(Clone)] +pub struct ApprovalVotingToApprovalDistribution>( + S, +); + +impl> + overseer::SubsystemSender for ApprovalVotingToApprovalDistribution +{ + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn send_message<'life0, 'async_trait>( + &'life0 mut self, + msg: ApprovalDistributionMessage, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_message(msg.into()) + } + + fn try_send_message( + &mut self, + msg: ApprovalDistributionMessage, + ) -> Result<(), metered::TrySendError> { + self.0.try_send_message(msg.into()).map_err(|err| match err { + // Safe to unwrap because it was built from the same type. + metered::TrySendError::Closed(msg) => + metered::TrySendError::Closed(msg.try_into().unwrap()), + metered::TrySendError::Full(msg) => + metered::TrySendError::Full(msg.try_into().unwrap()), + }) + } + + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn send_messages<'life0, 'async_trait, I>( + &'life0 mut self, + msgs: I, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + I: IntoIterator + Send, + I::IntoIter: Send, + I: 'async_trait, + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_messages(msgs.into_iter().map(|msg| msg.into())) + } + + fn send_unbounded_message(&mut self, msg: ApprovalDistributionMessage) { + self.0.send_unbounded_message(msg.into()) + } + + fn send_message_with_priority<'life0, 'async_trait, P>( + &'life0 mut self, + msg: ApprovalDistributionMessage, + ) -> ::core::pin::Pin< + Box + ::core::marker::Send + 'async_trait>, + > + where + P: 'async_trait + Priority, + 'life0: 'async_trait, + Self: 'async_trait, + { + self.0.send_message_with_priority::

(msg.into()) + } + + fn try_send_message_with_priority( + &mut self, + msg: ApprovalDistributionMessage, + ) -> Result<(), metered::TrySendError> { + self.0.try_send_message_with_priority::

(msg.into()).map_err(|err| match err { + // Safe to unwrap because it was built from the same type. + metered::TrySendError::Closed(msg) => + metered::TrySendError::Closed(msg.try_into().unwrap()), + metered::TrySendError::Full(msg) => + metered::TrySendError::Full(msg.try_into().unwrap()), + }) + } +} diff --git a/polkadot/node/core/approval-voting-parallel/src/metrics.rs b/polkadot/node/core/approval-voting-parallel/src/metrics.rs new file mode 100644 index 00000000000..1b4ab4bd9b8 --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/metrics.rs @@ -0,0 +1,236 @@ +// 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 . + +//! The Metrics for Approval Voting Parallel Subsystem. + +use std::collections::HashMap; + +use polkadot_node_metrics::{metered::Meter, metrics}; +use polkadot_overseer::prometheus; + +#[derive(Default, Clone)] +pub struct Metrics(Option); + +/// Approval Voting parallel metrics. +#[derive(Clone)] +pub struct MetricsInner { + // The inner metrics of the approval distribution workers. + approval_distribution: polkadot_approval_distribution::metrics::Metrics, + // The inner metrics of the approval voting workers. + approval_voting: polkadot_node_core_approval_voting::Metrics, + + // Time of flight metrics for bounded channels. + to_worker_bounded_tof: prometheus::HistogramVec, + // Number of elements sent to the worker's bounded queue. + to_worker_bounded_sent: prometheus::GaugeVec, + // Number of elements received by the worker's bounded queue. + to_worker_bounded_received: prometheus::GaugeVec, + // Number of times senders blocked while sending messages to the worker. + to_worker_bounded_blocked: prometheus::GaugeVec, + // Time of flight metrics for unbounded channels. + to_worker_unbounded_tof: prometheus::HistogramVec, + // Number of elements sent to the worker's unbounded queue. + to_worker_unbounded_sent: prometheus::GaugeVec, + // Number of elements received by the worker's unbounded queue. + to_worker_unbounded_received: prometheus::GaugeVec, +} + +impl Metrics { + /// Get the approval distribution metrics. + pub fn approval_distribution_metrics( + &self, + ) -> polkadot_approval_distribution::metrics::Metrics { + self.0 + .as_ref() + .map(|metrics_inner| metrics_inner.approval_distribution.clone()) + .unwrap_or_default() + } + + /// Get the approval voting metrics. + pub fn approval_voting_metrics(&self) -> polkadot_node_core_approval_voting::Metrics { + self.0 + .as_ref() + .map(|metrics_inner| metrics_inner.approval_voting.clone()) + .unwrap_or_default() + } +} + +impl metrics::Metrics for Metrics { + /// Try to register the metrics. + fn try_register( + registry: &prometheus::Registry, + ) -> std::result::Result { + Ok(Metrics(Some(MetricsInner { + approval_distribution: polkadot_approval_distribution::metrics::Metrics::try_register( + registry, + )?, + approval_voting: polkadot_node_core_approval_voting::Metrics::try_register(registry)?, + to_worker_bounded_tof: prometheus::register( + prometheus::HistogramVec::new( + prometheus::HistogramOpts::new( + "polkadot_approval_voting_parallel_worker_bounded_tof", + "Duration spent in a particular approval voting worker channel from entrance to removal", + ) + .buckets(vec![ + 0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, + 4.9152, 6.5536, + ]), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_sent: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_sent", + "Number of elements sent to approval voting workers' bounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_received: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_received", + "Number of elements received by approval voting workers' bounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_bounded_blocked: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_bounded_blocked", + "Number of times approval voting workers blocked while sending messages to a subsystem", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_tof: prometheus::register( + prometheus::HistogramVec::new( + prometheus::HistogramOpts::new( + "polkadot_approval_voting_parallel_worker_unbounded_tof", + "Duration spent in a particular approval voting worker channel from entrance to removal", + ) + .buckets(vec![ + 0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, + 4.9152, 6.5536, + ]), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_sent: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_unbounded_sent", + "Number of elements sent to approval voting workers' unbounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + to_worker_unbounded_received: prometheus::register( + prometheus::GaugeVec::::new( + prometheus::Opts::new( + "polkadot_approval_voting_parallel_worker_unbounded_received", + "Number of elements received by approval voting workers' unbounded queues", + ), + &["worker_name"], + )?, + registry, + )?, + }))) + } +} + +/// The meters to watch. +#[derive(Clone)] +pub struct Meters { + bounded: Meter, + unbounded: Meter, +} + +impl Meters { + pub fn new(bounded: &Meter, unbounded: &Meter) -> Self { + Self { bounded: bounded.clone(), unbounded: unbounded.clone() } + } +} + +/// A metrics watcher that watches the meters and updates the metrics. +pub struct MetricsWatcher { + to_watch: HashMap, + metrics: Metrics, +} + +impl MetricsWatcher { + /// Create a new metrics watcher. + pub fn new(metrics: Metrics) -> Self { + Self { to_watch: HashMap::new(), metrics } + } + + /// Watch the meters of a worker with this name. + pub fn watch(&mut self, worker_name: String, meters: Meters) { + self.to_watch.insert(worker_name, meters); + } + + /// Collect all the metrics. + pub fn collect_metrics(&self) { + for (name, meter) in &self.to_watch { + let bounded_readouts = meter.bounded.read(); + let unbounded_readouts = meter.unbounded.read(); + if let Some(metrics) = self.metrics.0.as_ref() { + metrics + .to_worker_bounded_sent + .with_label_values(&[name]) + .set(bounded_readouts.sent as u64); + + metrics + .to_worker_bounded_received + .with_label_values(&[name]) + .set(bounded_readouts.received as u64); + + metrics + .to_worker_bounded_blocked + .with_label_values(&[name]) + .set(bounded_readouts.blocked as u64); + + metrics + .to_worker_unbounded_sent + .with_label_values(&[name]) + .set(unbounded_readouts.sent as u64); + + metrics + .to_worker_unbounded_received + .with_label_values(&[name]) + .set(unbounded_readouts.received as u64); + + let hist_bounded = metrics.to_worker_bounded_tof.with_label_values(&[name]); + for tof in bounded_readouts.tof { + hist_bounded.observe(tof.as_f64()); + } + + let hist_unbounded = metrics.to_worker_unbounded_tof.with_label_values(&[name]); + for tof in unbounded_readouts.tof { + hist_unbounded.observe(tof.as_f64()); + } + } + } + } +} diff --git a/polkadot/node/core/approval-voting-parallel/src/tests.rs b/polkadot/node/core/approval-voting-parallel/src/tests.rs new file mode 100644 index 00000000000..215a707147f --- /dev/null +++ b/polkadot/node/core/approval-voting-parallel/src/tests.rs @@ -0,0 +1,1178 @@ +// 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 . + +//! The tests for Approval Voting Parallel Subsystem. + +use std::{ + collections::{HashMap, HashSet}, + future::Future, + sync::Arc, + time::Duration, +}; + +use crate::{ + build_worker_handles, metrics::MetricsWatcher, prio_right, run_main_loop, start_workers, + validator_index_for_msg, ApprovalVotingParallelSubsystem, Metrics, WorkProvider, +}; +use assert_matches::assert_matches; +use futures::{channel::oneshot, future, stream::PollNext, StreamExt}; +use itertools::Itertools; +use polkadot_node_core_approval_voting::{ApprovalVotingWorkProvider, Config}; +use polkadot_node_network_protocol::{peer_set::ValidationVersion, ObservedRole, PeerId, View}; +use polkadot_node_primitives::approval::{ + time::SystemClock, + v1::{ + AssignmentCert, AssignmentCertKind, IndirectAssignmentCert, IndirectSignedApprovalVote, + RELAY_VRF_MODULO_CONTEXT, + }, + v2::{ + AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, IndirectAssignmentCertV2, + IndirectSignedApprovalVoteV2, + }, +}; +use polkadot_node_subsystem::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + FromOrchestra, +}; +use polkadot_node_subsystem_test_helpers::{mock::new_leaf, TestSubsystemContext}; +use polkadot_overseer::{ActiveLeavesUpdate, OverseerSignal, SpawnGlue, TimeoutExt}; +use polkadot_primitives::{CandidateHash, CoreIndex, Hash, ValidatorIndex}; +use sc_keystore::{Keystore, LocalKeystore}; +use sp_consensus::SyncOracle; +use sp_consensus_babe::{VrfPreOutput, VrfProof, VrfSignature}; +use sp_core::{testing::TaskExecutor, H256}; +use sp_keyring::Sr25519Keyring; +type VirtualOverseer = + polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; + +const SLOT_DURATION_MILLIS: u64 = 6000; + +pub mod test_constants { + pub(crate) const DATA_COL: u32 = 0; + pub(crate) const NUM_COLUMNS: u32 = 1; +} + +fn fake_assignment_cert(block_hash: Hash, validator: ValidatorIndex) -> IndirectAssignmentCert { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"WhenParachains?"; + let mut prng = rand_core::OsRng; + let keypair = schnorrkel::Keypair::generate_with(&mut prng); + let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); + let preout = inout.to_preout(); + + IndirectAssignmentCert { + block_hash, + validator, + cert: AssignmentCert { + kind: AssignmentCertKind::RelayVRFModulo { sample: 1 }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + }, + } +} + +fn fake_assignment_cert_v2( + block_hash: Hash, + validator: ValidatorIndex, + core_bitfield: CoreBitfield, +) -> IndirectAssignmentCertV2 { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"WhenParachains?"; + let mut prng = rand_core::OsRng; + let keypair = schnorrkel::Keypair::generate_with(&mut prng); + let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); + let preout = inout.to_preout(); + + IndirectAssignmentCertV2 { + block_hash, + validator, + cert: AssignmentCertV2 { + kind: AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + }, + } +} + +/// Creates a meaningless signature +pub fn dummy_signature() -> polkadot_primitives::ValidatorSignature { + sp_core::crypto::UncheckedFrom::unchecked_from([1u8; 64]) +} + +fn build_subsystem( + sync_oracle: Box, +) -> ( + ApprovalVotingParallelSubsystem, + TestSubsystemContext>, + VirtualOverseer, +) { + sp_tracing::init_for_tests(); + + let pool = sp_core::testing::TaskExecutor::new(); + let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context::< + ApprovalVotingParallelMessage, + _, + >(pool.clone()); + + let keystore = LocalKeystore::in_memory(); + let _ = keystore.sr25519_generate_new( + polkadot_primitives::PARACHAIN_KEY_TYPE_ID, + Some(&Sr25519Keyring::Alice.to_seed()), + ); + + let clock = Arc::new(SystemClock {}); + let db = kvdb_memorydb::create(test_constants::NUM_COLUMNS); + let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + + ( + ApprovalVotingParallelSubsystem::with_config_and_clock( + Config { + col_approval_data: test_constants::DATA_COL, + slot_duration_millis: SLOT_DURATION_MILLIS, + }, + Arc::new(db), + Arc::new(keystore), + sync_oracle, + Metrics::default(), + clock.clone(), + SpawnGlue(pool), + None, + ), + context, + virtual_overseer, + ) +} + +#[derive(Clone)] +struct TestSyncOracle {} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&self) -> bool { + false + } + + fn is_offline(&self) -> bool { + unimplemented!("not used in network bridge") + } +} + +fn test_harness( + num_approval_distro_workers: usize, + prio_right: Clos, + subsystem_gracefully_exits: bool, + test_fn: impl FnOnce( + VirtualOverseer, + WorkProvider, + Vec>, + ) -> T, +) where + T: Future, + Clos: Clone + FnMut(&mut State) -> PollNext, + State: Default, +{ + let (subsystem, context, virtual_overseer) = build_subsystem(Box::new(TestSyncOracle {})); + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + let channel_size = 5; + + let (to_approval_voting_worker, approval_voting_work_provider) = + build_worker_handles::( + "to_approval_voting_worker".into(), + channel_size, + &mut metrics_watcher, + prio_right.clone(), + ); + + let approval_distribution_channels = { 0..num_approval_distro_workers } + .into_iter() + .map(|worker_index| { + build_worker_handles::( + format!("to_approval_distro/{}", worker_index), + channel_size, + &mut metrics_watcher, + prio_right.clone(), + ) + }) + .collect_vec(); + + let to_approval_distribution_workers = + approval_distribution_channels.iter().map(|(tx, _)| tx.clone()).collect_vec(); + let approval_distribution_work_providers = + approval_distribution_channels.into_iter().map(|(_, rx)| rx).collect_vec(); + + let subsystem = async move { + let result = run_main_loop( + context, + to_approval_voting_worker, + to_approval_distribution_workers, + metrics_watcher, + ) + .await; + + if subsystem_gracefully_exits && result.is_err() { + result + } else { + Ok(()) + } + }; + + let test_fut = test_fn( + virtual_overseer, + approval_voting_work_provider, + approval_distribution_work_providers, + ); + + futures::pin_mut!(test_fut); + futures::pin_mut!(subsystem); + + futures::executor::block_on(future::join( + async move { + let _overseer = test_fut.await; + }, + subsystem, + )) + .1 + .unwrap(); +} + +const TIMEOUT: Duration = Duration::from_millis(2000); + +async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) { + overseer + .send(FromOrchestra::Signal(signal)) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); +} + +async fn overseer_message(overseer: &mut VirtualOverseer, msg: ApprovalVotingParallelMessage) { + overseer + .send(FromOrchestra::Communication { msg }) + .timeout(TIMEOUT) + .await + .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); +} + +async fn run_start_workers() { + let (subsystem, mut context, _) = build_subsystem(Box::new(TestSyncOracle {})); + let mut metrics_watcher = MetricsWatcher::new(subsystem.metrics.clone()); + let _workers = start_workers(&mut context, subsystem, &mut metrics_watcher).await.unwrap(); +} + +// Test starting the workers succeeds. +#[test] +fn start_workers_succeeds() { + futures::executor::block_on(run_start_workers()); +} + +// Test main loop forwards messages to the correct worker for all type of messages. +#[test] +fn test_main_loop_forwards_correctly() { + let num_approval_distro_workers = 4; + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + // 1. Check Signals are correctly forwarded to the workers. + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + } + + let (test_tx, _rx) = oneshot::channel(); + let test_hash = Hash::random(); + let test_block_nr = 2; + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::ApprovedAncestor(test_hash, test_block_nr, test_tx), + ) + .await; + assert_matches!( + approval_voting_work_provider.recv().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalVotingMessage::ApprovedAncestor(hash, block_nr, _) + } => { + assert_eq!(hash, test_hash); + assert_eq!(block_nr, test_block_nr); + } + ); + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + + // 2. Check GetApprovalSignaturesForCandidate is correctly forwarded to the workers. + let (test_tx, _rx) = oneshot::channel(); + let test_hash = CandidateHash(Hash::random()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + test_hash, test_tx, + ), + ) + .await; + + assert_matches!( + approval_voting_work_provider.recv().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalVotingMessage::GetApprovalSignaturesForCandidate(hash, _) + } => { + assert_eq!(hash, test_hash); + } + ); + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + + // 3. Check NewBlocks is correctly forwarded to the workers. + overseer_message(&mut overseer, ApprovalVotingParallelMessage::NewBlocks(vec![])).await; + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NewBlocks(blocks) + } => { + assert!(blocks.is_empty()); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 4. Check DistributeAssignment is correctly forwarded to the workers. + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(cert, bitfield) + } => { + assert_eq!(cert, assignment); + assert_eq!(bitfield, 1.into()); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 5. Check DistributeApproval is correctly forwarded to the workers. + let validator_index = ValidatorIndex(26); + let expected_vote = IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }; + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeApproval(expected_vote.clone()), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeApproval(vote) + } => { + assert_eq!(vote, expected_vote); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + + // 6. Check NetworkBridgeUpdate::PeerMessage is correctly forwarded just to one of the + // workers. + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]; + let expected_msg = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + PeerId::random(), + expected_msg.clone(), + ), + ), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + _, + msg, + ), + ) + } => { + assert_eq!(msg, expected_msg); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 7. Check NetworkBridgeUpdate::PeerConnected is correctly forwarded to all workers. + let expected_peer_id = PeerId::random(); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerConnected( + expected_peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + ), + ), + ) + .await; + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerConnected( + peer_id, + role, + version, + authority_id, + ), + ) + } => { + assert_eq!(peer_id, expected_peer_id); + assert_eq!(role, ObservedRole::Authority); + assert_eq!(version, ValidationVersion::V3.into()); + assert_eq!(authority_id, None); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + // 8. Check ApprovalCheckingLagUpdate is correctly forwarded to all workers. + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(7), + ) + .await; + + for rx_approval_distribution_worker in rx_approval_distribution_workers.iter_mut() { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::ApprovalCheckingLagUpdate( + lag + ) + } => { + assert_eq!(lag, 7); + } + ); + } + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + + overseer + }, + ); +} + +/// Test GetApprovalSignatures correctly gatheres the signatures from all workers. +#[test] +fn test_handle_get_approval_signatures() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let (tx, rx) = oneshot::channel(); + let first_block = Hash::random(); + let second_block = Hash::random(); + let expected_candidates: HashSet<_> = + vec![(first_block, 2), (second_block, 3)].into_iter().collect(); + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::GetApprovalSignatures( + expected_candidates.clone(), + tx, + ), + ) + .await; + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + let mut all_votes = HashMap::new(); + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::GetApprovalSignatures( + candidates, tx + ) + } => { + assert_eq!(candidates, expected_candidates); + let to_send: HashMap<_, _> = {0..10}.into_iter().map(|validator| { + let validator_index = ValidatorIndex(validator as u32 * num_approval_distro_workers as u32 + index as u32); + (validator_index, (first_block, vec![2, 4], dummy_signature())) + }).collect(); + tx.send(to_send.clone()).unwrap(); + all_votes.extend(to_send.clone()); + + } + ); + } + + let received_votes = rx.await.unwrap(); + assert_eq!(received_votes, all_votes); + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + + overseer + }, + ) +} + +/// Test subsystem exits with error when approval_voting_work_provider exits. +#[test] +fn test_subsystem_exits_with_error_if_approval_voting_worker_errors() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + false, + |overseer, approval_voting_work_provider, _rx_approval_distribution_workers| async move { + // Drop the approval_voting_work_provider to simulate an error. + std::mem::drop(approval_voting_work_provider); + + overseer + }, + ) +} + +/// Test subsystem exits with error when approval_distribution_workers exits. +#[test] +fn test_subsystem_exits_with_error_if_approval_distribution_worker_errors() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + false, + |overseer, _approval_voting_work_provider, rx_approval_distribution_workers| async move { + // Drop the approval_distribution_workers to simulate an error. + std::mem::drop(rx_approval_distribution_workers.into_iter().next().unwrap()); + overseer + }, + ) +} + +/// Test signals sent before messages are processed in order. +#[test] +fn test_signal_before_message_keeps_receive_order() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + let rx_approval_distribution_worker = rx_approval_distribution_workers + .get_mut(validator_index.0 as usize % num_approval_distro_workers) + .unwrap(); + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + assert_matches!( + rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(_, _) + } + ); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +/// Test signals sent after messages are processed with the highest priority. +#[test] +fn test_signal_is_prioritized_when_unread_messages_in_the_queue() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let validator_index = ValidatorIndex(17); + let assignment = + fake_assignment_cert_v2(Hash::random(), validator_index, CoreIndex(1).into()); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::DistributeAssignment(assignment.clone(), 1.into()), + ) + .await; + + let signal = OverseerSignal::ActiveLeaves(ActiveLeavesUpdate::start_work(new_leaf( + Hash::random(), + 1, + ))); + overseer_signal(&mut overseer, signal.clone()).await; + + let approval_voting_receives = approval_voting_work_provider.recv().await.unwrap(); + assert_matches!(approval_voting_receives, FromOrchestra::Signal(_)); + let rx_approval_distribution_worker = rx_approval_distribution_workers + .get_mut(validator_index.0 as usize % num_approval_distro_workers) + .unwrap(); + let approval_distribution_receives = + rx_approval_distribution_worker.next().await.unwrap(); + assert_matches!(approval_distribution_receives, FromOrchestra::Signal(_)); + assert_matches!( + rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::DistributeAssignment(_, _) + } + ); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +/// Test peer view updates have higher priority than normal messages. +#[test] +fn test_peer_view_is_prioritized_when_unread_messages_in_the_queue() { + let num_approval_distro_workers = 4; + + test_harness( + num_approval_distro_workers, + prio_right, + true, + |mut overseer, mut approval_voting_work_provider, mut rx_approval_distribution_workers| async move { + let validator_index = ValidatorIndex(17); + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]; + let expected_msg = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + PeerId::random(), + expected_msg.clone(), + ), + ), + ) + .await; + + overseer_message( + &mut overseer, + ApprovalVotingParallelMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange( + PeerId::random(), + View::default(), + ), + ), + ) + .await; + + for (index, rx_approval_distribution_worker) in + rx_approval_distribution_workers.iter_mut().enumerate() + { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerViewChange( + _, + _, + ), + ) + } => { + } + ); + if index == validator_index.0 as usize % num_approval_distro_workers { + assert_matches!(rx_approval_distribution_worker.next().await.unwrap(), + FromOrchestra::Communication { + msg: ApprovalDistributionMessage::NetworkBridgeUpdate( + polkadot_node_subsystem::messages::NetworkBridgeEvent::PeerMessage( + _, + msg, + ), + ) + } => { + assert_eq!(msg, expected_msg); + } + ); + } else { + assert!(rx_approval_distribution_worker + .next() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + } + } + + assert!(approval_voting_work_provider + .recv() + .timeout(Duration::from_millis(200)) + .await + .is_none()); + + overseer_signal(&mut overseer, OverseerSignal::Conclude).await; + overseer + }, + ) +} + +// Test validator_index_for_msg with empty messages. +#[test] +fn test_validator_index_with_empty_message() { + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); + + let result = validator_index_for_msg(polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![]), + )); + + assert_eq!(result, (None, Some(vec![]))); +} + +// Test validator_index_for_msg when all the messages are originating from the same validator. +#[test] +fn test_validator_index_with_all_messages_from_the_same_validator() { + let validator_index = ValidatorIndex(3); + let v1_assignment = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments(vec![ + (fake_assignment_cert(H256::random(), validator_index), 1), + (fake_assignment_cert(H256::random(), validator_index), 3), + ]), + ); + let result = validator_index_for_msg(v1_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v1_assignment)), None)); + + let v1_approval = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v1_approval.clone()); + + assert_eq!(result, (Some((validator_index, v1_approval)), None)); + + let validator_index = ValidatorIndex(3); + let v2_assignment = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments(vec![ + (fake_assignment_cert(H256::random(), validator_index), 1), + (fake_assignment_cert(H256::random(), validator_index), 3), + ]), + ); + let result = validator_index_for_msg(v2_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v2_assignment)), None)); + + let v2_approval = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v2_approval.clone()); + + assert_eq!(result, (Some((validator_index, v2_approval)), None)); + + let validator_index = ValidatorIndex(3); + let v3_assignment = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments(vec![ + ( + fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(1).into()), + 1.into(), + ), + ( + fake_assignment_cert_v2(H256::random(), validator_index, CoreIndex(3).into()), + 3.into(), + ), + ]), + ); + let result = validator_index_for_msg(v3_assignment.clone()); + + assert_eq!(result, (Some((validator_index, v3_assignment)), None)); + + let v3_approval = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals(vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: validator_index, + signature: dummy_signature(), + }, + ]), + ); + let result = validator_index_for_msg(v3_approval.clone()); + + assert_eq!(result, (Some((validator_index, v3_approval)), None)); +} + +// Test validator_index_for_msg when all the messages are originating from different validators, +// so the function should split them by validator index, so we can forward them separately to the +// worker they are assigned to. +#[test] +fn test_validator_index_with_messages_from_different_validators() { + let first_validator_index = ValidatorIndex(3); + let second_validator_index = ValidatorIndex(4); + let assignments = vec![ + (fake_assignment_cert(H256::random(), first_validator_index), 1), + (fake_assignment_cert(H256::random(), second_validator_index), 3), + ]; + let v1_assignment = polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + assignments.clone(), + ), + ); + let result = validator_index_for_msg(v1_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V1( + polkadot_node_network_protocol::v1::ApprovalDistributionMessage::Assignments( + assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let v2_assignment = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + assignments.clone(), + ), + ); + let result = validator_index_for_msg(v2_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Assignments( + assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let first_validator_index = ValidatorIndex(3); + let second_validator_index = ValidatorIndex(4); + let v2_assignments = vec![ + ( + fake_assignment_cert_v2(H256::random(), first_validator_index, CoreIndex(1).into()), + 1.into(), + ), + ( + fake_assignment_cert_v2(H256::random(), second_validator_index, CoreIndex(3).into()), + 3.into(), + ), + ]; + + let approvals = vec![ + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 1, + validator: first_validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVote { + block_hash: H256::random(), + candidate_index: 2, + validator: second_validator_index, + signature: dummy_signature(), + }, + ]; + let v2_approvals = polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + let result = validator_index_for_msg(v2_approvals.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), approvals.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, approvals[index].validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V2( + polkadot_node_network_protocol::v2::ApprovalDistributionMessage::Approvals( + approvals.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let v3_assignment = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + v2_assignments.clone(), + ), + ); + let result = validator_index_for_msg(v3_assignment.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), v2_assignments.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, v2_assignments[index].0.validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Assignments( + v2_assignments.get(index).into_iter().cloned().collect(), + ), + ) + ); + } + + let approvals = vec![ + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 1.into(), + validator: first_validator_index, + signature: dummy_signature(), + }, + IndirectSignedApprovalVoteV2 { + block_hash: H256::random(), + candidate_indices: 2.into(), + validator: second_validator_index, + signature: dummy_signature(), + }, + ]; + let v3_approvals = polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.clone(), + ), + ); + let result = validator_index_for_msg(v3_approvals.clone()); + + assert_matches!(result, (None, Some(_))); + let messsages_split_by_validator = result.1.unwrap(); + assert_eq!(messsages_split_by_validator.len(), approvals.len()); + for (index, (validator_index, message)) in messsages_split_by_validator.into_iter().enumerate() + { + assert_eq!(validator_index, approvals[index].validator); + assert_eq!( + message, + polkadot_node_network_protocol::Versioned::V3( + polkadot_node_network_protocol::v3::ApprovalDistributionMessage::Approvals( + approvals.get(index).into_iter().cloned().collect(), + ), + ) + ); + } +} diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index 0b03f1127ee..db0396a8319 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -53,6 +53,7 @@ fn main() -> Result<(), String> { stop_when_approved: false, workdir_prefix: "/tmp".to_string(), num_no_shows_per_candidate: 0, + approval_voting_parallel_enabled: true, }; println!("Benchmarking..."); diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index 5096fe5e689..9cf9047b727 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -34,8 +34,9 @@ use polkadot_node_primitives::{ }; use polkadot_node_subsystem::{ messages::{ - ApprovalVotingMessage, BlockDescription, ChainSelectionMessage, DisputeCoordinatorMessage, - DisputeDistributionMessage, ImportStatementsResult, + ApprovalVotingMessage, ApprovalVotingParallelMessage, BlockDescription, + ChainSelectionMessage, DisputeCoordinatorMessage, DisputeDistributionMessage, + ImportStatementsResult, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, RuntimeApiError, }; @@ -117,6 +118,7 @@ pub(crate) struct Initialized { /// `CHAIN_IMPORT_MAX_BATCH_SIZE` and put the rest here for later processing. chain_import_backlog: VecDeque, metrics: Metrics, + approval_voting_parallel_enabled: bool, } #[overseer::contextbounds(DisputeCoordinator, prefix = self::overseer)] @@ -130,7 +132,13 @@ impl Initialized { highest_session_seen: SessionIndex, gaps_in_cache: bool, ) -> Self { - let DisputeCoordinatorSubsystem { config: _, store: _, keystore, metrics } = subsystem; + let DisputeCoordinatorSubsystem { + config: _, + store: _, + keystore, + metrics, + approval_voting_parallel_enabled, + } = subsystem; let (participation_sender, participation_receiver) = mpsc::channel(1); let participation = Participation::new(participation_sender, metrics.clone()); @@ -148,6 +156,7 @@ impl Initialized { participation_receiver, chain_import_backlog: VecDeque::new(), metrics, + approval_voting_parallel_enabled, } } @@ -1059,9 +1068,21 @@ impl Initialized { // 4. We are waiting (and blocking the whole subsystem) on a response right after - // therefore even with all else failing we will never have more than // one message in flight at any given time. - ctx.send_unbounded_message( - ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate_hash, tx), - ); + if self.approval_voting_parallel_enabled { + ctx.send_unbounded_message( + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + candidate_hash, + tx, + ), + ); + } else { + ctx.send_unbounded_message( + ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + candidate_hash, + tx, + ), + ); + } match rx.await { Err(_) => { gum::warn!( diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index 34d9ddf3a97..84408eb9630 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -122,6 +122,7 @@ pub struct DisputeCoordinatorSubsystem { store: Arc, keystore: Arc, metrics: Metrics, + approval_voting_parallel_enabled: bool, } /// Configuration for the dispute coordinator subsystem. @@ -164,8 +165,9 @@ impl DisputeCoordinatorSubsystem { config: Config, keystore: Arc, metrics: Metrics, + approval_voting_parallel_enabled: bool, ) -> Self { - Self { store, config, keystore, metrics } + Self { store, config, keystore, metrics, approval_voting_parallel_enabled } } /// Initialize and afterwards run `Initialized::run`. diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index f97a625a952..b41cdb94b4d 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -580,6 +580,7 @@ impl TestState { self.config, self.subsystem_keystore.clone(), Metrics::default(), + false, ); let backend = DbBackend::new(self.db.clone(), self.config.column_config(), Metrics::default()); diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 971b6de5f8f..2fcb639338e 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -73,7 +73,8 @@ use std::{ time::Duration, }; -mod metrics; +/// Approval distribution metrics. +pub mod metrics; #[cfg(test)] mod tests; @@ -99,7 +100,7 @@ const MAX_BITFIELD_SIZE: usize = 500; pub struct ApprovalDistribution { metrics: Metrics, slot_duration_millis: u64, - clock: Box, + clock: Arc, assignment_criteria: Arc, } @@ -2668,7 +2669,7 @@ impl ApprovalDistribution { Self::new_with_clock( metrics, slot_duration_millis, - Box::new(SystemClock), + Arc::new(SystemClock), assignment_criteria, ) } @@ -2677,7 +2678,7 @@ impl ApprovalDistribution { pub fn new_with_clock( metrics: Metrics, slot_duration_millis: u64, - clock: Box, + clock: Arc, assignment_criteria: Arc, ) -> Self { Self { metrics, slot_duration_millis, clock, assignment_criteria } diff --git a/polkadot/node/network/approval-distribution/src/metrics.rs b/polkadot/node/network/approval-distribution/src/metrics.rs index 10553c35296..2f677ba415e 100644 --- a/polkadot/node/network/approval-distribution/src/metrics.rs +++ b/polkadot/node/network/approval-distribution/src/metrics.rs @@ -79,31 +79,19 @@ impl Metrics { .map(|metrics| metrics.time_import_pending_now_known.start_timer()) } - pub fn on_approval_already_known(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["known"]).inc() - } - } - - pub fn on_approval_entry_not_found(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["noapprovalentry"]).inc() - } - } - - pub fn on_approval_recent_outdated(&self) { + pub(crate) fn on_approval_recent_outdated(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["outdated"]).inc() } } - pub fn on_approval_invalid_block(&self) { + pub(crate) fn on_approval_invalid_block(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["invalidblock"]).inc() } } - pub fn on_approval_unknown_assignment(&self) { + pub(crate) fn on_approval_unknown_assignment(&self) { if let Some(metrics) = &self.0 { metrics .approvals_received_result @@ -112,94 +100,73 @@ impl Metrics { } } - pub fn on_approval_duplicate(&self) { + pub(crate) fn on_approval_duplicate(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["duplicate"]).inc() } } - pub fn on_approval_out_of_view(&self) { + pub(crate) fn on_approval_out_of_view(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["outofview"]).inc() } } - pub fn on_approval_good_known(&self) { + pub(crate) fn on_approval_good_known(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["goodknown"]).inc() } } - pub fn on_approval_bad(&self) { + pub(crate) fn on_approval_bad(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["bad"]).inc() } } - pub fn on_approval_unexpected(&self) { - if let Some(metrics) = &self.0 { - metrics.approvals_received_result.with_label_values(&["unexpected"]).inc() - } - } - - pub fn on_approval_bug(&self) { + pub(crate) fn on_approval_bug(&self) { if let Some(metrics) = &self.0 { metrics.approvals_received_result.with_label_values(&["bug"]).inc() } } - pub fn on_assignment_already_known(&self) { - if let Some(metrics) = &self.0 { - metrics.assignments_received_result.with_label_values(&["known"]).inc() - } - } - - pub fn on_assignment_recent_outdated(&self) { + pub(crate) fn on_assignment_recent_outdated(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["outdated"]).inc() } } - pub fn on_assignment_invalid_block(&self) { + pub(crate) fn on_assignment_invalid_block(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["invalidblock"]).inc() } } - pub fn on_assignment_duplicate(&self) { + pub(crate) fn on_assignment_duplicate(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["duplicate"]).inc() } } - pub fn on_assignment_out_of_view(&self) { + pub(crate) fn on_assignment_out_of_view(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["outofview"]).inc() } } - pub fn on_assignment_good_known(&self) { + pub(crate) fn on_assignment_good_known(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["goodknown"]).inc() } } - pub fn on_assignment_bad(&self) { + pub(crate) fn on_assignment_bad(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["bad"]).inc() } } - pub fn on_assignment_duplicatevoting(&self) { - if let Some(metrics) = &self.0 { - metrics - .assignments_received_result - .with_label_values(&["duplicatevoting"]) - .inc() - } - } - - pub fn on_assignment_far(&self) { + pub(crate) fn on_assignment_far(&self) { if let Some(metrics) = &self.0 { metrics.assignments_received_result.with_label_values(&["far"]).inc() } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 4ee9320e0e4..068559dea76 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -54,7 +54,7 @@ type VirtualOverseer = fn test_harness>( assignment_criteria: Arc, - clock: Box, + clock: Arc, mut state: State, test_fn: impl FnOnce(VirtualOverseer) -> T, ) -> State { @@ -555,16 +555,15 @@ fn try_import_the_same_assignment() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; - // setup peers + setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V1).await; - // Set up a gossip topology, where a, b, c and d are topology neighbors to the node // under testing. let peers_with_optional_peer_id = peers .iter() @@ -661,7 +660,7 @@ fn try_import_the_same_assignment_v2() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -772,7 +771,7 @@ fn delay_reputation_change() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_with_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -845,7 +844,7 @@ fn spam_attack_results_in_negative_reputation_change() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -942,7 +941,7 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1043,7 +1042,7 @@ fn import_approval_happy_path_v1_v2_peers() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1183,7 +1182,7 @@ fn import_approval_happy_path_v2() { let candidate_hash_second = polkadot_primitives::CandidateHash(Hash::repeat_byte(0xCC)); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1314,7 +1313,7 @@ fn multiple_assignments_covered_with_one_approval_vote() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1524,7 +1523,7 @@ fn unify_with_peer_multiple_assignments_covered_with_one_approval_vote() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1723,7 +1722,7 @@ fn import_approval_bad() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1810,7 +1809,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1858,7 +1857,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1877,7 +1876,7 @@ fn update_our_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -1905,7 +1904,7 @@ fn update_peer_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2004,7 +2003,7 @@ fn update_peer_view() { let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2064,7 +2063,7 @@ fn update_peer_view() { let finalized_number = 4_000_000_000; let state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2106,7 +2105,7 @@ fn update_peer_authority_id() { let _state = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2287,7 +2286,7 @@ fn import_remotely_then_locally() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2393,7 +2392,7 @@ fn sends_assignments_even_when_state_is_approved() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2499,7 +2498,7 @@ fn sends_assignments_even_when_state_is_approved_v2() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2625,7 +2624,7 @@ fn race_condition_in_local_vs_remote_view_update() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2711,7 +2710,7 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2841,7 +2840,7 @@ fn propagates_assignments_along_unshared_dimension() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3000,7 +2999,7 @@ fn propagates_to_required_after_connect() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3165,7 +3164,7 @@ fn sends_to_more_peers_after_getting_topology() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), State::default(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3303,7 +3302,7 @@ fn originator_aggression_l1() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3484,7 +3483,7 @@ fn non_originator_aggression_l1() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3609,7 +3608,7 @@ fn non_originator_aggression_l2() { let aggression_l2_threshold = state.aggression_config.l2_threshold.unwrap(); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3794,7 +3793,7 @@ fn resends_messages_periodically() { state.aggression_config.resend_unfinalized_period = Some(2); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -3958,7 +3957,7 @@ fn import_versioned_approval() { let candidate_hash = polkadot_primitives::CandidateHash(Hash::repeat_byte(0xBB)); let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4131,7 +4130,7 @@ fn batch_test_round(message_count: usize) { let subsystem = ApprovalDistribution::new_with_clock( Default::default(), Default::default(), - Box::new(SystemClock {}), + Arc::new(SystemClock {}), Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), ); let mut rng = rand_chacha::ChaCha12Rng::seed_from_u64(12345); @@ -4318,7 +4317,7 @@ fn subsystem_rejects_assignment_in_future() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(89) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4384,7 +4383,7 @@ fn subsystem_rejects_bad_assignments() { Arc::new(MockAssignmentCriteria { tranche: Err(InvalidAssignment(criteria::InvalidAssignmentReason::NullAssignment)), }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4447,7 +4446,7 @@ fn subsystem_rejects_wrong_claimed_assignments() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -4531,7 +4530,7 @@ fn subsystem_accepts_tranche0_duplicate_assignments() { let _ = test_harness( Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), - Box::new(DummyClock {}), + Arc::new(DummyClock {}), state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 7745c42f78a..9a2357f02d8 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -45,8 +45,9 @@ use polkadot_node_subsystem::{ errors::SubsystemError, messages::{ network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, - BitfieldDistributionMessage, CollatorProtocolMessage, GossipSupportMessage, - NetworkBridgeEvent, NetworkBridgeRxMessage, StatementDistributionMessage, + ApprovalVotingParallelMessage, BitfieldDistributionMessage, CollatorProtocolMessage, + GossipSupportMessage, NetworkBridgeEvent, NetworkBridgeRxMessage, + StatementDistributionMessage, }, overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, }; @@ -89,6 +90,7 @@ pub struct NetworkBridgeRx { validation_service: Box, collation_service: Box, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, } impl NetworkBridgeRx { @@ -105,6 +107,7 @@ impl NetworkBridgeRx { peerset_protocol_names: PeerSetProtocolNames, mut notification_services: HashMap>, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Self { let shared = Shared::default(); @@ -125,6 +128,7 @@ impl NetworkBridgeRx { validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled, } } } @@ -156,6 +160,7 @@ async fn handle_validation_message( peerset_protocol_names: &PeerSetProtocolNames, notification_service: &mut Box, notification_sinks: &mut Arc>>>, + approval_voting_parallel_enabled: bool, ) where AD: validator_discovery::AuthorityDiscovery + Send, { @@ -276,6 +281,7 @@ async fn handle_validation_message( ], sender, &metrics, + approval_voting_parallel_enabled, ) .await; @@ -329,6 +335,7 @@ async fn handle_validation_message( NetworkBridgeEvent::PeerDisconnected(peer), sender, &metrics, + approval_voting_parallel_enabled, ) .await; } @@ -398,7 +405,13 @@ async fn handle_validation_message( network_service.report_peer(peer, report.into()); } - dispatch_validation_events_to_all(events, sender, &metrics).await; + dispatch_validation_events_to_all( + events, + sender, + &metrics, + approval_voting_parallel_enabled, + ) + .await; }, } } @@ -652,6 +665,7 @@ async fn handle_network_messages( mut validation_service: Box, mut collation_service: Box, mut notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Result<(), Error> where AD: validator_discovery::AuthorityDiscovery + Send, @@ -669,6 +683,7 @@ where &peerset_protocol_names, &mut validation_service, &mut notification_sinks, + approval_voting_parallel_enabled, ).await, None => return Err(Error::EventStreamConcluded), }, @@ -727,6 +742,7 @@ async fn run_incoming_orchestra_signals( sync_oracle: Box, metrics: Metrics, notification_sinks: Arc>>>, + approval_voting_parallel_enabled: bool, ) -> Result<(), Error> where AD: validator_discovery::AuthorityDiscovery + Clone, @@ -766,6 +782,7 @@ where local_index, }), ctx.sender(), + approval_voting_parallel_enabled, ); }, FromOrchestra::Communication { @@ -787,6 +804,7 @@ where dispatch_validation_event_to_all_unbounded( NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids), ctx.sender(), + approval_voting_parallel_enabled, ); }, FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), @@ -826,6 +844,7 @@ where finalized_number, &metrics, ¬ification_sinks, + approval_voting_parallel_enabled, ); note_peers_count(&metrics, &shared); } @@ -875,6 +894,7 @@ where validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled, } = bridge; let (task, network_event_handler) = handle_network_messages( @@ -887,6 +907,7 @@ where validation_service, collation_service, notification_sinks.clone(), + approval_voting_parallel_enabled, ) .remote_handle(); @@ -900,6 +921,7 @@ where sync_oracle, metrics, notification_sinks, + approval_voting_parallel_enabled, ); futures::pin_mut!(orchestra_signal_handler); @@ -926,6 +948,7 @@ fn update_our_view( finalized_number: BlockNumber, metrics: &Metrics, notification_sinks: &Arc>>>, + approval_voting_parallel_enabled: bool, ) { let new_view = construct_view(live_heads.iter().map(|v| v.hash), finalized_number); @@ -970,6 +993,7 @@ fn update_our_view( dispatch_validation_event_to_all_unbounded( NetworkBridgeEvent::OurViewChange(our_view.clone()), ctx.sender(), + approval_voting_parallel_enabled, ); dispatch_collation_event_to_all_unbounded( @@ -1081,8 +1105,15 @@ async fn dispatch_validation_event_to_all( event: NetworkBridgeEvent, ctx: &mut impl overseer::NetworkBridgeRxSenderTrait, metrics: &Metrics, + approval_voting_parallel_enabled: bool, ) { - dispatch_validation_events_to_all(std::iter::once(event), ctx, metrics).await + dispatch_validation_events_to_all( + std::iter::once(event), + ctx, + metrics, + approval_voting_parallel_enabled, + ) + .await } async fn dispatch_collation_event_to_all( @@ -1095,6 +1126,7 @@ async fn dispatch_collation_event_to_all( fn dispatch_validation_event_to_all_unbounded( event: NetworkBridgeEvent, sender: &mut impl overseer::NetworkBridgeRxSenderTrait, + approval_voting_parallel_enabled: bool, ) { event .focus() @@ -1106,11 +1138,20 @@ fn dispatch_validation_event_to_all_unbounded( .ok() .map(BitfieldDistributionMessage::from) .and_then(|msg| Some(sender.send_unbounded_message(msg))); - event - .focus() - .ok() - .map(ApprovalDistributionMessage::from) - .and_then(|msg| Some(sender.send_unbounded_message(msg))); + + if approval_voting_parallel_enabled { + event + .focus() + .ok() + .map(ApprovalVotingParallelMessage::from) + .and_then(|msg| Some(sender.send_unbounded_message(msg))); + } else { + event + .focus() + .ok() + .map(ApprovalDistributionMessage::from) + .and_then(|msg| Some(sender.send_unbounded_message(msg))); + } event .focus() .ok() @@ -1131,6 +1172,7 @@ async fn dispatch_validation_events_to_all( events: I, sender: &mut impl overseer::NetworkBridgeRxSenderTrait, _metrics: &Metrics, + approval_voting_parallel_enabled: bool, ) where I: IntoIterator>, I::IntoIter: Send, @@ -1160,7 +1202,11 @@ async fn dispatch_validation_events_to_all( for event in events { send_message!(event, StatementDistributionMessage); send_message!(event, BitfieldDistributionMessage); - send_message!(event, ApprovalDistributionMessage); + if approval_voting_parallel_enabled { + send_message!(event, ApprovalVotingParallelMessage); + } else { + send_message!(event, ApprovalDistributionMessage); + } send_message!(event, GossipSupportMessage); } } diff --git a/polkadot/node/network/bridge/src/rx/tests.rs b/polkadot/node/network/bridge/src/rx/tests.rs index 601dca5cb8a..a96817eb254 100644 --- a/polkadot/node/network/bridge/src/rx/tests.rs +++ b/polkadot/node/network/bridge/src/rx/tests.rs @@ -529,6 +529,7 @@ fn test_harness>( validation_service, collation_service, notification_sinks, + approval_voting_parallel_enabled: false, }; let network_bridge = run_network_in(bridge, context) diff --git a/polkadot/node/overseer/src/dummy.rs b/polkadot/node/overseer/src/dummy.rs index fc5f0070773..6f9cd9d0040 100644 --- a/polkadot/node/overseer/src/dummy.rs +++ b/polkadot/node/overseer/src/dummy.rs @@ -88,6 +88,7 @@ pub fn dummy_overseer_builder( DummySubsystem, DummySubsystem, DummySubsystem, + DummySubsystem, >, SubsystemError, > @@ -131,6 +132,7 @@ pub fn one_for_all_overseer_builder( Sub, Sub, Sub, + Sub, >, SubsystemError, > @@ -155,6 +157,7 @@ where + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> + + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> + Subsystem, SubsystemError> @@ -183,6 +186,7 @@ where .statement_distribution(subsystem.clone()) .approval_distribution(subsystem.clone()) .approval_voting(subsystem.clone()) + .approval_voting_parallel(subsystem.clone()) .gossip_support(subsystem.clone()) .dispute_coordinator(subsystem.clone()) .dispute_distribution(subsystem.clone()) diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 23adf4f4d8a..10a4320433a 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -76,13 +76,13 @@ use sc_client_api::{BlockImportNotification, BlockchainEvents, FinalityNotificat use self::messages::{BitfieldSigningMessage, PvfCheckerMessage}; use polkadot_node_subsystem_types::messages::{ - ApprovalDistributionMessage, ApprovalVotingMessage, AvailabilityDistributionMessage, - AvailabilityRecoveryMessage, AvailabilityStoreMessage, BitfieldDistributionMessage, - CandidateBackingMessage, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage, - CollationGenerationMessage, CollatorProtocolMessage, DisputeCoordinatorMessage, - DisputeDistributionMessage, GossipSupportMessage, NetworkBridgeRxMessage, - NetworkBridgeTxMessage, ProspectiveParachainsMessage, ProvisionerMessage, RuntimeApiMessage, - StatementDistributionMessage, + ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage, + AvailabilityDistributionMessage, AvailabilityRecoveryMessage, AvailabilityStoreMessage, + BitfieldDistributionMessage, CandidateBackingMessage, CandidateValidationMessage, + ChainApiMessage, ChainSelectionMessage, CollationGenerationMessage, CollatorProtocolMessage, + DisputeCoordinatorMessage, DisputeDistributionMessage, GossipSupportMessage, + NetworkBridgeRxMessage, NetworkBridgeTxMessage, ProspectiveParachainsMessage, + ProvisionerMessage, RuntimeApiMessage, StatementDistributionMessage, }; pub use polkadot_node_subsystem_types::{ @@ -550,6 +550,7 @@ pub struct Overseer { BitfieldDistributionMessage, StatementDistributionMessage, ApprovalDistributionMessage, + ApprovalVotingParallelMessage, GossipSupportMessage, DisputeDistributionMessage, CollationGenerationMessage, @@ -595,7 +596,19 @@ pub struct Overseer { RuntimeApiMessage, ])] approval_voting: ApprovalVoting, - + #[subsystem(blocking, message_capacity: 64000, ApprovalVotingParallelMessage, sends: [ + AvailabilityRecoveryMessage, + CandidateValidationMessage, + ChainApiMessage, + ChainSelectionMessage, + DisputeCoordinatorMessage, + RuntimeApiMessage, + NetworkBridgeTxMessage, + ApprovalVotingMessage, + ApprovalDistributionMessage, + ApprovalVotingParallelMessage, + ])] + approval_voting_parallel: ApprovalVotingParallel, #[subsystem(GossipSupportMessage, sends: [ NetworkBridgeTxMessage, NetworkBridgeRxMessage, // TODO @@ -613,6 +626,7 @@ pub struct Overseer { AvailabilityStoreMessage, AvailabilityRecoveryMessage, ChainSelectionMessage, + ApprovalVotingParallelMessage, ])] dispute_coordinator: DisputeCoordinator, diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index 8e78d8fc892..cb0add03e2e 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -950,7 +950,7 @@ fn test_prospective_parachains_msg() -> ProspectiveParachainsMessage { // Checks that `stop`, `broadcast_signal` and `broadcast_message` are implemented correctly. #[test] fn overseer_all_subsystems_receive_signals_and_messages() { - const NUM_SUBSYSTEMS: usize = 23; + const NUM_SUBSYSTEMS: usize = 24; // -4 for BitfieldSigning, GossipSupport, AvailabilityDistribution and PvfCheckerSubsystem. const NUM_SUBSYSTEMS_MESSAGED: usize = NUM_SUBSYSTEMS - 4; @@ -1028,6 +1028,11 @@ fn overseer_all_subsystems_receive_signals_and_messages() { handle .send_msg_anon(AllMessages::ApprovalDistribution(test_approval_distribution_msg())) .await; + handle + .send_msg_anon(AllMessages::ApprovalVotingParallel( + test_approval_distribution_msg().into(), + )) + .await; handle .send_msg_anon(AllMessages::ApprovalVoting(test_approval_voting_msg())) .await; @@ -1101,6 +1106,7 @@ fn context_holds_onto_message_until_enough_signals_received() { let (chain_selection_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (pvf_checker_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); let (prospective_parachains_bounded_tx, _) = metered::channel(CHANNEL_CAPACITY); + let (approval_voting_parallel_tx, _) = metered::channel(CHANNEL_CAPACITY); let (candidate_validation_unbounded_tx, _) = metered::unbounded(); let (candidate_backing_unbounded_tx, _) = metered::unbounded(); @@ -1125,6 +1131,7 @@ fn context_holds_onto_message_until_enough_signals_received() { let (chain_selection_unbounded_tx, _) = metered::unbounded(); let (pvf_checker_unbounded_tx, _) = metered::unbounded(); let (prospective_parachains_unbounded_tx, _) = metered::unbounded(); + let (approval_voting_parallel_unbounded_tx, _) = metered::unbounded(); let channels_out = ChannelsOut { candidate_validation: candidate_validation_bounded_tx.clone(), @@ -1150,6 +1157,7 @@ fn context_holds_onto_message_until_enough_signals_received() { chain_selection: chain_selection_bounded_tx.clone(), pvf_checker: pvf_checker_bounded_tx.clone(), prospective_parachains: prospective_parachains_bounded_tx.clone(), + approval_voting_parallel: approval_voting_parallel_tx.clone(), candidate_validation_unbounded: candidate_validation_unbounded_tx.clone(), candidate_backing_unbounded: candidate_backing_unbounded_tx.clone(), @@ -1174,6 +1182,7 @@ fn context_holds_onto_message_until_enough_signals_received() { chain_selection_unbounded: chain_selection_unbounded_tx.clone(), pvf_checker_unbounded: pvf_checker_unbounded_tx.clone(), prospective_parachains_unbounded: prospective_parachains_unbounded_tx.clone(), + approval_voting_parallel_unbounded: approval_voting_parallel_unbounded_tx.clone(), }; let (mut signal_tx, signal_rx) = metered::channel(CHANNEL_CAPACITY); diff --git a/polkadot/node/primitives/src/approval/mod.rs b/polkadot/node/primitives/src/approval/mod.rs index 79f4cfa9e0b..42342f9889a 100644 --- a/polkadot/node/primitives/src/approval/mod.rs +++ b/polkadot/node/primitives/src/approval/mod.rs @@ -124,7 +124,7 @@ pub mod v1 { } /// Metadata about a block which is now live in the approval protocol. - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct BlockApprovalMeta { /// The hash of the block. pub hash: Hash, diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 89f8212bf9d..8d50b54b2fd 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -116,6 +116,7 @@ polkadot-gossip-support = { optional = true, workspace = true, default-features polkadot-network-bridge = { optional = true, workspace = true, default-features = true } polkadot-node-collation-generation = { optional = true, workspace = true, default-features = true } polkadot-node-core-approval-voting = { optional = true, workspace = true, default-features = true } +polkadot-node-core-approval-voting-parallel = { optional = true, workspace = true, default-features = true } polkadot-node-core-av-store = { optional = true, workspace = true, default-features = true } polkadot-node-core-backing = { optional = true, workspace = true, default-features = true } polkadot-node-core-bitfield-signing = { optional = true, workspace = true, default-features = true } @@ -160,6 +161,7 @@ full-node = [ "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", + "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 79424356880..d029f3be53e 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -660,6 +660,8 @@ pub struct NewFullParams { #[allow(dead_code)] pub malus_finality_delay: Option, pub hwbench: Option, + /// Enable approval voting processing in parallel. + pub enable_approval_voting_parallel: bool, } #[cfg(feature = "full-node")] @@ -753,6 +755,7 @@ pub fn new_full< execute_workers_max_num, prepare_workers_soft_max_num, prepare_workers_hard_max_num, + enable_approval_voting_parallel, }: NewFullParams, ) -> Result { use polkadot_availability_recovery::FETCH_CHUNKS_THRESHOLD; @@ -784,6 +787,14 @@ pub fn new_full< Some(backoff) }; + // Running approval voting in parallel is enabled by default on all networks except Polkadot and + // Kusama, unless explicitly enabled by the commandline option. + // This is meant to be temporary until we have enough confidence in the new system to enable it + // by default on all networks. + let enable_approval_voting_parallel = (!config.chain_spec.is_kusama() && + !config.chain_spec.is_polkadot()) || + enable_approval_voting_parallel; + let disable_grandpa = config.disable_grandpa; let name = config.network.node_name.clone(); @@ -806,6 +817,7 @@ pub fn new_full< overseer_handle.clone(), metrics, Some(basics.task_manager.spawn_handle()), + enable_approval_voting_parallel, ) } else { SelectRelayChain::new_longest_chain(basics.backend.clone()) @@ -1016,6 +1028,7 @@ pub fn new_full< dispute_coordinator_config, chain_selection_config, fetch_chunks_threshold, + enable_approval_voting_parallel, }) }; diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 3c071e34fe1..a98b6bcb308 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -58,6 +58,9 @@ pub use polkadot_network_bridge::{ }; pub use polkadot_node_collation_generation::CollationGenerationSubsystem; pub use polkadot_node_core_approval_voting::ApprovalVotingSubsystem; +pub use polkadot_node_core_approval_voting_parallel::{ + ApprovalVotingParallelSubsystem, Metrics as ApprovalVotingParallelMetrics, +}; pub use polkadot_node_core_av_store::AvailabilityStoreSubsystem; pub use polkadot_node_core_backing::CandidateBackingSubsystem; pub use polkadot_node_core_bitfield_signing::BitfieldSigningSubsystem; @@ -139,9 +142,16 @@ pub struct ExtendedOverseerGenArgs { /// than the value put in here we always try to recovery availability from backers. /// The presence of this parameter here is needed to have different values per chain. pub fetch_chunks_threshold: Option, + /// Enable approval-voting-parallel subsystem and disable the standalone approval-voting and + /// approval-distribution subsystems. + pub enable_approval_voting_parallel: bool, } /// Obtain a prepared validator `Overseer`, that is initialized with all default values. +/// +/// The difference between this function and `validator_with_parallel_overseer_builder` is that this +/// function enables the standalone approval-voting and approval-distribution subsystems +/// and disables the approval-voting-parallel subsystem. pub fn validator_overseer_builder( OverseerGenArgs { runtime_client, @@ -174,6 +184,7 @@ pub fn validator_overseer_builder( dispute_coordinator_config, chain_selection_config, fetch_chunks_threshold, + enable_approval_voting_parallel, }: ExtendedOverseerGenArgs, ) -> Result< InitializedOverseerBuilder< @@ -203,6 +214,7 @@ pub fn validator_overseer_builder( CollatorProtocolSubsystem, ApprovalDistributionSubsystem, ApprovalVotingSubsystem, + DummySubsystem, GossipSupportSubsystem, DisputeCoordinatorSubsystem, DisputeDistributionSubsystem, @@ -223,7 +235,8 @@ where let spawner = SpawnGlue(spawner); let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; - + let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = + Metrics::register(registry)?; let builder = Overseer::builder() .network_bridge_tx(NetworkBridgeTxSubsystem::new( network_service.clone(), @@ -241,6 +254,7 @@ where peerset_protocol_names, notification_services, notification_sinks, + enable_approval_voting_parallel, )) .availability_distribution(AvailabilityDistributionSubsystem::new( keystore.clone(), @@ -310,18 +324,19 @@ where rand::rngs::StdRng::from_entropy(), )) .approval_distribution(ApprovalDistributionSubsystem::new( - Metrics::register(registry)?, + approval_voting_parallel_metrics.approval_distribution_metrics(), approval_voting_config.slot_duration_millis, Arc::new(RealAssignmentCriteria {}), )) .approval_voting(ApprovalVotingSubsystem::with_config( - approval_voting_config, + approval_voting_config.clone(), parachains_db.clone(), keystore.clone(), Box::new(sync_service.clone()), - Metrics::register(registry)?, + approval_voting_parallel_metrics.approval_voting_metrics(), Arc::new(spawner.clone()), )) + .approval_voting_parallel(DummySubsystem) .gossip_support(GossipSupportSubsystem::new( keystore.clone(), authority_discovery_service.clone(), @@ -332,6 +347,229 @@ where dispute_coordinator_config, keystore.clone(), Metrics::register(registry)?, + enable_approval_voting_parallel, + )) + .dispute_distribution(DisputeDistributionSubsystem::new( + keystore.clone(), + dispute_req_receiver, + authority_discovery_service.clone(), + Metrics::register(registry)?, + )) + .chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db)) + .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) + .activation_external_listeners(Default::default()) + .span_per_active_leaf(Default::default()) + .active_leaves(Default::default()) + .supports_parachains(runtime_client) + .metrics(metrics) + .spawner(spawner); + + let builder = if let Some(capacity) = overseer_message_channel_capacity_override { + builder.message_channel_capacity(capacity) + } else { + builder + }; + Ok(builder) +} + +/// Obtain a prepared validator `Overseer`, that is initialized with all default values. +/// +/// The difference between this function and `validator_overseer_builder` is that this +/// function enables the approval-voting-parallel subsystem and disables the standalone +/// approval-voting and approval-distribution subsystems. +pub fn validator_with_parallel_overseer_builder( + OverseerGenArgs { + runtime_client, + network_service, + sync_service, + authority_discovery_service, + collation_req_v1_receiver: _, + collation_req_v2_receiver: _, + available_data_req_receiver, + registry, + spawner, + is_parachain_node, + overseer_message_channel_capacity_override, + req_protocol_names, + peerset_protocol_names, + notification_services, + }: OverseerGenArgs, + ExtendedOverseerGenArgs { + keystore, + parachains_db, + candidate_validation_config, + availability_config, + pov_req_receiver, + chunk_req_v1_receiver, + chunk_req_v2_receiver, + statement_req_receiver, + candidate_req_v2_receiver, + approval_voting_config, + dispute_req_receiver, + dispute_coordinator_config, + chain_selection_config, + fetch_chunks_threshold, + enable_approval_voting_parallel, + }: ExtendedOverseerGenArgs, +) -> Result< + InitializedOverseerBuilder< + SpawnGlue, + Arc, + CandidateValidationSubsystem, + PvfCheckerSubsystem, + CandidateBackingSubsystem, + StatementDistributionSubsystem, + AvailabilityDistributionSubsystem, + AvailabilityRecoverySubsystem, + BitfieldSigningSubsystem, + BitfieldDistributionSubsystem, + ProvisionerSubsystem, + RuntimeApiSubsystem, + AvailabilityStoreSubsystem, + NetworkBridgeRxSubsystem< + Arc, + AuthorityDiscoveryService, + >, + NetworkBridgeTxSubsystem< + Arc, + AuthorityDiscoveryService, + >, + ChainApiSubsystem, + CollationGenerationSubsystem, + CollatorProtocolSubsystem, + DummySubsystem, + DummySubsystem, + ApprovalVotingParallelSubsystem, + GossipSupportSubsystem, + DisputeCoordinatorSubsystem, + DisputeDistributionSubsystem, + ChainSelectionSubsystem, + ProspectiveParachainsSubsystem, + >, + Error, +> +where + RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, + Spawner: 'static + SpawnNamed + Clone + Unpin, +{ + use polkadot_node_subsystem_util::metrics::Metrics; + + let metrics = ::register(registry)?; + let notification_sinks = Arc::new(Mutex::new(HashMap::new())); + + let spawner = SpawnGlue(spawner); + + let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; + let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = + Metrics::register(registry)?; + let builder = Overseer::builder() + .network_bridge_tx(NetworkBridgeTxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + network_bridge_metrics.clone(), + req_protocol_names.clone(), + peerset_protocol_names.clone(), + notification_sinks.clone(), + )) + .network_bridge_rx(NetworkBridgeRxSubsystem::new( + network_service.clone(), + authority_discovery_service.clone(), + Box::new(sync_service.clone()), + network_bridge_metrics, + peerset_protocol_names, + notification_services, + notification_sinks, + enable_approval_voting_parallel, + )) + .availability_distribution(AvailabilityDistributionSubsystem::new( + keystore.clone(), + IncomingRequestReceivers { + pov_req_receiver, + chunk_req_v1_receiver, + chunk_req_v2_receiver, + }, + req_protocol_names.clone(), + Metrics::register(registry)?, + )) + .availability_recovery(AvailabilityRecoverySubsystem::for_validator( + fetch_chunks_threshold, + available_data_req_receiver, + &req_protocol_names, + Metrics::register(registry)?, + )) + .availability_store(AvailabilityStoreSubsystem::new( + parachains_db.clone(), + availability_config, + Box::new(sync_service.clone()), + Metrics::register(registry)?, + )) + .bitfield_distribution(BitfieldDistributionSubsystem::new(Metrics::register(registry)?)) + .bitfield_signing(BitfieldSigningSubsystem::new( + keystore.clone(), + Metrics::register(registry)?, + )) + .candidate_backing(CandidateBackingSubsystem::new( + keystore.clone(), + Metrics::register(registry)?, + )) + .candidate_validation(CandidateValidationSubsystem::with_config( + candidate_validation_config, + keystore.clone(), + Metrics::register(registry)?, // candidate-validation metrics + Metrics::register(registry)?, // validation host metrics + )) + .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) + .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) + .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collator_protocol({ + let side = match is_parachain_node { + IsParachainNode::Collator(_) | IsParachainNode::FullNode => + return Err(Error::Overseer(SubsystemError::Context( + "build validator overseer for parachain node".to_owned(), + ))), + IsParachainNode::No => ProtocolSide::Validator { + keystore: keystore.clone(), + eviction_policy: Default::default(), + metrics: Metrics::register(registry)?, + }, + }; + CollatorProtocolSubsystem::new(side) + }) + .provisioner(ProvisionerSubsystem::new(Metrics::register(registry)?)) + .runtime_api(RuntimeApiSubsystem::new( + runtime_client.clone(), + Metrics::register(registry)?, + spawner.clone(), + )) + .statement_distribution(StatementDistributionSubsystem::new( + keystore.clone(), + statement_req_receiver, + candidate_req_v2_receiver, + Metrics::register(registry)?, + rand::rngs::StdRng::from_entropy(), + )) + .approval_distribution(DummySubsystem) + .approval_voting(DummySubsystem) + .approval_voting_parallel(ApprovalVotingParallelSubsystem::with_config( + approval_voting_config, + parachains_db.clone(), + keystore.clone(), + Box::new(sync_service.clone()), + approval_voting_parallel_metrics, + spawner.clone(), + overseer_message_channel_capacity_override, + )) + .gossip_support(GossipSupportSubsystem::new( + keystore.clone(), + authority_discovery_service.clone(), + Metrics::register(registry)?, + )) + .dispute_coordinator(DisputeCoordinatorSubsystem::new( + parachains_db.clone(), + dispute_coordinator_config, + keystore.clone(), + Metrics::register(registry)?, + enable_approval_voting_parallel, )) .dispute_distribution(DisputeDistributionSubsystem::new( keystore.clone(), @@ -407,6 +645,7 @@ pub fn collator_overseer_builder( DummySubsystem, DummySubsystem, DummySubsystem, + DummySubsystem, >, Error, > @@ -439,6 +678,7 @@ where peerset_protocol_names, notification_services, notification_sinks, + false, )) .availability_distribution(DummySubsystem) .availability_recovery(AvailabilityRecoverySubsystem::for_collator( @@ -481,6 +721,7 @@ where .statement_distribution(DummySubsystem) .approval_distribution(DummySubsystem) .approval_voting(DummySubsystem) + .approval_voting_parallel(DummySubsystem) .gossip_support(DummySubsystem) .dispute_coordinator(DummySubsystem) .dispute_distribution(DummySubsystem) @@ -537,9 +778,15 @@ impl OverseerGen for ValidatorOverseerGen { "create validator overseer as mandatory extended arguments were not provided" .to_owned(), )))?; - validator_overseer_builder(args, ext_args)? - .build_with_connector(connector) - .map_err(|e| e.into()) + if ext_args.enable_approval_voting_parallel { + validator_with_parallel_overseer_builder(args, ext_args)? + .build_with_connector(connector) + .map_err(|e| e.into()) + } else { + validator_overseer_builder(args, ext_args)? + .build_with_connector(connector) + .map_err(|e| e.into()) + } } } diff --git a/polkadot/node/service/src/relay_chain_selection.rs b/polkadot/node/service/src/relay_chain_selection.rs index c0b1ce8b0eb..e48874f01ca 100644 --- a/polkadot/node/service/src/relay_chain_selection.rs +++ b/polkadot/node/service/src/relay_chain_selection.rs @@ -39,8 +39,8 @@ use super::{HeaderProvider, HeaderProviderProvider}; use futures::channel::oneshot; use polkadot_node_primitives::MAX_FINALITY_LAG as PRIMITIVES_MAX_FINALITY_LAG; use polkadot_node_subsystem::messages::{ - ApprovalDistributionMessage, ApprovalVotingMessage, ChainSelectionMessage, - DisputeCoordinatorMessage, HighestApprovedAncestorBlock, + ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage, + ChainSelectionMessage, DisputeCoordinatorMessage, HighestApprovedAncestorBlock, }; use polkadot_node_subsystem_util::metrics::{self, prometheus}; use polkadot_overseer::{AllMessages, Handle}; @@ -169,6 +169,7 @@ where overseer: Handle, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, ) -> Self { gum::debug!(target: LOG_TARGET, "Using dispute aware relay-chain selection algorithm",); @@ -179,6 +180,7 @@ where overseer, metrics, spawn_handle, + approval_voting_parallel_enabled, )), } } @@ -230,6 +232,7 @@ pub struct SelectRelayChainInner { overseer: OH, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, } impl SelectRelayChainInner @@ -244,8 +247,15 @@ where overseer: OH, metrics: Metrics, spawn_handle: Option, + approval_voting_parallel_enabled: bool, ) -> Self { - SelectRelayChainInner { backend, overseer, metrics, spawn_handle } + SelectRelayChainInner { + backend, + overseer, + metrics, + spawn_handle, + approval_voting_parallel_enabled, + } } fn block_header(&self, hash: Hash) -> Result { @@ -284,6 +294,7 @@ where overseer: self.overseer.clone(), metrics: self.metrics.clone(), spawn_handle: self.spawn_handle.clone(), + approval_voting_parallel_enabled: self.approval_voting_parallel_enabled, } } } @@ -448,13 +459,25 @@ where // 2. Constrain according to `ApprovedAncestor`. let (subchain_head, subchain_number, subchain_block_descriptions) = { let (tx, rx) = oneshot::channel(); - overseer - .send_msg( - ApprovalVotingMessage::ApprovedAncestor(subchain_head, target_number, tx), - std::any::type_name::(), - ) - .await; - + if self.approval_voting_parallel_enabled { + overseer + .send_msg( + ApprovalVotingParallelMessage::ApprovedAncestor( + subchain_head, + target_number, + tx, + ), + std::any::type_name::(), + ) + .await; + } else { + overseer + .send_msg( + ApprovalVotingMessage::ApprovedAncestor(subchain_head, target_number, tx), + std::any::type_name::(), + ) + .await; + } match rx .await .map_err(Error::ApprovedAncestorCanceled) @@ -476,13 +499,23 @@ where // task for sending the message to not block here and delay finality. if let Some(spawn_handle) = &self.spawn_handle { let mut overseer_handle = self.overseer.clone(); + let approval_voting_parallel_enabled = self.approval_voting_parallel_enabled; let lag_update_task = async move { - overseer_handle - .send_msg( - ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag), - std::any::type_name::(), - ) - .await; + if approval_voting_parallel_enabled { + overseer_handle + .send_msg( + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag), + std::any::type_name::(), + ) + .await; + } else { + overseer_handle + .send_msg( + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag), + std::any::type_name::(), + ) + .await; + } }; spawn_handle.spawn( diff --git a/polkadot/node/service/src/tests.rs b/polkadot/node/service/src/tests.rs index 195432bcb75..85b750ddad6 100644 --- a/polkadot/node/service/src/tests.rs +++ b/polkadot/node/service/src/tests.rs @@ -83,6 +83,7 @@ fn test_harness>( context.sender().clone(), Default::default(), None, + false, ); let target_hash = case_vars.target_block; diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index ae798cf2640..293df9f6e6d 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -80,6 +80,7 @@ serde_yaml = { workspace = true } serde_json = { workspace = true } polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-node-core-approval-voting-parallel = { workspace = true, default-features = true } polkadot-approval-distribution = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true } diff --git a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml index cae1a30914d..1423d324df3 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml @@ -9,6 +9,7 @@ TestConfiguration: coalesce_tranche_diff: 12 num_no_shows_per_candidate: 10 workdir_prefix: "/tmp/" + approval_voting_parallel_enabled: false n_validators: 500 n_cores: 100 min_pov_size: 1120 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml index 7edb48e302a..87c6103a5d0 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml @@ -9,6 +9,7 @@ TestConfiguration: coalesce_tranche_diff: 12 num_no_shows_per_candidate: 0 workdir_prefix: "/tmp" + approval_voting_parallel_enabled: true n_validators: 500 n_cores: 100 min_pov_size: 1120 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml index 7c24f50e6af..5e2ea3817d1 100644 --- a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml @@ -8,6 +8,7 @@ TestConfiguration: stop_when_approved: true coalesce_tranche_diff: 12 num_no_shows_per_candidate: 0 + approval_voting_parallel_enabled: false workdir_prefix: "/tmp/" n_validators: 500 n_cores: 100 diff --git a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs index 4b2b9169682..a3a475ac6b9 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/helpers.rs @@ -21,8 +21,11 @@ use polkadot_node_network_protocol::{ View, }; use polkadot_node_primitives::approval::time::{Clock, SystemClock, Tick}; +use polkadot_node_subsystem::messages::{ + ApprovalDistributionMessage, ApprovalVotingParallelMessage, +}; use polkadot_node_subsystem_types::messages::{ - network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, NetworkBridgeEvent, + network_bridge_event::NewGossipTopology, NetworkBridgeEvent, }; use polkadot_overseer::AllMessages; use polkadot_primitives::{ @@ -121,6 +124,7 @@ pub fn generate_topology(test_authorities: &TestAuthorities) -> SessionGridTopol pub fn generate_new_session_topology( test_authorities: &TestAuthorities, test_node: ValidatorIndex, + approval_voting_parallel_enabled: bool, ) -> Vec { let topology = generate_topology(test_authorities); @@ -129,14 +133,29 @@ pub fn generate_new_session_topology( topology, local_index: Some(test_node), }); - vec![AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event))] + vec![if approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + event, + )) + } else { + 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 { +pub fn generate_peer_view_change_for( + block_hash: Hash, + peer_id: PeerId, + approval_voting_parallel_enabled: bool, +) -> AllMessages { let network = NetworkBridgeEvent::PeerViewChange(peer_id, View::new([block_hash], 0)); - - AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) + if approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + network, + )) + } else { + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) + } } /// Helper function to create a a signature for the block header. diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs index 9d85039b888..29ebc4a419a 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -49,20 +49,21 @@ use itertools::Itertools; use orchestra::TimeoutExt; use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait}; use polkadot_approval_distribution::ApprovalDistribution; +use polkadot_node_core_approval_voting_parallel::ApprovalVotingParallelSubsystem; use polkadot_node_primitives::approval::time::{ slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock, }; use polkadot_node_core_approval_voting::{ - ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics, - RealAssignmentCriteria, + ApprovalVotingSubsystem, Config as ApprovalVotingConfig, RealAssignmentCriteria, }; 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::{ + messages::{ApprovalDistributionMessage, ApprovalVotingMessage, ApprovalVotingParallelMessage}, + 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, ValidatorId, @@ -138,6 +139,9 @@ pub struct ApprovalsOptions { /// The number of no shows per candidate #[clap(short, long, default_value_t = 0)] pub num_no_shows_per_candidate: u32, + /// Enable approval voting parallel. + #[clap(short, long, default_value_t = true)] + pub approval_voting_parallel_enabled: bool, } impl ApprovalsOptions { @@ -272,7 +276,7 @@ pub struct ApprovalTestState { /// Total unique sent messages. total_unique_messages: Arc, /// Approval voting metrics. - approval_voting_metrics: ApprovalVotingMetrics, + approval_voting_parallel_metrics: polkadot_node_core_approval_voting_parallel::Metrics, /// The delta ticks from the tick the messages were generated to the the time we start this /// message. delta_tick_from_generated: Arc, @@ -330,7 +334,10 @@ impl ApprovalTestState { 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) + approval_voting_parallel_metrics: + polkadot_node_core_approval_voting_parallel::Metrics::try_register( + &dependencies.registry, + ) .unwrap(), delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)), configuration: configuration.clone(), @@ -456,6 +463,14 @@ impl ApprovalTestState { }) .collect() } + + fn subsystem_name(&self) -> &'static str { + if self.options.approval_voting_parallel_enabled { + "approval-voting-parallel-subsystem" + } else { + "approval-distribution-subsystem" + } + } } impl ApprovalTestState { @@ -597,13 +612,16 @@ impl PeerMessageProducer { // 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; + let msg = if self.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel( + ApprovalVotingParallelMessage::GetApprovalSignatures(HashSet::new(), tx), + ) + } else { + AllMessages::ApprovalDistribution( + ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx), + ) + }; + self.send_overseer_message(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 "); @@ -743,7 +761,11 @@ impl PeerMessageProducer { 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); + let view_update = generate_peer_view_change_for( + block_info.hash, + *peer_id, + self.state.options.approval_voting_parallel_enabled, + ); self.send_overseer_message(view_update, validator, None).await; } @@ -808,24 +830,12 @@ fn build_overseer( 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(), - Arc::new(system_clock.clone()), - Arc::new(SpawnGlue(spawn_task_handle.clone())), - ); + let keystore = Arc::new(keystore); + let db = Arc::new(db); - let approval_distribution = ApprovalDistribution::new_with_clock( - Metrics::register(Some(&dependencies.registry)).unwrap(), - SLOT_DURATION_MILLIS, - Box::new(system_clock.clone()), - Arc::new(RealAssignmentCriteria {}), - ); let mock_chain_api = MockChainApi::new(state.build_chain_api_state()); - let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock }; + let mock_chain_selection = + MockChainSelection { state: state.clone(), clock: system_clock.clone() }; let mock_runtime_api = MockRuntimeApi::new( config.clone(), state.test_authorities.clone(), @@ -840,11 +850,14 @@ fn build_overseer( network_interface.subsystem_sender(), state.test_authorities.clone(), ); - let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None); + let mock_rx_bridge = MockNetworkBridgeRx::new( + network_receiver, + None, + state.options.approval_voting_parallel_enabled, + ); 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) + let task_handle = spawn_task_handle.clone(); + let dummy = dummy_builder!(task_handle, overseer_metrics) .replace_chain_api(|_| mock_chain_api) .replace_chain_selection(|_| mock_chain_selection) .replace_runtime_api(|_| mock_runtime_api) @@ -853,8 +866,45 @@ fn build_overseer( .replace_availability_recovery(|_| MockAvailabilityRecovery::new()) .replace_candidate_validation(|_| MockCandidateValidation::new()); - let (overseer, raw_handle) = - dummy.build_with_connector(overseer_connector).expect("Should not fail"); + let (overseer, raw_handle) = if state.options.approval_voting_parallel_enabled { + let approval_voting_parallel = ApprovalVotingParallelSubsystem::with_config_and_clock( + TEST_CONFIG, + db.clone(), + keystore.clone(), + Box::new(TestSyncOracle {}), + state.approval_voting_parallel_metrics.clone(), + Arc::new(system_clock.clone()), + SpawnGlue(spawn_task_handle.clone()), + None, + ); + dummy + .replace_approval_voting_parallel(|_| approval_voting_parallel) + .build_with_connector(overseer_connector) + .expect("Should not fail") + } else { + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + TEST_CONFIG, + db.clone(), + keystore.clone(), + Box::new(TestSyncOracle {}), + state.approval_voting_parallel_metrics.approval_voting_metrics(), + Arc::new(system_clock.clone()), + Arc::new(SpawnGlue(spawn_task_handle.clone())), + ); + + let approval_distribution = ApprovalDistribution::new_with_clock( + state.approval_voting_parallel_metrics.approval_distribution_metrics(), + TEST_CONFIG.slot_duration_millis, + Arc::new(system_clock.clone()), + Arc::new(RealAssignmentCriteria {}), + ); + + dummy + .replace_approval_voting(|_| approval_voting) + .replace_approval_distribution(|_| approval_distribution) + .build_with_connector(overseer_connector) + .expect("Should not fail") + }; let overseer_handle = OverseerHandleReal::new(raw_handle); (overseer, overseer_handle) @@ -943,11 +993,18 @@ pub async fn bench_approvals_run( // 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(|e| { - AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(e)) + if state.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::NetworkBridgeUpdate( + e, + )) + } else { + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(e)) + } }); initialization_messages.extend(generate_new_session_topology( &state.test_authorities, ValidatorIndex(NODE_UNDER_TEST), + state.options.approval_voting_parallel_enabled, )); for message in initialization_messages { env.send_message(message).await; @@ -1012,7 +1069,14 @@ pub async fn bench_approvals_run( 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")), + Some(( + "subsystem_name", + if state.options.approval_voting_parallel_enabled { + "approval-voting-parallel-subsystem" + } else { + "approval-distribution-subsystem" + }, + )), |value| { gum::debug!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); value >= at_least_messages as f64 @@ -1029,11 +1093,22 @@ pub async fn bench_approvals_run( 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 msg = if state.options.approval_voting_parallel_enabled { + AllMessages::ApprovalVotingParallel( + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ), + ) + } else { + AllMessages::ApprovalVoting( + ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ), + ) + }; + env.send_message(msg).await; let result = rx.await.unwrap(); @@ -1057,7 +1132,7 @@ pub async fn bench_approvals_run( 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")), + Some(("subsystem_name", state.subsystem_name())), |value| { gum::debug!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); value >= at_least_messages as f64 @@ -1098,5 +1173,8 @@ pub async fn bench_approvals_run( state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) ); - env.collect_resource_usage(&["approval-distribution", "approval-voting"]) + env.collect_resource_usage( + &["approval-distribution", "approval-voting", "approval-voting-parallel"], + true, + ) } diff --git a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs index 32dc8ae2c8d..f28adff315f 100644 --- a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs @@ -210,7 +210,7 @@ pub fn prepare_test( state.test_authorities.clone(), ); let network_bridge_rx = - network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_v2_cfg)); + network_bridge::MockNetworkBridgeRx::new(network_receiver, Some(chunk_req_v2_cfg), false); let runtime_api = MockRuntimeApi::new( state.config.clone(), @@ -372,7 +372,7 @@ pub async fn benchmark_availability_read( ); env.stop().await; - env.collect_resource_usage(&["availability-recovery"]) + env.collect_resource_usage(&["availability-recovery"], false) } pub async fn benchmark_availability_write( @@ -506,9 +506,8 @@ pub async fn benchmark_availability_write( ); env.stop().await; - env.collect_resource_usage(&[ - "availability-distribution", - "bitfield-distribution", - "availability-store", - ]) + env.collect_resource_usage( + &["availability-distribution", "bitfield-distribution", "availability-store"], + false, + ) } diff --git a/polkadot/node/subsystem-bench/src/lib/display.rs b/polkadot/node/subsystem-bench/src/lib/display.rs index b153d54a7c3..c47dd9a0790 100644 --- a/polkadot/node/subsystem-bench/src/lib/display.rs +++ b/polkadot/node/subsystem-bench/src/lib/display.rs @@ -96,6 +96,23 @@ pub struct TestMetric { value: f64, } +impl TestMetric { + pub fn name(&self) -> &str { + &self.name + } + + pub fn value(&self) -> f64 { + self.value + } + + pub fn label_value(&self, label_name: &str) -> Option<&str> { + self.label_names + .iter() + .position(|name| name == label_name) + .and_then(|index| self.label_values.get(index).map(|s| s.as_str())) + } +} + impl Display for TestMetric { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( diff --git a/polkadot/node/subsystem-bench/src/lib/environment.rs b/polkadot/node/subsystem-bench/src/lib/environment.rs index a63f90da50b..4de683ad648 100644 --- a/polkadot/node/subsystem-bench/src/lib/environment.rs +++ b/polkadot/node/subsystem-bench/src/lib/environment.rs @@ -351,10 +351,14 @@ impl TestEnvironment { } } - pub fn collect_resource_usage(&self, subsystems_under_test: &[&str]) -> BenchmarkUsage { + pub fn collect_resource_usage( + &self, + subsystems_under_test: &[&str], + break_down_cpu_usage_per_task: bool, + ) -> BenchmarkUsage { BenchmarkUsage { network_usage: self.network_usage(), - cpu_usage: self.cpu_usage(subsystems_under_test), + cpu_usage: self.cpu_usage(subsystems_under_test, break_down_cpu_usage_per_task), } } @@ -378,7 +382,11 @@ impl TestEnvironment { ] } - fn cpu_usage(&self, subsystems_under_test: &[&str]) -> Vec { + fn cpu_usage( + &self, + subsystems_under_test: &[&str], + break_down_per_task: bool, + ) -> Vec { let test_metrics = super::display::parse_metrics(self.registry()); let mut usage = vec![]; let num_blocks = self.config().num_blocks as f64; @@ -392,6 +400,22 @@ impl TestEnvironment { total: total_cpu, per_block: total_cpu / num_blocks, }); + + if break_down_per_task { + for metric in subsystem_cpu_metrics.all() { + if metric.name() != "substrate_tasks_polling_duration_sum" { + continue; + } + + if let Some(task_name) = metric.label_value("task_name") { + usage.push(ResourceUsage { + resource_name: format!("{}/{}", subsystem, task_name), + total: metric.value(), + per_block: metric.value() / num_blocks, + }); + } + } + } } let test_env_cpu_metrics = diff --git a/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs index 8783b35f1c0..092a8fc5f4c 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/dummy.rs @@ -96,5 +96,6 @@ mock!(NetworkBridgeTx); mock!(ChainApi); mock!(ChainSelection); mock!(ApprovalVoting); +mock!(ApprovalVotingParallel); mock!(ApprovalDistribution); mock!(RuntimeApi); diff --git a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs index da4ac05e33b..2ca47d9fc08 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs @@ -47,6 +47,7 @@ macro_rules! dummy_builder { // All subsystem except approval_voting and approval_distribution are mock subsystems. Overseer::builder() .approval_voting(MockApprovalVoting {}) + .approval_voting_parallel(MockApprovalVotingParallel {}) .approval_distribution(MockApprovalDistribution {}) .availability_recovery(MockAvailabilityRecovery {}) .candidate_validation(MockCandidateValidation {}) diff --git a/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs index d70953926d1..f5474a61e3d 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/network_bridge.rs @@ -24,13 +24,13 @@ use crate::{ 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, - StatementDistributionMessage, + ApprovalDistributionMessage, ApprovalVotingParallelMessage, NetworkBridgeTxMessage, }, + overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::{ + messages::{BitfieldDistributionMessage, NetworkBridgeEvent, StatementDistributionMessage}, OverseerSignal, }; use sc_network::{request_responses::ProtocolConfig, RequestFailure}; @@ -57,6 +57,8 @@ pub struct MockNetworkBridgeRx { network_receiver: NetworkInterfaceReceiver, /// Chunk request sender chunk_request_sender: Option, + /// Approval voting parallel enabled. + approval_voting_parallel_enabled: bool, } impl MockNetworkBridgeTx { @@ -73,8 +75,9 @@ impl MockNetworkBridgeRx { pub fn new( network_receiver: NetworkInterfaceReceiver, chunk_request_sender: Option, + approval_voting_parallel_enabled: bool, ) -> MockNetworkBridgeRx { - Self { network_receiver, chunk_request_sender } + Self { network_receiver, chunk_request_sender, approval_voting_parallel_enabled } } } @@ -199,9 +202,15 @@ impl MockNetworkBridgeRx { 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; + if self.approval_voting_parallel_enabled { + ctx.send_message( + ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } else { + ctx.send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } } Versioned::V3( polkadot_node_network_protocol::v3::ValidationProtocol::StatementDistribution(msg) diff --git a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs index bd47505f56a..e2d50f28568 100644 --- a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs @@ -135,7 +135,8 @@ fn build_overseer( network_interface.subsystem_sender(), state.test_authorities.clone(), ); - let network_bridge_rx = MockNetworkBridgeRx::new(network_receiver, Some(candidate_req_cfg)); + let network_bridge_rx = + MockNetworkBridgeRx::new(network_receiver, Some(candidate_req_cfg), false); let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) .replace_runtime_api(|_| mock_runtime_api) @@ -445,5 +446,5 @@ pub async fn benchmark_statement_distribution( ); env.stop().await; - env.collect_resource_usage(&["statement-distribution"]) + env.collect_resource_usage(&["statement-distribution"], false) } diff --git a/polkadot/node/subsystem-bench/src/lib/usage.rs b/polkadot/node/subsystem-bench/src/lib/usage.rs index 883e9aa7ad0..5f691ae2db3 100644 --- a/polkadot/node/subsystem-bench/src/lib/usage.rs +++ b/polkadot/node/subsystem-bench/src/lib/usage.rs @@ -32,14 +32,14 @@ impl std::fmt::Display for BenchmarkUsage { write!( f, "\n{}\n{}\n\n{}\n{}\n", - format!("{:<32}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), + format!("{:<64}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), self.network_usage .iter() .map(|v| v.to_string()) .sorted() .collect::>() .join("\n"), - format!("{:<32}{:>12}{:>12}", "CPU usage, seconds", "total", "per block").blue(), + format!("{:<64}{:>12}{:>12}", "CPU usage, seconds", "total", "per block").blue(), self.cpu_usage .iter() .map(|v| v.to_string()) @@ -134,7 +134,7 @@ pub struct ResourceUsage { impl std::fmt::Display for ResourceUsage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{:<32}{:>12.4}{:>12.4}", self.resource_name.cyan(), self.total, self.per_block) + write!(f, "{:<64}{:>12.4}{:>12.4}", self.resource_name.cyan(), self.total, self.per_block) } } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 854a9da158b..fafc700e739 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -955,6 +955,103 @@ pub struct BlockDescription { pub candidates: Vec, } +/// Message to the approval voting parallel subsystem running both approval-distribution and +/// approval-voting logic in parallel. This is a combination of all the messages ApprovalVoting and +/// ApprovalDistribution subsystems can receive. +/// +/// The reason this exists is, so that we can keep both modes of running in the same polkadot +/// binary, based on the value of `--approval-voting-parallel-enabled`, we decide if we run with two +/// different subsystems for approval-distribution and approval-voting or run the approval-voting +/// parallel which has several parallel workers for the approval-distribution and a worker for +/// approval-voting. +/// +/// This is meant to be a temporary state until we can safely remove running the two subsystems +/// individually. +#[derive(Debug, derive_more::From)] +pub enum ApprovalVotingParallelMessage { + /// Gets mapped into `ApprovalVotingMessage::ApprovedAncestor` + ApprovedAncestor(Hash, BlockNumber, oneshot::Sender>), + + /// Gets mapped into `ApprovalVotingMessage::GetApprovalSignaturesForCandidate` + GetApprovalSignaturesForCandidate( + CandidateHash, + oneshot::Sender, ValidatorSignature)>>, + ), + /// Gets mapped into `ApprovalDistributionMessage::NewBlocks` + NewBlocks(Vec), + /// Gets mapped into `ApprovalDistributionMessage::DistributeAssignment` + DistributeAssignment(IndirectAssignmentCertV2, CandidateBitfield), + /// Gets mapped into `ApprovalDistributionMessage::DistributeApproval` + DistributeApproval(IndirectSignedApprovalVoteV2), + /// An update from the network bridge, gets mapped into + /// `ApprovalDistributionMessage::NetworkBridgeUpdate` + #[from] + NetworkBridgeUpdate(NetworkBridgeEvent), + + /// Gets mapped into `ApprovalDistributionMessage::GetApprovalSignatures` + GetApprovalSignatures( + HashSet<(Hash, CandidateIndex)>, + oneshot::Sender, ValidatorSignature)>>, + ), + /// Gets mapped into `ApprovalDistributionMessage::ApprovalCheckingLagUpdate` + ApprovalCheckingLagUpdate(BlockNumber), +} + +impl TryFrom for ApprovalVotingMessage { + type Error = (); + + fn try_from(msg: ApprovalVotingParallelMessage) -> Result { + match msg { + ApprovalVotingParallelMessage::ApprovedAncestor(hash, number, tx) => + Ok(ApprovalVotingMessage::ApprovedAncestor(hash, number, tx)), + ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate(candidate, tx) => + Ok(ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate, tx)), + _ => Err(()), + } + } +} + +impl TryFrom for ApprovalDistributionMessage { + type Error = (); + + fn try_from(msg: ApprovalVotingParallelMessage) -> Result { + match msg { + ApprovalVotingParallelMessage::NewBlocks(blocks) => + Ok(ApprovalDistributionMessage::NewBlocks(blocks)), + ApprovalVotingParallelMessage::DistributeAssignment(assignment, claimed_cores) => + Ok(ApprovalDistributionMessage::DistributeAssignment(assignment, claimed_cores)), + ApprovalVotingParallelMessage::DistributeApproval(vote) => + Ok(ApprovalDistributionMessage::DistributeApproval(vote)), + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg) => + Ok(ApprovalDistributionMessage::NetworkBridgeUpdate(msg)), + ApprovalVotingParallelMessage::GetApprovalSignatures(candidate_indicies, tx) => + Ok(ApprovalDistributionMessage::GetApprovalSignatures(candidate_indicies, tx)), + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag) => + Ok(ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag)), + _ => Err(()), + } + } +} + +impl From for ApprovalVotingParallelMessage { + fn from(msg: ApprovalDistributionMessage) -> Self { + match msg { + ApprovalDistributionMessage::NewBlocks(blocks) => + ApprovalVotingParallelMessage::NewBlocks(blocks), + ApprovalDistributionMessage::DistributeAssignment(cert, bitfield) => + ApprovalVotingParallelMessage::DistributeAssignment(cert, bitfield), + ApprovalDistributionMessage::DistributeApproval(vote) => + ApprovalVotingParallelMessage::DistributeApproval(vote), + ApprovalDistributionMessage::NetworkBridgeUpdate(msg) => + ApprovalVotingParallelMessage::NetworkBridgeUpdate(msg), + ApprovalDistributionMessage::GetApprovalSignatures(candidate_indicies, tx) => + ApprovalVotingParallelMessage::GetApprovalSignatures(candidate_indicies, tx), + ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag) => + ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag), + } + } +} + /// Response type to `ApprovalVotingMessage::ApprovedAncestor`. #[derive(Clone, Debug)] pub struct HighestApprovedAncestorBlock { diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index b1238788486..f879aa93df9 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -101,6 +101,7 @@ pub fn new_full( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ), sc_network::config::NetworkBackendType::Litep2p => @@ -123,6 +124,7 @@ pub fn new_full( execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ), } diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs index e8588274df2..4660b4d38f7 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs @@ -98,6 +98,7 @@ fn main() -> Result<()> { execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index 7198a831a47..3dfa714e6d1 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -100,6 +100,7 @@ fn main() -> Result<()> { execute_workers_max_num: None, prepare_workers_hard_max_num: None, prepare_workers_soft_max_num: None, + enable_approval_voting_parallel: false, }, ) .map_err(|e| e.to_string())?; diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md new file mode 100644 index 00000000000..84661b7bf9b --- /dev/null +++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting-parallel.md @@ -0,0 +1,30 @@ +# Approval voting parallel + +The approval-voting-parallel subsystem acts as an orchestrator for the tasks handled by the [Approval Voting](approval-voting.md) +and [Approval Distribution](approval-distribution.md) subsystems. Initially, these two systems operated separately and interacted +with each other and other subsystems through orchestra. + +With approval-voting-parallel, we have a single subsystem that creates two types of workers: +- Four approval-distribution workers that operate in parallel, each handling tasks based on the validator_index of the message + originator. +- One approval-voting worker that performs the tasks previously managed by the standalone approval-voting subsystem. + +This subsystem does not maintain any state. Instead, it functions as an orchestrator that: +- Spawns and initializes each workers. +- Forwards each message and signal to the appropriate worker. +- Aggregates results for messages that require input from more than one worker, such as GetApprovalSignatures. + +## Forwarding logic + +The messages received and forwarded by approval-voting-parallel split in three categories: +- Signals which need to be forwarded to all workers. +- Messages that only the `approval-voting` worker needs to handle, `ApprovalVotingParallelMessage::ApprovedAncestor` + and `ApprovalVotingParallelMessage::GetApprovalSignaturesForCandidate` +- Control messages that all `approval-distribution` workers need to receive `ApprovalVotingParallelMessage::NewBlocks`, + `ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate` and all network bridge variants `ApprovalVotingParallelMessage::NetworkBridgeUpdate` + except `ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage)` +- Data messages `ApprovalVotingParallelMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage)` which need to be sent + just to a single `approval-distribution` worker based on the ValidatorIndex. The logic for assigning the work is: + ``` + assigned_worker_index = validator_index % number_of_workers; + ``` diff --git a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml index 19c7015403d..113de0e73aa 100644 --- a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml +++ b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml @@ -18,7 +18,7 @@ requests = { memory = "2G", cpu = "1" } [[relaychain.node_groups]] name = "alice" - args = [ "-lparachain=trace,runtime=debug" ] + args = [ "-lparachain=debug,runtime=debug" ] count = 13 [[parachains]] diff --git a/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml new file mode 100644 index 00000000000..c035e23639c --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.toml @@ -0,0 +1,120 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 4 + relay_vrf_modulo_samples = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + max_approval_coalesce_count = 5 + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "alice" + args = ["-lparachain=debug,runtime=debug", "--enable-approval-voting-parallel"] + count = 8 + + [[relaychain.node_groups]] + name = "bob" + args = ["-lparachain=debug,runtime=debug"] + count = 7 + +[[parachains]] +id = 2000 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" + + [parachains.collator] + name = "collator01" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] + +[[parachains]] +id = 2001 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=10" + + [parachains.collator] + name = "collator02" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2001", "--pvf-complexity=10"] + +[[parachains]] +id = 2002 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=100" + + [parachains.collator] + name = "collator03" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2002", "--pvf-complexity=100"] + +[[parachains]] +id = 2003 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=300" + + [parachains.collator] + name = "collator04" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--parachain-id=2003", "--pvf-complexity=300"] + +[[parachains]] +id = 2004 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator05" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2004", "--pvf-complexity=300"] + +[[parachains]] +id = 2005 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=400" + + [parachains.collator] + name = "collator06" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--pvf-complexity=400", "--parachain-id=2005"] + +[[parachains]] +id = 2006 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator07" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2006"] + +[[parachains]] +id = 2007 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator08" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2007"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl new file mode 100644 index 00000000000..d7070774747 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0016-approval-voting-parallel.zndsl @@ -0,0 +1,35 @@ +Description: Check finality works with approval voting parallel enabled +Network: ./0016-approval-voting-parallel.toml +Creds: config + +# Check authority status. +alice: reports node_roles is 4 + +# Ensure parachains are registered. +alice: parachain 2000 is registered within 60 seconds +alice: parachain 2001 is registered within 60 seconds +alice: parachain 2002 is registered within 60 seconds +alice: parachain 2003 is registered within 60 seconds +alice: parachain 2004 is registered within 60 seconds +alice: parachain 2005 is registered within 60 seconds +alice: parachain 2006 is registered within 60 seconds +alice: parachain 2007 is registered within 60 seconds + +# Ensure parachains made progress. +alice: parachain 2000 block height is at least 10 within 300 seconds +alice: parachain 2001 block height is at least 10 within 300 seconds +alice: parachain 2002 block height is at least 10 within 300 seconds +alice: parachain 2003 block height is at least 10 within 300 seconds +alice: parachain 2004 block height is at least 10 within 300 seconds +alice: parachain 2005 block height is at least 10 within 300 seconds +alice: parachain 2006 block height is at least 10 within 300 seconds +alice: parachain 2007 block height is at least 10 within 300 seconds + +alice: reports substrate_block_height{status="finalized"} is at least 30 within 180 seconds +bob: reports substrate_block_height{status="finalized"} is at least 30 within 180 seconds + +alice: reports polkadot_parachain_approval_checking_finality_lag < 3 +bob: reports polkadot_parachain_approval_checking_finality_lag < 3 + +alice: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds +bob: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds diff --git a/prdoc/pr_4849.prdoc b/prdoc/pr_4849.prdoc new file mode 100644 index 00000000000..18529515106 --- /dev/null +++ b/prdoc/pr_4849.prdoc @@ -0,0 +1,47 @@ +title: Introduce approval-voting-parallel subsystem + +doc: + - audience: Node Dev + description: | + This introduces a new subsystem called approval-voting-parallel. It combines the tasks + previously handled by the approval-voting and approval-distribution subsystems. + + The new subsystem is enabled by default on all test networks. On production networks + like Polkadot and Kusama, the legacy system with two separate subsystems is still in use. + However, there is a CLI option --enable-approval-voting-parallel to gradually roll out + the new subsystem on specific nodes. Once we are confident that it works as expected, + it will be enabled by default on all networks. + + The approval-voting-parallel subsystem coordinates two groups of workers: + - Four approval-distribution workers that operate in parallel, each handling tasks based + on the validator_index of the message originator. + - One approval-voting worker that performs the tasks previously managed by the standalone + approval-voting subsystem. + +crates: + - name: polkadot-overseer + bump: major + - name: polkadot-node-primitives + bump: major + - name: polkadot-node-subsystem-types + bump: major + - name: polkadot-service + bump: major + - name: polkadot-approval-distribution + bump: major + - name: polkadot-node-core-approval-voting + bump: major + - name: polkadot-node-core-approval-voting-parallel + bump: major + - name: polkadot-network-bridge + bump: major + - name: polkadot-node-core-dispute-coordinator + bump: major + - name: cumulus-relay-chain-inprocess-interface + bump: major + - name: polkadot-cli + bump: major + - name: polkadot + bump: major + - name: polkadot-sdk + bump: minor diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index b7c1c375094..83cbebbc61c 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -600,7 +600,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", @@ -1967,6 +1967,11 @@ path = "../polkadot/node/core/approval-voting" default-features = false optional = true +[dependencies.polkadot-node-core-approval-voting-parallel] +path = "../polkadot/node/core/approval-voting-parallel" +default-features = false +optional = true + [dependencies.polkadot-node-core-av-store] path = "../polkadot/node/core/av-store" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index b7b9c15fe58..4a653dab99b 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -796,6 +796,10 @@ pub use polkadot_node_collation_generation; #[cfg(feature = "polkadot-node-core-approval-voting")] pub use polkadot_node_core_approval_voting; +/// Approval Voting Subsystem running approval work in parallel. +#[cfg(feature = "polkadot-node-core-approval-voting-parallel")] +pub use polkadot_node_core_approval_voting_parallel; + /// The Availability Store subsystem. Wrapper over the DB that stores availability data and /// chunks. #[cfg(feature = "polkadot-node-core-av-store")] -- GitLab From 7626a9d6c0208dff74943abab6f436b458b26c5a Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:54:10 +0200 Subject: [PATCH 013/127] [ci] Disable cargo-hfuzz, disable cargo-doc (#5843) Changes in PR: - disabled cargo-hfuzz until [the issue](https://github.com/paritytech/polkadot-sdk/issues/5812) is fixed. - enabled condition to skip jobs when no rust files are changed --- .github/workflows/check-runtime-migration.yml | 2 +- .github/workflows/checks.yml | 7 +- .github/workflows/docs.yml | 11 ++- .github/workflows/tests-linux-stable.yml | 7 +- .github/workflows/tests-misc.yml | 88 +++++++++++-------- .github/workflows/tests.yml | 16 ++-- .gitlab/pipeline/test.yml | 1 + 7 files changed, 71 insertions(+), 61 deletions(-) diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 8185cf171ae..8d30e752b11 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -17,13 +17,13 @@ concurrency: cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml # More info can be found here: https://github.com/paritytech/polkadot/pull/5865 check-runtime-migration: runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} # We need to set this to rather long to allow the snapshot to be created, but the average time # should be much lower. timeout-minutes: 60 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f765d79254c..fa1d5f2bdd2 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -15,14 +15,13 @@ concurrency: permissions: {} jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml cargo-clippy: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -38,7 +37,7 @@ jobs: check-try-runtime: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 40 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -57,7 +56,7 @@ jobs: check-core-crypto-features: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 30 container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 514bac3973b..44ad9613997 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,15 +9,17 @@ on: merge_group: concurrency: - group: ${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: preflight: + if: contains(github.event.label.name, 'GHA-migration') uses: ./.github/workflows/reusable-preflight.yml test-rustdoc: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -38,7 +40,8 @@ jobs: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" build-rustdoc: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} needs: [preflight, test-rustdoc] container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index 994f1c9b78e..a23a7ad67de 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -13,13 +13,12 @@ concurrency: cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml test-linux-stable-int: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -40,7 +39,7 @@ jobs: # https://github.com/paritytech/ci_cd/issues/864 test-linux-stable-runtime-benchmarks: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -58,7 +57,7 @@ jobs: test-linux-stable: needs: [preflight] - # if: ${{ needs.preflight.outputs.changes_rust }} + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ matrix.runners }} timeout-minutes: 60 strategy: diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 90162adc225..7f3e01eb424 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -22,6 +22,7 @@ jobs: test-full-crypto-feature: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 60 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -46,6 +47,7 @@ jobs: # into one job needs: [preflight, test-full-crypto-feature] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -67,6 +69,7 @@ jobs: timeout-minutes: 60 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -95,6 +98,7 @@ jobs: timeout-minutes: 20 needs: [preflight, test-frame-examples-compile-to-wasm] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -194,6 +198,7 @@ jobs: needs: [preflight] timeout-minutes: 30 runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -226,6 +231,7 @@ jobs: timeout-minutes: 20 needs: [preflight, test-node-metrics] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -241,6 +247,7 @@ jobs: timeout-minutes: 20 needs: [preflight, check-tracing] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -251,50 +258,52 @@ jobs: run: | forklift cargo build --locked -p westend-runtime --features metadata-hash - cargo-hfuzz: - timeout-minutes: 20 - needs: [preflight, check-metadata-hash] - runs-on: ${{ needs.preflight.outputs.RUNNER }} - container: - image: ${{ needs.preflight.outputs.IMAGE }} - env: - # max 10s per iteration, 60s per file - HFUZZ_RUN_ARGS: | - --exit_upon_crash - --exit_code_upon_crash 1 - --timeout 10 - --run_time 60 - - # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: - # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian - # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr - # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR - HFUZZ_BUILD_ARGS: | - --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" - --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Run honggfuzz - run: | - cd substrate/primitives/arithmetic/fuzzer - forklift cargo hfuzz build - for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); - do - forklift cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; - done - - - name: Upload artifacts - uses: actions/upload-artifact@v4.3.6 - with: - path: substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ - name: hfuzz-${{ github.sha }} + # disabled until https://github.com/paritytech/polkadot-sdk/issues/5812 is resolved + # cargo-hfuzz: + # timeout-minutes: 20 + # needs: [preflight, check-metadata-hash] + # runs-on: ${{ needs.preflight.outputs.RUNNER }} + # container: + # image: ${{ needs.preflight.outputs.IMAGE }} + # env: + # # max 10s per iteration, 60s per file + # HFUZZ_RUN_ARGS: | + # --exit_upon_crash + # --exit_code_upon_crash 1 + # --timeout 10 + # --run_time 60 + + # # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr + # # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling absolute CARGO_TARGET_DIR + # HFUZZ_BUILD_ARGS: | + # --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" + # --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + + # - name: Run honggfuzz + # run: | + # cd substrate/primitives/arithmetic/fuzzer + # forklift cargo hfuzz build + # for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); + # do + # forklift cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; + # done + + # - name: Upload artifacts + # uses: actions/upload-artifact@v4.3.6 + # with: + # path: substrate/primitives/arithmetic/fuzzer/hfuzz_workspace/ + # name: hfuzz-${{ github.sha }} cargo-check-each-crate: timeout-minutes: 140 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: @@ -322,6 +331,7 @@ jobs: timeout-minutes: 30 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER_MACOS }} + if: ${{ needs.preflight.outputs.changes_rust }} env: SKIP_WASM_BUILD: 1 steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1132c2ca4dd..e371132647a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,21 +5,20 @@ on: branches: - master pull_request: - types: [ opened, synchronize, reopened, ready_for_review ] + types: [opened, synchronize, reopened, ready_for_review] merge_group: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -37,8 +36,8 @@ jobs: # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: @@ -61,10 +60,9 @@ jobs: run: | echo "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed." >> $GITHUB_STEP_SUMMARY - cargo-check-all-benches: - needs: [ preflight ] - # if: ${{ needs.preflight.outputs.changes_rust }} + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} runs-on: ${{ needs.preflight.outputs.RUNNER }} timeout-minutes: 60 container: diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 6fb35c61e48..0252620cd0f 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -112,6 +112,7 @@ test-linux-stable-codecov: # some tests do not run with `try-runtime` feature enabled # https://github.com/paritytech/polkadot-sdk/pull/4251#discussion_r1624282143 +# Move to github after https://github.com/paritytech/ci_cd/issues/1056 is fixed test-linux-stable-no-try-runtime: stage: test extends: -- GitLab From 17243e039158b78f2b57a4205ef35a2ec223bf1e Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:02:49 -0300 Subject: [PATCH 014/127] bump zombienet version `v1.3.110` (#5834) Bump `zombienet` version to prevent report fails at teardown phase. --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index c17366dbe4c..e43b5a54b04 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.105" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.110" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" -- GitLab From 6c3219ebe9231a0305f53c7b33cb558d46058062 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:51:29 +0200 Subject: [PATCH 015/127] [ci] Update CI image with rust 1.81.0 and 2024-09-11 (#5676) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cc https://github.com/paritytech/ci_cd/issues/1035 cc https://github.com/paritytech/ci_cd/issues/1023 --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: command-bot <> Co-authored-by: Maksym H <1177472+mordamax@users.noreply.github.com> Co-authored-by: gui Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: ggwpez --- .github/env | 2 +- .github/scripts/generate-prdoc.py | 6 +- .github/workflows/checks.yml | 8 +- .github/workflows/command-prdoc.yml | 2 +- .github/workflows/tests-linux-stable.yml | 5 +- .github/workflows/tests-misc.yml | 16 +- .github/workflows/tests.yml | 6 +- .gitlab-ci.yml | 3 +- Cargo.toml | 2 +- bridges/modules/grandpa/src/lib.rs | 4 +- bridges/modules/xcm-bridge-hub/src/lib.rs | 40 +- .../header-chain/src/justification/mod.rs | 15 +- bridges/primitives/messages/src/lane.rs | 6 +- .../src/cli/relay_headers_and_messages/mod.rs | 4 +- .../src/messages/metrics.rs | 3 +- .../src/on_demand/parachains.rs | 3 +- .../messages/src/message_race_delivery.rs | 44 +- .../messages/src/message_race_limits.rs | 49 +- .../relays/messages/src/message_race_loop.rs | 18 - .../messages/src/message_race_strategy.rs | 6 +- .../outbound-queue/merkle-tree/src/lib.rs | 20 +- .../primitives/router/src/inbound/mod.rs | 6 +- .../primitives/router/src/outbound/mod.rs | 3 +- .../runtime/runtime-common/src/lib.rs | 3 +- cumulus/pallets/parachain-system/src/lib.rs | 3 +- cumulus/pallets/xcmp-queue/src/mock.rs | 61 +- .../asset-hub-rococo/src/tests/teleport.rs | 4 +- .../asset-hub-westend/src/tests/teleport.rs | 4 +- .../people-rococo/src/tests/teleport.rs | 4 +- .../people-westend/src/tests/teleport.rs | 4 +- .../assets/asset-hub-rococo/src/lib.rs | 5 +- .../assets/asset-hub-westend/src/lib.rs | 5 +- .../assets/test-utils/src/test_cases.rs | 3 +- .../bridge-hub-westend/tests/tests.rs | 32 +- .../test-utils/src/test_cases/mod.rs | 11 +- .../runtimes/starters/shell/src/lib.rs | 6 +- .../src/fake_runtime_api/mod.rs | 2 + cumulus/primitives/utility/src/lib.rs | 6 +- polkadot/cli/src/command.rs | 2 - .../core/approval-voting-parallel/src/lib.rs | 3 +- .../node/core/approval-voting/src/import.rs | 11 +- polkadot/node/core/approval-voting/src/lib.rs | 7 +- .../node/core/approval-voting/src/tests.rs | 3 +- polkadot/node/core/pvf/build.rs | 2 +- .../common/src/worker/security/change_root.rs | 3 +- polkadot/node/core/pvf/src/artifacts.rs | 16 +- polkadot/node/core/pvf/src/host.rs | 25 +- polkadot/node/core/pvf/src/prepare/pool.rs | 15 +- .../core/pvf/src/prepare/worker_interface.rs | 2 - polkadot/node/core/pvf/src/testing.rs | 2 +- .../approval-distribution/src/tests.rs | 3 +- .../src/tests/mod.rs | 11 +- .../src/tests/state.rs | 1 - .../src/task/strategy/chunks.rs | 13 +- .../src/task/strategy/systematic.rs | 6 +- polkadot/node/overseer/src/lib.rs | 1 + polkadot/node/service/src/fake_runtime_api.rs | 1 + .../node/service/src/parachains_db/mod.rs | 9 +- polkadot/node/service/src/tests.rs | 3 - .../src/lib/approval/message_generator.rs | 5 +- .../subsystem-bench/src/lib/statement/mod.rs | 16 +- .../subsystem-types/src/runtime_client.rs | 3 +- polkadot/primitives/src/runtime_api.rs | 2 +- polkadot/primitives/src/v8/metrics.rs | 12 - polkadot/runtime/parachains/src/hrmp.rs | 2 +- .../runtime/parachains/src/inclusion/mod.rs | 3 +- .../parachains/src/paras_inherent/mod.rs | 25 +- polkadot/runtime/parachains/src/ump_tests.rs | 7 +- polkadot/runtime/westend/src/lib.rs | 49 +- polkadot/runtime/westend/src/weights/mod.rs | 1 - .../pallet_referenda_fellowship_referenda.rs | 525 --------------- .../list-syscalls/execute-worker-syscalls | 1 + .../list-syscalls/prepare-worker-syscalls | 1 + polkadot/statement-table/src/generic.rs | 6 +- .../parachain/xcm_config.rs | 2 +- .../relay_chain/xcm_config.rs | 2 +- polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs | 2 +- polkadot/xcm/procedural/tests/ui.rs | 1 - .../xcm/xcm-builder/src/asset_conversion.rs | 8 +- .../xcm-builder/src/nonfungibles_adapter.rs | 9 +- .../xcm/xcm-executor/src/traits/weight.rs | 7 - polkadot/xcm/xcm-runtime-apis/src/dry_run.rs | 7 +- .../xcm-runtime-apis/tests/fee_estimation.rs | 2 +- prdoc/pr_4851.prdoc | 4 +- prdoc/pr_5676.prdoc | 174 +++++ prdoc/pr_5678.prdoc | 2 +- prdoc/pr_5684.prdoc | 2 +- scripts/update-ui-tests.sh | 12 +- substrate/bin/node/cli/tests/fees.rs | 132 ---- .../authority-discovery/src/worker/tests.rs | 33 +- .../cli/src/commands/import_blocks_cmd.rs | 7 +- substrate/client/cli/src/commands/vanity.rs | 14 - .../client/cli/src/params/node_key_params.rs | 4 +- .../client/consensus/beefy/src/fisherman.rs | 9 +- .../consensus/grandpa/src/aux_schema.rs | 4 +- .../consensus/grandpa/src/voting_rule.rs | 2 +- substrate/client/consensus/slots/build.rs | 2 +- substrate/client/consensus/slots/src/lib.rs | 2 +- substrate/client/executor/wasmtime/build.rs | 2 +- .../client/executor/wasmtime/src/tests.rs | 2 +- substrate/client/network-gossip/src/bridge.rs | 3 - substrate/client/network/src/behaviour.rs | 12 +- .../client/network/src/litep2p/discovery.rs | 15 +- .../src/protocol/notifications/behaviour.rs | 112 ++-- .../src/protocol/notifications/handler.rs | 29 - .../src/protocol/notifications/service/mod.rs | 6 +- .../notifications/upgrade/notifications.rs | 15 +- .../client/network/src/service/metrics.rs | 5 - substrate/client/network/sync/src/engine.rs | 3 +- substrate/client/network/sync/src/lib.rs | 1 - .../network/sync/src/request_metrics.rs | 25 - substrate/client/network/test/src/lib.rs | 3 +- substrate/client/network/test/src/sync.rs | 18 - .../rpc-spec-v2/src/chain_head/test_utils.rs | 3 +- .../client/service/test/src/client/mod.rs | 6 +- .../transaction-pool/src/graph/tracked_map.rs | 3 +- substrate/client/utils/src/mpsc.rs | 7 +- substrate/frame/authorship/src/lib.rs | 3 +- substrate/frame/bags-list/src/lib.rs | 2 +- substrate/frame/bags-list/src/list/tests.rs | 2 +- substrate/frame/bags-list/src/mock.rs | 2 +- substrate/frame/balances/src/lib.rs | 3 +- .../balances/src/tests/currency_tests.rs | 6 +- substrate/frame/beefy-mmr/src/benchmarking.rs | 12 +- .../frame/benchmarking/pov/src/weights.rs | 2 + substrate/frame/bounties/src/lib.rs | 12 +- substrate/frame/child-bounties/src/lib.rs | 13 +- .../frame/contracts/src/benchmarking/code.rs | 3 +- substrate/frame/contracts/src/exec.rs | 7 - substrate/frame/contracts/src/wasm/mod.rs | 4 - substrate/frame/delegated-staking/src/lib.rs | 4 +- .../election-provider-multi-phase/src/lib.rs | 3 +- .../election-provider-support/src/lib.rs | 4 +- substrate/frame/elections-phragmen/src/lib.rs | 3 +- .../examples/offchain-worker/src/tests.rs | 22 +- .../src/migrations/v1.rs | 2 +- substrate/frame/executive/src/lib.rs | 3 +- substrate/frame/nis/src/lib.rs | 16 +- substrate/frame/referenda/src/mock.rs | 9 - substrate/frame/referenda/src/types.rs | 3 +- substrate/frame/revive/proc-macro/src/lib.rs | 25 +- substrate/frame/society/src/lib.rs | 12 - substrate/frame/society/src/tests.rs | 2 +- substrate/frame/staking/src/tests.rs | 2 +- .../procedural/src/construct_runtime/parse.rs | 3 - .../support/procedural/src/derive_impl.rs | 1 + substrate/frame/support/procedural/src/lib.rs | 25 +- .../src/pallet/expand/genesis_build.rs | 3 +- .../procedural/src/pallet/expand/hooks.rs | 4 +- .../procedural/src/pallet/parse/call.rs | 23 +- .../procedural/src/runtime/parse/pallet.rs | 1 - substrate/frame/support/src/lib.rs | 2 + .../src/storage/generator/double_map.rs | 3 +- .../support/src/storage/generator/map.rs | 3 +- .../support/src/storage/generator/nmap.rs | 3 +- .../support/src/storage/types/double_map.rs | 3 +- substrate/frame/support/src/tests/mod.rs | 1 + substrate/frame/support/src/traits/misc.rs | 8 +- .../traits/try_runtime/decode_entire_state.rs | 6 +- ...bad_return_type_blank_with_question.stderr | 4 + .../deprecated_where_block.stderr | 616 ++++++++++++++---- .../undefined_event_part.stderr | 4 +- .../undefined_genesis_config_part.stderr | 2 +- .../undefined_inherent_part.stderr | 4 +- .../undefined_origin_part.stderr | 2 +- .../frame/support/test/tests/derive_impl.rs | 10 - .../support/test/tests/derive_no_bound.rs | 1 + .../test/tests/derive_no_bound_ui/ord.stderr | 10 + substrate/frame/support/test/tests/pallet.rs | 5 +- .../call_argument_invalid_bound.stderr | 9 + .../call_argument_invalid_bound_2.stderr | 13 +- .../call_argument_invalid_bound_3.stderr | 9 + .../compare_unset_storage_version.stderr | 6 + ...ev_mode_without_arg_max_encoded_len.stderr | 16 +- .../error_does_not_derive_pallet_error.stderr | 16 +- .../tests/pallet_ui/hooks_invalid_item.stderr | 6 + .../inherent_check_inner_span.stderr | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 100 +-- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 100 +-- .../pallet_ui/storage_info_unsatisfied.stderr | 16 +- .../storage_info_unsatisfied_nmap.stderr | 16 +- .../type_value_error_in_block.stderr | 6 + substrate/frame/system/src/tests.rs | 1 + .../frame/transaction-payment/src/tests.rs | 6 +- substrate/frame/utility/src/lib.rs | 4 +- substrate/frame/vesting/src/tests.rs | 10 +- substrate/primitives/api/test/Cargo.toml | 1 + .../primitives/api/test/tests/trybuild.rs | 10 +- .../api/test/tests/ui/deprecation_info.stderr | 15 + .../ui/impl_incorrect_method_signature.stderr | 18 +- .../tests/ui/mock_only_self_reference.stderr | 6 +- ...reference_in_impl_runtime_apis_call.stderr | 18 +- .../primitives/runtime-interface/tests/ui.rs | 6 +- .../tests/ui/no_feature_gated_method.stderr | 34 +- substrate/primitives/runtime/src/traits.rs | 12 - substrate/primitives/state-machine/src/ext.rs | 1 + substrate/test-utils/cli/build.rs | 2 +- substrate/test-utils/cli/src/lib.rs | 2 +- substrate/utils/fork-tree/src/lib.rs | 3 +- .../utils/frame/benchmarking-cli/build.rs | 4 + .../utils/wasm-builder/src/wasm_project.rs | 7 +- .../pallets/template/src/benchmarking.rs | 1 - .../pallets/template/src/benchmarking.rs | 2 +- 203 files changed, 1455 insertions(+), 1885 deletions(-) delete mode 100644 polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs create mode 100644 prdoc/pr_5676.prdoc delete mode 100644 substrate/client/network/sync/src/request_metrics.rs diff --git a/.github/env b/.github/env index 2e4d5b48100..bb61e1f4cd9 100644 --- a/.github/env +++ b/.github/env @@ -1 +1 @@ -IMAGE="docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v202407161507" +IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index d3b6b523ecf..f05d517a580 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -67,7 +67,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): # Go up until we find a Cargo.toml p = os.path.join(workspace.path, p) while not os.path.exists(os.path.join(p, "Cargo.toml")): - print(f"Could not find Cargo.toml in {p}") if p == '/': exit(1) p = os.path.dirname(p) @@ -76,7 +75,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): manifest = toml.load(f) if not "package" in manifest: - print(f"File was not in any crate: {p}") continue crate_name = manifest["package"]["name"] @@ -85,8 +83,6 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): else: print(f"Skipping unpublished crate: {crate_name}") - print(f"Modified crates: {modified_crates.keys()}") - for crate_name in modified_crates.keys(): entry = { "name": crate_name } @@ -137,4 +133,4 @@ def main(args): if __name__ == "__main__": args = setup_parser().parse_args() - main(args) \ No newline at end of file + main(args) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index fa1d5f2bdd2..0793c31dbb8 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -32,8 +32,8 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | - forklift cargo clippy --all-targets --locked --workspace - forklift cargo clippy --all-targets --all-features --locked --workspace + forklift cargo clippy --all-targets --locked --workspace --quiet + forklift cargo clippy --all-targets --all-features --locked --workspace --quiet check-try-runtime: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] @@ -45,13 +45,13 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: script run: | - forklift cargo check --locked --all --features try-runtime + forklift cargo check --locked --all --features try-runtime --quiet # this is taken from cumulus # Check that parachain-template will compile with `try-runtime` feature flag. forklift cargo check --locked -p parachain-template-node --features try-runtime # add after https://github.com/paritytech/substrate/pull/14502 is merged # experimental code may rely on try-runtime and vice-versa - forklift cargo check --locked --all --features try-runtime,experimental + forklift cargo check --locked --all --features try-runtime,experimental --quiet # check-core-crypto-features works fast without forklift check-core-crypto-features: runs-on: ${{ needs.preflight.outputs.RUNNER }} diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index aa9de9474a7..2154c8a987f 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -28,7 +28,7 @@ on: - "Runtime Dev" - "Runtime User" - "Node Dev" - - "Node User" + - "Node Operator" overwrite: type: choice description: Overwrite existing PrDoc diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index a23a7ad67de..599edd8e69a 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -53,7 +53,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet + run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet --cargo-quiet test-linux-stable: needs: [preflight] @@ -90,12 +90,13 @@ jobs: --locked \ --release \ --no-fail-fast \ + --cargo-quiet \ --features try-runtime,experimental,riscv,ci-only-tests \ --partition count:${{ matrix.partition }} # run runtime-api tests with `enable-staging-api` feature on the 1st node - name: runtime-api tests if: ${{ matrix.partition == '1/3' }} - run: forklift cargo nextest run -p sp-api-test --features enable-staging-api + run: forklift cargo nextest run -p sp-api-test --features enable-staging-api --cargo-quiet confirm-required-jobs-passed: runs-on: ubuntu-latest diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 7f3e01eb424..45ed4ebdc6c 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -77,8 +77,7 @@ jobs: # but still want to have debug assertions. RUSTFLAGS: "-C debug-assertions -D warnings" RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" + SKIP_WASM_BUILD: 1 # Ensure we run the UI tests. RUN_UI_TESTS: 1 steps: @@ -86,13 +85,14 @@ jobs: uses: actions/checkout@v4 - name: script run: | - forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental - forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental - forklift cargo test --locked -q --profile testnet -p xcm-procedural - forklift cargo test --locked -q --profile testnet -p frame-election-provider-solution-type - forklift cargo test --locked -q --profile testnet -p sp-api-test + cargo version + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental ui + forklift cargo test --locked -q --profile testnet -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental ui + forklift cargo test --locked -q --profile testnet -p xcm-procedural ui + forklift cargo test --locked -q --profile testnet -p frame-election-provider-solution-type ui + forklift cargo test --locked -q --profile testnet -p sp-api-test ui # There is multiple version of sp-runtime-interface in the repo. So we point to the manifest. - forklift cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml + forklift cargo test --locked -q --profile testnet --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui test-deterministic-wasm: timeout-minutes: 20 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e371132647a..6d6e393b041 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet + run: forklift cargo run --locked --release -p staging-node-cli --bin substrate-node --features runtime-benchmarks --quiet -- benchmark pallet --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 --quiet # cf https://github.com/paritytech/polkadot-sdk/issues/1652 test-syscalls: @@ -51,7 +51,7 @@ jobs: - name: script id: test run: | - forklift cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker + forklift cargo build --locked --profile production --target x86_64-unknown-linux-musl --bin polkadot-execute-worker --bin polkadot-prepare-worker --quiet cd polkadot/scripts/list-syscalls ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-execute-worker --only-used-syscalls | diff -u execute-worker-syscalls - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls | diff -u prepare-worker-syscalls - @@ -73,4 +73,4 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: script - run: forklift cargo check --all --benches + run: forklift cargo check --all --benches --quiet diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c6b3928b46..dbc5dafeb0a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,8 @@ workflow: - if: $CI_COMMIT_BRANCH variables: - CI_IMAGE: !reference [.ci-unified, variables, CI_IMAGE] + # CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] + CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" diff --git a/Cargo.toml b/Cargo.toml index c92254242fc..c40d59fce3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -552,7 +552,7 @@ default-members = [ [workspace.lints.rust] suspicious_double_ref_op = { level = "allow", priority = 2 } # `substrate_runtime` is a common `cfg` condition name used in the repo. -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(substrate_runtime)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(build_opt_level, values("3"))', 'cfg(build_profile, values("debug", "release"))', 'cfg(enable_alloc_error_handler)', 'cfg(fuzzing)', 'cfg(substrate_runtime)'] } [workspace.lints.clippy] all = { level = "allow", priority = 0 } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index dff4b98fd91..22a15ec4062 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -728,15 +728,13 @@ pub mod pallet { init_params; let authority_set_length = authority_list.len(); let authority_set = StoredAuthoritySet::::try_new(authority_list, set_id) - .map_err(|e| { + .inspect_err(|_| { log::error!( target: LOG_TARGET, "Failed to initialize bridge. Number of authorities in the set {} is larger than the configured value {}", authority_set_length, T::BridgedChain::MAX_AUTHORITIES_COUNT, ); - - e })?; let initial_hash = header.hash(); diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 22c60fb4ad6..1b2536598a2 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -415,7 +415,7 @@ pub mod pallet { bridge.deposit, Precision::BestEffort, ) - .map_err(|e| { + .inspect_err(|e| { // we can't do anything here - looks like funds have been (partially) unreserved // before by someone else. Let's not fail, though - it'll be worse for the caller log::error!( @@ -423,7 +423,6 @@ pub mod pallet { "Failed to unreserve during the bridge {:?} closure with error: {e:?}", locations.bridge_id(), ); - e }) .ok() .unwrap_or(BalanceOf::>::zero()); @@ -1456,25 +1455,26 @@ mod tests { let lane_id = TestLaneIdType::try_new(1, 2).unwrap(); let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap(); - let test_bridge_state = |id, - bridge, - (lane_id, bridge_id), - (inbound_lane_id, outbound_lane_id), - expected_error: Option| { - Bridges::::insert(id, bridge); - LaneToBridge::::insert(lane_id, bridge_id); - - let lanes_manager = LanesManagerOf::::new(); - lanes_manager.create_inbound_lane(inbound_lane_id).unwrap(); - lanes_manager.create_outbound_lane(outbound_lane_id).unwrap(); + let test_bridge_state = + |id, + bridge, + (lane_id, bridge_id), + (inbound_lane_id, outbound_lane_id), + expected_error: Option| { + Bridges::::insert(id, bridge); + LaneToBridge::::insert(lane_id, bridge_id); - let result = XcmOverBridge::do_try_state(); - if let Some(e) = expected_error { - assert_err!(result, e); - } else { - assert_ok!(result); - } - }; + let lanes_manager = LanesManagerOf::::new(); + lanes_manager.create_inbound_lane(inbound_lane_id).unwrap(); + lanes_manager.create_outbound_lane(outbound_lane_id).unwrap(); + + let result = XcmOverBridge::do_try_state(); + if let Some(e) = expected_error { + assert_err!(result, e); + } else { + assert_ok!(result); + } + }; let cleanup = |bridge_id, lane_ids| { Bridges::::remove(bridge_id); for lane_id in lane_ids { diff --git a/bridges/primitives/header-chain/src/justification/mod.rs b/bridges/primitives/header-chain/src/justification/mod.rs index d7c2cbf429e..87f53dac646 100644 --- a/bridges/primitives/header-chain/src/justification/mod.rs +++ b/bridges/primitives/header-chain/src/justification/mod.rs @@ -32,7 +32,6 @@ pub use verification::{ use bp_runtime::{BlockNumberOf, Chain, HashOf, HeaderId}; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::RuntimeDebugNoBound; use scale_info::TypeInfo; use sp_consensus_grandpa::{AuthorityId, AuthoritySignature}; use sp_runtime::{traits::Header as HeaderT, RuntimeDebug, SaturatedConversion}; @@ -43,7 +42,8 @@ use sp_std::prelude::*; /// /// This particular proof is used to prove that headers on a bridged chain /// (so not our chain) have been finalized correctly. -#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, RuntimeDebugNoBound)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] pub struct GrandpaJustification { /// The round (voting period) this justification is valid for. pub round: u64, @@ -54,6 +54,17 @@ pub struct GrandpaJustification { pub votes_ancestries: Vec

, } +// A proper Debug impl for no-std is not possible for the `GrandpaJustification` since the `Commit` +// type only implements Debug that for `std` here: +// https://github.com/paritytech/finality-grandpa/blob/8c45a664c05657f0c71057158d3ba555ba7d20de/src/lib.rs#L275 +// so we do a manual impl. +#[cfg(not(feature = "std"))] +impl core::fmt::Debug for GrandpaJustification { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "GrandpaJustification {{ round: {:?}, commit: , votes_ancestries: {:?} }}", self.round, self.votes_ancestries) + } +} + impl GrandpaJustification { /// Returns reasonable size of justification using constants from the provided chain. /// diff --git a/bridges/primitives/messages/src/lane.rs b/bridges/primitives/messages/src/lane.rs index c835449db27..0f14ce93e11 100644 --- a/bridges/primitives/messages/src/lane.rs +++ b/bridges/primitives/messages/src/lane.rs @@ -71,7 +71,7 @@ impl LaneIdType for LegacyLaneId { } } -#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +#[cfg(feature = "std")] impl TryFrom> for LegacyLaneId { type Error = (); @@ -140,7 +140,7 @@ impl HashedLaneId { /// There's no `From` implementation for the `LaneId`, because using this conversion /// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we /// want the call to be explicit. - #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[cfg(feature = "std")] pub const fn from_inner(inner: H256) -> Self { Self(inner) } @@ -184,7 +184,7 @@ impl LaneIdType for HashedLaneId { } } -#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +#[cfg(feature = "std")] impl TryFrom> for HashedLaneId { type Error = (); diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index 9261dc43753..e875c53a2e7 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -325,7 +325,9 @@ where _, Self::Left, MessagesLaneIdOf, - >(common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes) + >( + common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes + ) .await?; } diff --git a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs index 9d45a4b3d66..efe429701c4 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/metrics.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/metrics.rs @@ -53,9 +53,8 @@ where let token_decimals = client .token_decimals() .await? - .map(|token_decimals| { + .inspect(|token_decimals| { log::info!(target: "bridge", "Read `tokenDecimals` for {}: {}", C::NAME, token_decimals); - token_decimals }) .unwrap_or_else(|| { // turns out it is normal not to have this property - e.g. when polkadot binary is diff --git a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs index 2ef86f48ecb..96eba0af988 100644 --- a/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/on_demand/parachains.rs @@ -664,7 +664,8 @@ impl<'a, P: SubstrateParachainsPipeline, SourceRelayClnt, TargetClnt> for ( &'a OnDemandParachainsRelay, &'a ParachainsSource, - ) where + ) +where SourceRelayClnt: Client, TargetClnt: Client, { diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs index cbb89baabcc..b09533a4ddc 100644 --- a/bridges/relays/messages/src/message_race_delivery.rs +++ b/bridges/relays/messages/src/message_race_delivery.rs @@ -59,9 +59,7 @@ pub async fn run( _phantom: Default::default(), }, target_state_updates, - MessageDeliveryStrategy:: { - lane_source_client: source_client, - lane_target_client: target_client, + MessageDeliveryStrategy::

{ max_unrewarded_relayer_entries_at_target: params .max_unrewarded_relayer_entries_at_target, max_unconfirmed_nonces_at_target: params.max_unconfirmed_nonces_at_target, @@ -71,7 +69,6 @@ pub async fn run( latest_confirmed_nonces_at_source: VecDeque::new(), target_nonces: None, strategy: BasicStrategy::new(), - metrics_msg, }, ) .await @@ -300,11 +297,7 @@ struct DeliveryRaceTargetNoncesData { } /// Messages delivery strategy. -struct MessageDeliveryStrategy { - /// The client that is connected to the message lane source node. - lane_source_client: SC, - /// The client that is connected to the message lane target node. - lane_target_client: TC, +struct MessageDeliveryStrategy { /// Maximal unrewarded relayer entries at target client. max_unrewarded_relayer_entries_at_target: MessageNonce, /// Maximal unconfirmed nonces at target client. @@ -322,8 +315,6 @@ struct MessageDeliveryStrategy { target_nonces: Option>, /// Basic delivery strategy. strategy: MessageDeliveryStrategyBase

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

= BasicStrategy< @@ -335,7 +326,7 @@ type MessageDeliveryStrategyBase

= BasicStrategy<

::MessagesProof, >; -impl std::fmt::Debug for MessageDeliveryStrategy { +impl std::fmt::Debug for MessageDeliveryStrategy

{ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { fmt.debug_struct("MessageDeliveryStrategy") .field( @@ -353,11 +344,9 @@ impl std::fmt::Debug for MessageDeliveryStrategy MessageDeliveryStrategy +impl MessageDeliveryStrategy

where P: MessageLane, - SC: MessageLaneSourceClient

, - TC: MessageLaneTargetClient

, { /// Returns true if some race action can be selected (with `select_race_action`) at given /// `best_finalized_source_header_id_at_best_target` source header at target. @@ -465,23 +454,18 @@ where let max_nonces = std::cmp::min(max_nonces, self.max_messages_in_single_batch); let max_messages_weight_in_single_batch = self.max_messages_weight_in_single_batch; let max_messages_size_in_single_batch = self.max_messages_size_in_single_batch; - let lane_source_client = self.lane_source_client.clone(); - let lane_target_client = self.lane_target_client.clone(); // select nonces from nonces, available for delivery let selected_nonces = match self.strategy.available_source_queue_indices(race_state) { Some(available_source_queue_indices) => { let source_queue = self.strategy.source_queue(); - let reference = RelayMessagesBatchReference { + let reference = RelayMessagesBatchReference::

{ max_messages_in_this_batch: max_nonces, max_messages_weight_in_single_batch, max_messages_size_in_single_batch, - lane_source_client: lane_source_client.clone(), - lane_target_client: lane_target_client.clone(), best_target_nonce, nonces_queue: source_queue.clone(), nonces_queue_range: available_source_queue_indices, - metrics: self.metrics_msg.clone(), }; MessageRaceLimits::decide(reference).await @@ -534,12 +518,10 @@ where } #[async_trait] -impl RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> - for MessageDeliveryStrategy +impl

RaceStrategy, TargetHeaderIdOf

, P::MessagesProof> + for MessageDeliveryStrategy

where P: MessageLane, - SC: MessageLaneSourceClient

, - TC: MessageLaneTargetClient

, { type SourceNoncesRange = MessageDetailsMap; type ProofParameters = MessageProofParameters; @@ -707,8 +689,7 @@ mod tests { message_lane_loop::{ tests::{ header_id, TestMessageLane, TestMessagesBatchTransaction, TestMessagesProof, - TestSourceChainBalance, TestSourceClient, TestSourceHeaderId, TestTargetClient, - TestTargetHeaderId, + TestSourceChainBalance, TestSourceHeaderId, TestTargetHeaderId, }, MessageDetails, }, @@ -726,8 +707,7 @@ mod tests { TestMessagesProof, TestMessagesBatchTransaction, >; - type TestStrategy = - MessageDeliveryStrategy; + type TestStrategy = MessageDeliveryStrategy; fn source_nonces( new_nonces: RangeInclusive, @@ -770,9 +750,6 @@ mod tests { max_messages_weight_in_single_batch: Weight::from_parts(4, 0), max_messages_size_in_single_batch: 4, latest_confirmed_nonces_at_source: vec![(header_id(1), 19)].into_iter().collect(), - lane_source_client: TestSourceClient::default(), - lane_target_client: TestTargetClient::default(), - metrics_msg: None, target_nonces: Some(TargetClientNonces { latest_nonce: 19, nonces_data: DeliveryRaceTargetNoncesData { @@ -1167,9 +1144,6 @@ mod tests { max_messages_weight_in_single_batch: Weight::from_parts(4, 0), max_messages_size_in_single_batch: 4, latest_confirmed_nonces_at_source: VecDeque::new(), - lane_source_client: TestSourceClient::default(), - lane_target_client: TestTargetClient::default(), - metrics_msg: None, target_nonces: None, strategy: BasicStrategy::new(), }; diff --git a/bridges/relays/messages/src/message_race_limits.rs b/bridges/relays/messages/src/message_race_limits.rs index 873bb6aad04..8fcd1f911f6 100644 --- a/bridges/relays/messages/src/message_race_limits.rs +++ b/bridges/relays/messages/src/message_race_limits.rs @@ -23,33 +23,16 @@ use bp_messages::{MessageNonce, Weight}; use crate::{ message_lane::MessageLane, - message_lane_loop::{ - MessageDetails, MessageDetailsMap, SourceClient as MessageLaneSourceClient, - TargetClient as MessageLaneTargetClient, - }, + message_lane_loop::{MessageDetails, MessageDetailsMap}, message_race_loop::NoncesRange, message_race_strategy::SourceRangesQueue, - metrics::MessageLaneLoopMetrics, }; /// Reference data for participating in relay -pub struct RelayReference< - P: MessageLane, - SourceClient: MessageLaneSourceClient

, - TargetClient: MessageLaneTargetClient

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

, - TargetClient: MessageLaneTargetClient

, -> { +pub struct RelayMessagesBatchReference { /// Maximal number of relayed messages in single delivery transaction. pub max_messages_in_this_batch: MessageNonce, /// Maximal cumulative dispatch weight of relayed messages in single delivery transaction. pub max_messages_weight_in_single_batch: Weight, /// Maximal cumulative size of relayed messages in single delivery transaction. pub max_messages_size_in_single_batch: u32, - /// The client that is connected to the message lane source node. - pub lane_source_client: SourceClient, - /// The client that is connected to the message lane target node. - pub lane_target_client: TargetClient, - /// Metrics reference. - pub metrics: Option, /// Best available nonce at the **best** target block. We do not want to deliver nonces /// less than this nonce, even though the block may be retracted. pub best_target_nonce: MessageNonce, @@ -94,12 +67,8 @@ pub struct RelayMessagesBatchReference< pub struct MessageRaceLimits; impl MessageRaceLimits { - pub async fn decide< - P: MessageLane, - SourceClient: MessageLaneSourceClient

, - TargetClient: MessageLaneTargetClient

, - >( - reference: RelayMessagesBatchReference, + pub async fn decide( + reference: RelayMessagesBatchReference

, ) -> Option> { let mut hard_selected_count = 0; @@ -112,15 +81,9 @@ impl MessageRaceLimits { ); // relay reference - let mut relay_reference = RelayReference { - lane_source_client: reference.lane_source_client.clone(), - lane_target_client: reference.lane_target_client.clone(), - metrics: reference.metrics.clone(), - + let mut relay_reference = RelayReference::

{ selected_size: 0, - hard_selected_begin_nonce, - index: 0, nonce: 0, details: MessageDetails { diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs index 31341a9a0c0..ea6a2371dc9 100644 --- a/bridges/relays/messages/src/message_race_loop.rs +++ b/bridges/relays/messages/src/message_race_loop.rs @@ -225,15 +225,9 @@ pub trait RaceState: Clone + Send + Sync { /// client (at the `best_finalized_source_header_id_at_best_target`). fn set_best_finalized_source_header_id_at_best_target(&mut self, id: SourceHeaderId); - /// Best finalized source header id at the source client. - fn best_finalized_source_header_id_at_source(&self) -> Option; /// Best finalized source header id at the best block on the target /// client (at the `best_finalized_source_header_id_at_best_target`). fn best_finalized_source_header_id_at_best_target(&self) -> Option; - /// The best header id at the target client. - fn best_target_header_id(&self) -> Option; - /// Best finalized header id at the target client. - fn best_finalized_target_header_id(&self) -> Option; /// Returns `true` if we have selected nonces to submit to the target node. fn nonces_to_submit(&self) -> Option>; @@ -296,22 +290,10 @@ where self.best_finalized_source_header_id_at_best_target = Some(id); } - fn best_finalized_source_header_id_at_source(&self) -> Option { - self.best_finalized_source_header_id_at_source.clone() - } - fn best_finalized_source_header_id_at_best_target(&self) -> Option { self.best_finalized_source_header_id_at_best_target.clone() } - fn best_target_header_id(&self) -> Option { - self.best_target_header_id.clone() - } - - fn best_finalized_target_header_id(&self) -> Option { - self.best_finalized_target_header_id.clone() - } - fn nonces_to_submit(&self) -> Option> { self.nonces_to_submit.clone().map(|(_, nonces, _)| nonces) } diff --git a/bridges/relays/messages/src/message_race_strategy.rs b/bridges/relays/messages/src/message_race_strategy.rs index 3a532331d79..1303fcfedeb 100644 --- a/bridges/relays/messages/src/message_race_strategy.rs +++ b/bridges/relays/messages/src/message_race_strategy.rs @@ -67,7 +67,8 @@ impl< TargetHeaderHash, SourceNoncesRange, Proof, - > where + > +where SourceHeaderHash: Clone, SourceHeaderNumber: Clone + Ord, SourceNoncesRange: NoncesRange, @@ -189,7 +190,8 @@ impl< TargetHeaderHash, SourceNoncesRange, Proof, - > where + > +where SourceHeaderHash: Clone + Debug + Send + Sync, SourceHeaderNumber: Clone + Ord + Debug + Send + Sync, SourceNoncesRange: NoncesRange + Debug + Send + Sync, diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs index d5c89b9c098..eeeaa6e68cf 100644 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs @@ -182,12 +182,6 @@ where let root = merkelize::(hashes.into_iter(), &mut collect_proof); let leaf = leaf.expect("Requested `leaf_index` is greater than number of leaves."); - #[cfg(feature = "debug")] - log::debug!( - "[merkle_proof] Proof: {:?}", - collect_proof.proof.iter().map(hex::encode).collect::>() - ); - MerkleProof { root, proof: collect_proof.proof, number_of_leaves, leaf_index, leaf } } @@ -274,8 +268,6 @@ where V: Visitor, I: Iterator, { - #[cfg(feature = "debug")] - log::debug!("[merkelize_row]"); next.clear(); let hash_len = ::LENGTH; @@ -286,9 +278,6 @@ where let b = iter.next(); visitor.visit(index, &a, &b); - #[cfg(feature = "debug")] - log::debug!(" {:?}\n {:?}", a.as_ref().map(hex::encode), b.as_ref().map(hex::encode)); - index += 2; match (a, b) { (Some(a), Some(b)) => { @@ -309,14 +298,7 @@ where // Last item = root. (Some(a), None) => return Ok(a), // Finish up, no more items. - _ => { - #[cfg(feature = "debug")] - log::debug!( - "[merkelize_row] Next: {:?}", - next.iter().map(hex::encode).collect::>() - ); - return Err(next) - }, + _ => return Err(next), } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index a10884b4553..fbfc52d01c8 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -168,7 +168,8 @@ impl< ConvertAssetId, EthereumUniversalLocation, GlobalAssetHubLocation, - > where + > +where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, @@ -226,7 +227,8 @@ impl< ConvertAssetId, EthereumUniversalLocation, GlobalAssetHubLocation, - > where + > +where CreateAssetCall: Get, CreateAssetDeposit: Get, InboundQueuePalletInstance: Get, diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index d3b6c116dd7..b23bce0e218 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -44,7 +44,8 @@ impl where + > +where UniversalLocation: Get, EthereumNetwork: Get, OutboundQueue: SendMessage, diff --git a/bridges/snowbridge/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs index aae45520ff4..0b1a74b232a 100644 --- a/bridges/snowbridge/runtime/runtime-common/src/lib.rs +++ b/bridges/snowbridge/runtime/runtime-common/src/lib.rs @@ -50,7 +50,8 @@ impl where + > +where Balance: BaseArithmetic + Unsigned + Copy + From + Into + Debug, AccountId: Clone + FullCodec, FeeAssetLocation: Get, diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 9dc41aa03d9..21af35fe3de 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -390,7 +390,8 @@ pub mod pallet { let maximum_channels = host_config .hrmp_max_message_num_per_candidate - .min(>::take()) as usize; + .min(>::take()) + as usize; // Note: this internally calls the `GetChannelInfo` implementation for this // pallet, which draws on the `RelevantMessagingState`. That in turn has diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 470e00fe94e..3964ecf28ca 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -20,7 +20,7 @@ use cumulus_pallet_parachain_system::AnyRelayNumber; use cumulus_primitives_core::{ChannelInfo, IsSystem, ParaId}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, Everything, Nothing, OriginTrait}, + traits::{ConstU32, Everything, OriginTrait}, BoundedSlice, }; use frame_system::EnsureRoot; @@ -30,10 +30,6 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -use xcm_builder::{ - FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, - ParentIsPreset, -}; use xcm_executor::traits::ConvertOrigin; type Block = frame_system::mocking::MockBlock; @@ -119,61 +115,6 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } -/// Means for transacting assets on this chain. -pub type LocalAssetTransactor = FungibleAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 Location into a native chain account ID: - LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): - AccountId, - // We don't track any teleports. - (), ->; - -pub type LocationToAccountId = (ParentIsPreset,); - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = (); - type IsReserve = NativeAsset; - type IsTeleporter = NativeAsset; - type UniversalLocation = UniversalLocation; - type Barrier = (); - type Weigher = FixedWeightBounds; - type Trader = (); - type ResponseHandler = (); - type AssetTrap = (); - type AssetClaims = (); - type SubscriptionService = (); - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = FrameTransactionalProcessor; - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); - type XcmRecorder = (); -} - -pub type XcmRouter = ( - // XCMP to communicate with the sibling chains. - XcmpQueue, -); - pub struct SystemParachainAsSuperuser(PhantomData); impl ConvertOrigin for SystemParachainAsSuperuser 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 d6cf819118e..470b4d0f389 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 @@ -265,7 +265,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = AssetHubRococo::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced 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 ddb82a954a8..ee0f297792f 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 @@ -265,7 +265,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = AssetHubWestend::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced 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 44e6b3934f0..2619ca7591d 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 @@ -107,7 +107,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = PeopleRococo::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced 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 83888031723..d9a2c23ac0c 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 @@ -107,7 +107,9 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let delivery_fees = PeopleWestend::execute_with(|| { xcm_helpers::teleport_assets_delivery_fees::< ::XcmSender, - >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + >( + test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest + ) }); // Sender's balance is reduced 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 e46734dfa97..430c5bd1856 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -545,7 +545,8 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | + RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( @@ -866,7 +867,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; - type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type WeightInfo = weights::pallet_nft_fractionalization::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); 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 946b72e966e..8565cd30f43 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -543,7 +543,8 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } | RuntimeCall::NftFractionalization { .. } | - RuntimeCall::Nfts { .. } | RuntimeCall::Uniques { .. } + RuntimeCall::Nfts { .. } | + RuntimeCall::Uniques { .. } ) }, ProxyType::AssetOwner => matches!( @@ -859,7 +860,7 @@ impl pallet_nft_fractionalization::Config for Runtime { type Assets = Assets; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; - type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type WeightInfo = weights::pallet_nft_fractionalization::WeightInfo; type RuntimeHoldReason = RuntimeHoldReason; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); 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 67b585ecfe8..c8022214230 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -1143,7 +1143,8 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor .with_balances(vec![( foreign_creator_as_account_id.clone(), existential_deposit + - asset_deposit + metadata_deposit_base + + asset_deposit + + metadata_deposit_base + metadata_deposit_per_byte_eta + buy_execution_fee_amount.into() + buy_execution_fee_amount.into(), 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 4ff388f4ba2..d168d1b7a13 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 @@ -301,12 +301,16 @@ fn relayed_incoming_message_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { - bridge_hub_test_utils::open_bridge_with_storage::< - Runtime, - XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) - }) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, @@ -331,12 +335,16 @@ fn free_relay_extrinsic_works() { XcmOverBridgeHubRococoInstance, LocationToAccountId, WestendLocation, - >(SiblingParachainLocation::get(), BridgedUniversalLocation::get(), |locations, fee| { - bridge_hub_test_utils::open_bridge_with_storage::< - Runtime, - XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) - }) + >( + SiblingParachainLocation::get(), + BridgedUniversalLocation::get(), + |locations, fee| { + bridge_hub_test_utils::open_bridge_with_storage::< + Runtime, + XcmOverBridgeHubRococoInstance, + >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + }, + ) .1 }, construct_and_apply_extrinsic, 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 663558f5fd5..9c5d6269dc0 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 @@ -506,11 +506,12 @@ pub fn message_dispatch_routing_works< // 2. this message is sent from other global consensus with destination of this Runtime // sibling parachain (HRMP) - let bridging_message = test_data::simulate_message_exporter_on_bridged_chain::< - BridgedNetwork, - NetworkWithParentCount, - AlwaysLatest, - >((RuntimeNetwork::get(), [Parachain(sibling_parachain_id)].into())); + let bridging_message = + test_data::simulate_message_exporter_on_bridged_chain::< + BridgedNetwork, + NetworkWithParentCount, + AlwaysLatest, + >((RuntimeNetwork::get(), [Parachain(sibling_parachain_id)].into())); // 2.1. WITHOUT opened hrmp channel -> RoutingError let result = diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs index a7c8bc23935..c7e81d67bba 100644 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ b/cumulus/parachains/runtimes/starters/shell/src/lib.rs @@ -218,11 +218,7 @@ parameter_types! { impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< - cumulus_primitives_core::AggregateMessageOrigin, - >; - #[cfg(not(feature = "runtime-benchmarks"))] + // NOTE: Changes are needed here if you add benchmarking to the runtime. type MessageProcessor = xcm_builder::ProcessXcmMessage< AggregateMessageOrigin, xcm_executor::XcmExecutor, diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs index 02aa867d70f..bd4ff167d8f 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs @@ -24,12 +24,14 @@ use utils::{impl_node_runtime_apis, imports::*}; type CustomBlock = crate::common::types::Block; pub mod aura_sr25519 { use super::*; + #[allow(dead_code)] struct FakeRuntime; impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::sr25519::AuthorityId); } pub mod aura_ed25519 { use super::*; + #[allow(dead_code)] struct FakeRuntime; impl_node_runtime_apis!(FakeRuntime, CustomBlock, sp_consensus_aura::ed25519::AuthorityId); } diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 6bd14d136a6..8530f5b8748 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -385,7 +385,8 @@ impl< FungiblesAssetMatcher, OnUnbalanced, AccountId, - > where + > +where Fungibles::Balance: Into, { fn new() -> Self { @@ -545,7 +546,8 @@ impl< FungiblesAssetMatcher, OnUnbalanced, AccountId, - > where + > +where Fungibles::Balance: Into, { fn drop(&mut self) { diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 16576e4b272..34ada58bbcd 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -29,8 +29,6 @@ use sp_keyring::Sr25519Keyring; use std::net::ToSocketAddrs; pub use crate::error::Error; -#[cfg(feature = "hostperfcheck")] -pub use polkadot_performance_test::PerfCheckError; #[cfg(feature = "pyroscope")] use pyroscope_pprofrs::{pprof_backend, PprofConfig}; diff --git a/polkadot/node/core/approval-voting-parallel/src/lib.rs b/polkadot/node/core/approval-voting-parallel/src/lib.rs index 18c73cfba1f..1a7ef756bdf 100644 --- a/polkadot/node/core/approval-voting-parallel/src/lib.rs +++ b/polkadot/node/core/approval-voting-parallel/src/lib.rs @@ -878,7 +878,8 @@ pub struct ApprovalVotingToApprovalDistribution> - overseer::SubsystemSender for ApprovalVotingToApprovalDistribution + overseer::SubsystemSender + for ApprovalVotingToApprovalDistribution { #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] fn send_message<'life0, 'async_trait>( diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index bf6ea0c9814..5c456d22b21 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -320,7 +320,6 @@ pub struct BlockImportedCandidates { pub block_hash: Hash, pub block_number: BlockNumber, pub block_tick: Tick, - pub no_show_duration: Tick, pub imported_candidates: Vec<(CandidateHash, CandidateEntry)>, } @@ -469,14 +468,7 @@ pub(crate) async fn handle_new_head< None => return Ok(Vec::new()), }; - let (block_tick, no_show_duration) = { - let block_tick = slot_number_to_tick(state.slot_duration_millis, slot); - let no_show_duration = slot_number_to_tick( - state.slot_duration_millis, - Slot::from(u64::from(session_info.no_show_slots)), - ); - (block_tick, no_show_duration) - }; + let block_tick = slot_number_to_tick(state.slot_duration_millis, slot); let needed_approvals = session_info.needed_approvals; let validator_group_lens: Vec = @@ -595,7 +587,6 @@ pub(crate) async fn handle_new_head< block_hash, block_number: block_header.number, block_tick, - no_show_duration, imported_candidates: candidate_entries .into_iter() .map(|(h, e)| (h, e.into())) diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 2149ce81fa8..23f052b95f6 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -2548,7 +2548,12 @@ fn schedule_wakeup_action( last_assignment_tick.map(|l| l + APPROVAL_DELAY).filter(|t| t > &tick_now), next_no_show, ) - .map(|tick| Action::ScheduleWakeup { block_hash, block_number, candidate_hash, tick }) + .map(|tick| Action::ScheduleWakeup { + block_hash, + block_number, + candidate_hash, + tick, + }) }, RequiredTranches::Pending { considered, next_no_show, clock_drift, .. } => { // select the minimum of `next_no_show`, or the tick of the next non-empty tranche diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 8aa78df5495..ec825b800c7 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -253,7 +253,8 @@ where _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, _backing_groups: Vec, - ) -> Result { + ) -> Result + { self.1(validator_index) } } diff --git a/polkadot/node/core/pvf/build.rs b/polkadot/node/core/pvf/build.rs index e01cc6deecc..e46f2dc5f55 100644 --- a/polkadot/node/core/pvf/build.rs +++ b/polkadot/node/core/pvf/build.rs @@ -16,6 +16,6 @@ fn main() { if let Ok(profile) = std::env::var("PROFILE") { - println!(r#"cargo:rustc-cfg=build_type="{}""#, profile); + println!(r#"cargo:rustc-cfg=build_profile="{}""#, profile); } } diff --git a/polkadot/node/core/pvf/common/src/worker/security/change_root.rs b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs index 9ec66906819..fcfaf6541c2 100644 --- a/polkadot/node/core/pvf/common/src/worker/security/change_root.rs +++ b/polkadot/node/core/pvf/common/src/worker/security/change_root.rs @@ -124,7 +124,8 @@ fn try_restrict(worker_info: &WorkerInfo) -> Result<()> { libc::MS_BIND | libc::MS_REC | libc::MS_NOEXEC | libc::MS_NODEV | libc::MS_NOSUID | - libc::MS_NOATIME | additional_flags, + libc::MS_NOATIME | + additional_flags, ptr::null(), // ignored when MS_BIND is used ) < 0 { diff --git a/polkadot/node/core/pvf/src/artifacts.rs b/polkadot/node/core/pvf/src/artifacts.rs index 119af34082a..1126a0c90c8 100644 --- a/polkadot/node/core/pvf/src/artifacts.rs +++ b/polkadot/node/core/pvf/src/artifacts.rs @@ -56,7 +56,7 @@ use crate::{host::PrecheckResultSender, worker_interface::WORKER_DIR_PREFIX}; use always_assert::always; -use polkadot_node_core_pvf_common::{error::PrepareError, prepare::PrepareStats, pvf::PvfPrepData}; +use polkadot_node_core_pvf_common::{error::PrepareError, pvf::PvfPrepData}; use polkadot_parachain_primitives::primitives::ValidationCodeHash; use polkadot_primitives::ExecutorParamsPrepHash; use std::{ @@ -144,8 +144,6 @@ pub enum ArtifactState { last_time_needed: SystemTime, /// Size in bytes size: u64, - /// Stats produced by successful preparation. - prepare_stats: PrepareStats, }, /// A task to prepare this artifact is scheduled. Preparing { @@ -269,15 +267,11 @@ impl Artifacts { path: PathBuf, last_time_needed: SystemTime, size: u64, - prepare_stats: PrepareStats, ) { // See the precondition. always!(self .inner - .insert( - artifact_id, - ArtifactState::Prepared { path, last_time_needed, size, prepare_stats } - ) + .insert(artifact_id, ArtifactState::Prepared { path, last_time_needed, size }) .is_none()); } @@ -384,21 +378,18 @@ mod tests { path1.clone(), mock_now - Duration::from_secs(5), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id2.clone(), path2.clone(), mock_now - Duration::from_secs(10), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id3.clone(), path3.clone(), mock_now - Duration::from_secs(15), 1024, - PrepareStats::default(), ); let pruned = artifacts.prune(&cleanup_config); @@ -432,21 +423,18 @@ mod tests { path1.clone(), mock_now - Duration::from_secs(5), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id2.clone(), path2.clone(), mock_now - Duration::from_secs(10), 1024, - PrepareStats::default(), ); artifacts.insert_prepared( artifact_id3.clone(), path3.clone(), mock_now - Duration::from_secs(15), 1024, - PrepareStats::default(), ); let pruned = artifacts.prune(&cleanup_config); diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index 44a4cba2fbf..22943a06c43 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -813,12 +813,8 @@ async fn handle_prepare_done( } *state = match result { - Ok(PrepareSuccess { path, stats: prepare_stats, size }) => ArtifactState::Prepared { - path, - last_time_needed: SystemTime::now(), - size, - prepare_stats, - }, + Ok(PrepareSuccess { path, size, .. }) => + ArtifactState::Prepared { path, last_time_needed: SystemTime::now(), size }, Err(error) => { let last_time_failed = SystemTime::now(); let num_failures = *num_failures + 1; @@ -976,7 +972,6 @@ pub(crate) mod tests { use crate::{artifacts::generate_artifact_path, testing::artifact_id, PossiblyInvalidError}; use assert_matches::assert_matches; use futures::future::BoxFuture; - use polkadot_node_core_pvf_common::prepare::PrepareStats; use polkadot_node_primitives::BlockData; use sp_core::H256; @@ -1196,20 +1191,8 @@ pub(crate) mod tests { builder.cleanup_config = ArtifactsCleanupConfig::new(1024, Duration::from_secs(0)); let path1 = generate_artifact_path(cache_path); let path2 = generate_artifact_path(cache_path); - builder.artifacts.insert_prepared( - artifact_id(1), - path1.clone(), - mock_now, - 1024, - PrepareStats::default(), - ); - builder.artifacts.insert_prepared( - artifact_id(2), - path2.clone(), - mock_now, - 1024, - PrepareStats::default(), - ); + builder.artifacts.insert_prepared(artifact_id(1), path1.clone(), mock_now, 1024); + builder.artifacts.insert_prepared(artifact_id(2), path2.clone(), mock_now, 1024); let mut test = builder.build(); let mut host = test.host_handle(); diff --git a/polkadot/node/core/pvf/src/prepare/pool.rs b/polkadot/node/core/pvf/src/prepare/pool.rs index 4e11f977c9e..67cd71812e5 100644 --- a/polkadot/node/core/pvf/src/prepare/pool.rs +++ b/polkadot/node/core/pvf/src/prepare/pool.rs @@ -343,14 +343,13 @@ fn handle_mux( ), // Return `Concluded`, but do not kill the worker since the error was on the host // side. - Outcome::RenameTmpFile { worker: idle, result: _, err, src, dest } => - handle_concluded_no_rip( - from_pool, - spawned, - worker, - idle, - Err(PrepareError::RenameTmpFile { err, src, dest }), - ), + Outcome::RenameTmpFile { worker: idle, err, src, dest } => handle_concluded_no_rip( + from_pool, + spawned, + worker, + idle, + Err(PrepareError::RenameTmpFile { err, src, dest }), + ), // Could not clear worker cache. Kill the worker so other jobs can't see the data. Outcome::ClearWorkerDir { err } => { if attempt_retire(metrics, spawned, worker) { diff --git a/polkadot/node/core/pvf/src/prepare/worker_interface.rs b/polkadot/node/core/pvf/src/prepare/worker_interface.rs index d29d2717c4b..718416e8be7 100644 --- a/polkadot/node/core/pvf/src/prepare/worker_interface.rs +++ b/polkadot/node/core/pvf/src/prepare/worker_interface.rs @@ -81,7 +81,6 @@ pub enum Outcome { /// final destination location. RenameTmpFile { worker: IdleWorker, - result: PrepareWorkerResult, err: String, // Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible // conversion to `Option`. @@ -287,7 +286,6 @@ async fn handle_response( ); Outcome::RenameTmpFile { worker, - result, err: format!("{:?}", err), src: tmp_file.to_str().map(String::from), dest: artifact_path.to_str().map(String::from), diff --git a/polkadot/node/core/pvf/src/testing.rs b/polkadot/node/core/pvf/src/testing.rs index 8c75dafa69c..9a4004f3903 100644 --- a/polkadot/node/core/pvf/src/testing.rs +++ b/polkadot/node/core/pvf/src/testing.rs @@ -72,7 +72,7 @@ pub fn build_workers_and_get_paths() -> (PathBuf, PathBuf) { "--bin=polkadot-execute-worker", ]; - if cfg!(build_type = "release") { + if cfg!(build_profile = "release") { build_args.push("--release"); } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 068559dea76..063e71f2f52 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -535,7 +535,8 @@ impl AssignmentCriteria for MockAssignmentCriteria { _relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory, _assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2, _backing_groups: Vec, - ) -> Result { + ) -> Result + { self.tranche } } diff --git a/polkadot/node/network/availability-distribution/src/tests/mod.rs b/polkadot/node/network/availability-distribution/src/tests/mod.rs index 3320871bceb..078220607c3 100644 --- a/polkadot/node/network/availability-distribution/src/tests/mod.rs +++ b/polkadot/node/network/availability-distribution/src/tests/mod.rs @@ -45,7 +45,7 @@ fn test_harness>( let (context, virtual_overseer) = polkadot_node_subsystem_test_helpers::make_subsystem_context(pool.clone()); - let (pov_req_receiver, pov_req_cfg) = IncomingRequest::get_config_receiver::< + let (pov_req_receiver, _pov_req_cfg) = IncomingRequest::get_config_receiver::< Block, sc_network::NetworkWorker, >(&req_protocol_names); @@ -65,13 +65,8 @@ fn test_harness>( ); let subsystem = subsystem.run(context); - let test_fut = test_fx(TestHarness { - virtual_overseer, - pov_req_cfg, - chunk_req_v1_cfg, - chunk_req_v2_cfg, - pool, - }); + let test_fut = + test_fx(TestHarness { virtual_overseer, chunk_req_v1_cfg, chunk_req_v2_cfg, pool }); futures::pin_mut!(test_fut); futures::pin_mut!(subsystem); diff --git a/polkadot/node/network/availability-distribution/src/tests/state.rs b/polkadot/node/network/availability-distribution/src/tests/state.rs index 97e616f79fb..53d6fd2c530 100644 --- a/polkadot/node/network/availability-distribution/src/tests/state.rs +++ b/polkadot/node/network/availability-distribution/src/tests/state.rs @@ -60,7 +60,6 @@ type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContex >; pub struct TestHarness { pub virtual_overseer: VirtualOverseer, - pub pov_req_cfg: RequestResponseConfig, pub chunk_req_v1_cfg: RequestResponseConfig, pub chunk_req_v2_cfg: RequestResponseConfig, pub pool: TaskExecutor, diff --git a/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs b/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs index b6376a5b543..6b34538b626 100644 --- a/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs +++ b/polkadot/node/network/availability-recovery/src/task/strategy/chunks.rs @@ -107,9 +107,10 @@ impl FetchChunks { state: &mut State, common_params: &RecoveryParams, ) -> Result { - let recovery_duration = common_params - .metrics - .time_erasure_recovery(RecoveryStrategy::::strategy_type(self)); + let recovery_duration = + common_params + .metrics + .time_erasure_recovery(RecoveryStrategy::::strategy_type(self)); // Send request to reconstruct available data from chunks. let (avilable_data_tx, available_data_rx) = oneshot::channel(); @@ -136,18 +137,16 @@ impl FetchChunks { // Attempt post-recovery check. Ok(data) => do_post_recovery_check(common_params, data) .await - .map_err(|e| { + .inspect_err(|_| { recovery_duration.map(|rd| rd.stop_and_discard()); - e }) - .map(|data| { + .inspect(|_| { gum::trace!( target: LOG_TARGET, candidate_hash = ?common_params.candidate_hash, erasure_root = ?common_params.erasure_root, "Data recovery from chunks complete", ); - data }), Err(err) => { recovery_duration.map(|rd| rd.stop_and_discard()); diff --git a/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs b/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs index 677bc2d1375..8b8cff54991 100644 --- a/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs +++ b/polkadot/node/network/availability-recovery/src/task/strategy/systematic.rs @@ -125,18 +125,16 @@ impl FetchSystematicChunks { // Attempt post-recovery check. do_post_recovery_check(common_params, data) .await - .map_err(|e| { + .inspect_err(|_| { recovery_duration.map(|rd| rd.stop_and_discard()); - e }) - .map(|data| { + .inspect(|_| { gum::trace!( target: LOG_TARGET, candidate_hash = ?common_params.candidate_hash, erasure_root = ?common_params.erasure_root, "Data recovery from systematic chunks complete", ); - data }) }, Err(err) => { diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index 10a4320433a..e30ed69f9e3 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -60,6 +60,7 @@ // unused dependencies can not work for test and examples at the same time // yielding false positives #![warn(missing_docs)] +#![allow(dead_code)] // TODO https://github.com/paritytech/polkadot-sdk/issues/5793 use std::{ collections::{hash_map, HashMap}, diff --git a/polkadot/node/service/src/fake_runtime_api.rs b/polkadot/node/service/src/fake_runtime_api.rs index 1f2efdbbb5b..d8f147a9cf7 100644 --- a/polkadot/node/service/src/fake_runtime_api.rs +++ b/polkadot/node/service/src/fake_runtime_api.rs @@ -53,6 +53,7 @@ sp_api::decl_runtime_apis! { } } +#[allow(dead_code)] struct Runtime; sp_api::impl_runtime_apis! { diff --git a/polkadot/node/service/src/parachains_db/mod.rs b/polkadot/node/service/src/parachains_db/mod.rs index 59af30dceeb..887db80a303 100644 --- a/polkadot/node/service/src/parachains_db/mod.rs +++ b/polkadot/node/service/src/parachains_db/mod.rs @@ -100,18 +100,11 @@ pub struct CacheSizes { pub availability_meta: usize, /// Cache used by approval data. pub approval_data: usize, - /// Cache used by session window data - pub session_data: usize, } impl Default for CacheSizes { fn default() -> Self { - CacheSizes { - availability_data: 25, - availability_meta: 1, - approval_data: 5, - session_data: 1, - } + CacheSizes { availability_data: 25, availability_meta: 1, approval_data: 5 } } } diff --git a/polkadot/node/service/src/tests.rs b/polkadot/node/service/src/tests.rs index 85b750ddad6..78bbfcd5444 100644 --- a/polkadot/node/service/src/tests.rs +++ b/polkadot/node/service/src/tests.rs @@ -63,9 +63,6 @@ struct TestHarness { finality_target_rx: Receiver>, } -#[derive(Default)] -struct HarnessConfig; - fn test_harness>( case_vars: CaseVars, test: impl FnOnce(TestHarness) -> T, diff --git a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs index da25a3bf3b7..807afb0438c 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/message_generator.rs @@ -401,7 +401,7 @@ impl PeerMessagesGenerator { /// 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. +/// test. fn random_samplings_to_node( node_under_test: ValidatorIndex, num_validators: usize, @@ -474,8 +474,7 @@ fn issue_approvals( coalesce_approvals_len(options.coalesce_mean, options.coalesce_std_dev, rand_chacha); let result = assignments .iter() - .enumerate() - .map(|(_index, message)| match &message.msg { + .map(|message| match &message.msg { protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { let mut approvals_to_create = Vec::new(); diff --git a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs index e2d50f28568..dd7095d3b00 100644 --- a/polkadot/node/subsystem-bench/src/lib/statement/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/statement/mod.rs @@ -114,14 +114,14 @@ fn build_overseer( state.pvd.clone(), state.own_backing_group.clone(), ); - let (statement_req_receiver, statement_req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker, - >(&ReqProtocolNames::new(GENESIS_HASH, None)); - let (candidate_req_receiver, candidate_req_cfg) = IncomingRequest::get_config_receiver::< - Block, - sc_network::NetworkWorker, - >(&ReqProtocolNames::new(GENESIS_HASH, None)); + let (statement_req_receiver, statement_req_cfg) = + IncomingRequest::get_config_receiver::>( + &ReqProtocolNames::new(GENESIS_HASH, None), + ); + let (candidate_req_receiver, candidate_req_cfg) = + IncomingRequest::get_config_receiver::>( + &ReqProtocolNames::new(GENESIS_HASH, None), + ); let keystore = make_keystore(); let subsystem = StatementDistributionSubsystem::new( keystore.clone(), diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 7938223df23..a8af8b7996f 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -665,7 +665,8 @@ where fn number( &self, hash: Block::Hash, - ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> + { self.client.number(hash) } diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index ddebe99e621..3c90c050bae 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -36,7 +36,7 @@ //! //! Let's see a quick example: //! -//! ```rust(ignore) +//! ```nocompile //! sp_api::decl_runtime_apis! { //! #[api_version(2)] //! pub trait MyApi { diff --git a/polkadot/primitives/src/v8/metrics.rs b/polkadot/primitives/src/v8/metrics.rs index 1d66c9848a7..409efc86bc9 100644 --- a/polkadot/primitives/src/v8/metrics.rs +++ b/polkadot/primitives/src/v8/metrics.rs @@ -91,18 +91,6 @@ pub type RuntimeMetricLabelValue = RuntimeMetricLabel; /// A set of metric label values. pub type RuntimeMetricLabelValues = RuntimeMetricLabels; -/// Trait for converting Vec to `&str`. -pub trait AsStr { - /// Return a str reference. - fn as_str(&self) -> Option<&str>; -} - -impl AsStr for RuntimeMetricLabel { - fn as_str(&self) -> Option<&str> { - alloc::str::from_utf8(&self.0).ok() - } -} - impl From<&'static str> for RuntimeMetricLabel { fn from(s: &'static str) -> Self { Self(s.as_bytes().to_vec()) diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs index b149404b41b..220543f00ec 100644 --- a/polkadot/runtime/parachains/src/hrmp.rs +++ b/polkadot/runtime/parachains/src/hrmp.rs @@ -945,7 +945,7 @@ impl Pallet { outgoing_paras.len() as u32 )) .saturating_add(::WeightInfo::force_process_hrmp_close( - outgoing_paras.len() as u32 + outgoing_paras.len() as u32, )) } diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index e014529ea11..cc333e3b20f 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -991,9 +991,8 @@ impl Pallet { .iter() .filter_map(|d| { BoundedSlice::try_from(&d[..]) - .map_err(|e| { + .inspect_err(|_| { defensive!("Accepted candidate contains too long msg, len=", d.len()); - e }) .ok() }) diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 84d8299cd29..70c4ba72d58 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -1216,18 +1216,19 @@ fn filter_backed_statements_from_disabled_validators< // 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.acquire_info(bc.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.", - bc.descriptor().relay_parent() - ); - return false - }, - }; + let relay_parent_block_number = match allowed_relay_parents + .acquire_info(bc.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.", + bc.descriptor().relay_parent() + ); + return false + }, + }; // Get the group index for the core let group_idx = match scheduler::Pallet::::group_assigned_to_core( diff --git a/polkadot/runtime/parachains/src/ump_tests.rs b/polkadot/runtime/parachains/src/ump_tests.rs index d914bf8b666..91571859ecf 100644 --- a/polkadot/runtime/parachains/src/ump_tests.rs +++ b/polkadot/runtime/parachains/src/ump_tests.rs @@ -462,10 +462,11 @@ fn verify_relay_dispatch_queue_size_is_externally_accessible() { fn assert_queue_size(para: ParaId, count: u32, size: u32) { #[allow(deprecated)] - let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(para)).expect( - "enqueuing a message should create the dispatch queue\ + let raw_queue_size = sp_io::storage::get(&well_known_keys::relay_dispatch_queue_size(para)) + .expect( + "enqueuing a message should create the dispatch queue\ and it should be accessible via the well known keys", - ); + ); let (c, s) = <(u32, u32)>::decode(&mut &raw_queue_size[..]) .expect("the dispatch queue size should be decodable into (u32, u32)"); assert_eq!((c, s), (count, size)); diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index d6ed11d0787..576e297df31 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1110,7 +1110,8 @@ impl InstanceFilter for ProxyType { matches!( c, RuntimeCall::Staking(..) | - RuntimeCall::Session(..) | RuntimeCall::Utility(..) | + RuntimeCall::Session(..) | + RuntimeCall::Utility(..) | RuntimeCall::FastUnstake(..) | RuntimeCall::VoterList(..) | RuntimeCall::NominationPools(..) @@ -2761,49 +2762,3 @@ sp_api::impl_runtime_apis! { } } } - -mod clean_state_migration { - use super::Runtime; - #[cfg(feature = "try-runtime")] - use super::Vec; - use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; - use pallet_state_trie_migration::MigrationLimits; - - #[storage_alias] - type AutoLimits = StorageValue, ValueQuery>; - - // Actual type of value is `MigrationTask`, putting a dummy - // one to avoid the trait constraint on T. - // Since we only use `kill` it is fine. - #[storage_alias] - type MigrationProcess = StorageValue; - - #[storage_alias] - type SignedMigrationMaxLimits = StorageValue; - - /// Initialize an automatic migration process. - pub struct CleanMigrate; - - impl OnRuntimeUpgrade for CleanMigrate { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - Ok(Default::default()) - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - MigrationProcess::kill(); - AutoLimits::kill(); - SignedMigrationMaxLimits::kill(); - ::DbWeight::get().writes(3) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - frame_support::ensure!( - !AutoLimits::exists() && !SignedMigrationMaxLimits::exists(), - "State migration clean.", - ); - Ok(()) - } - } -} diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index 1e7b01bc472..ae11f6ccba4 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -33,7 +33,6 @@ pub mod pallet_nomination_pools; pub mod pallet_parameters; pub mod pallet_preimage; pub mod pallet_proxy; -pub mod pallet_referenda_fellowship_referenda; pub mod pallet_referenda_referenda; pub mod pallet_scheduler; pub mod pallet_session; diff --git a/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs b/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs deleted file mode 100644 index a4ac0667911..00000000000 --- a/polkadot/runtime/westend/src/weights/pallet_referenda_fellowship_referenda.rs +++ /dev/null @@ -1,525 +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_referenda` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-e8ezs4ez-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("kusama-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=kusama-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_referenda -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/kusama/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_referenda`. -pub struct WeightInfo(PhantomData); -impl pallet_referenda::WeightInfo for WeightInfo { - /// Storage: FellowshipCollective Members (r:1 w:0) - /// Proof: FellowshipCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumCount (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:0 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn submit() -> Weight { - // Proof Size summary in bytes: - // Measured: `327` - // Estimated: `42428` - // Minimum execution time: 28_969_000 picoseconds. - Weight::from_parts(30_902_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_preparing() -> Weight { - // Proof Size summary in bytes: - // Measured: `404` - // Estimated: `83866` - // Minimum execution time: 53_500_000 picoseconds. - Weight::from_parts(54_447_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `2042` - // Estimated: `42428` - // Minimum execution time: 114_321_000 picoseconds. - Weight::from_parts(122_607_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_not_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `2083` - // Estimated: `42428` - // Minimum execution time: 113_476_000 picoseconds. - Weight::from_parts(120_078_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `774` - // Estimated: `83866` - // Minimum execution time: 194_798_000 picoseconds. - Weight::from_parts(208_378_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn place_decision_deposit_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `639` - // Estimated: `83866` - // Minimum execution time: 69_502_000 picoseconds. - Weight::from_parts(71_500_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn refund_decision_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `317` - // Estimated: `4365` - // Minimum execution time: 30_561_000 picoseconds. - Weight::from_parts(31_427_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn refund_submission_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `167` - // Estimated: `4365` - // Minimum execution time: 14_535_000 picoseconds. - Weight::from_parts(14_999_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn cancel() -> Weight { - // Proof Size summary in bytes: - // Measured: `349` - // Estimated: `83866` - // Minimum execution time: 38_532_000 picoseconds. - Weight::from_parts(39_361_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:1 w:0) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn kill() -> Weight { - // Proof Size summary in bytes: - // Measured: `450` - // Estimated: `83866` - // Minimum execution time: 78_956_000 picoseconds. - Weight::from_parts(80_594_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:0) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - fn one_fewer_deciding_queue_empty() -> Weight { - // Proof Size summary in bytes: - // Measured: `140` - // Estimated: `4277` - // Minimum execution time: 9_450_000 picoseconds. - Weight::from_parts(9_881_000, 0) - .saturating_add(Weight::from_parts(0, 4277)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn one_fewer_deciding_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `2376` - // Estimated: `42428` - // Minimum execution time: 98_126_000 picoseconds. - Weight::from_parts(102_511_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn one_fewer_deciding_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `2362` - // Estimated: `42428` - // Minimum execution time: 99_398_000 picoseconds. - Weight::from_parts(104_045_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_requeued_insertion() -> Weight { - // Proof Size summary in bytes: - // Measured: `1807` - // Estimated: `4365` - // Minimum execution time: 43_734_000 picoseconds. - Weight::from_parts(46_962_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_requeued_slide() -> Weight { - // Proof Size summary in bytes: - // Measured: `1774` - // Estimated: `4365` - // Minimum execution time: 42_863_000 picoseconds. - Weight::from_parts(46_241_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `1790` - // Estimated: `4365` - // Minimum execution time: 57_511_000 picoseconds. - Weight::from_parts(64_027_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:0) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda TrackQueue (r:1 w:1) - /// Proof: FellowshipReferenda TrackQueue (max_values: None, max_size: Some(812), added: 3287, mode: MaxEncodedLen) - fn nudge_referendum_not_queued() -> Weight { - // Proof Size summary in bytes: - // Measured: `1831` - // Estimated: `4365` - // Minimum execution time: 56_726_000 picoseconds. - Weight::from_parts(61_962_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_no_deposit() -> Weight { - // Proof Size summary in bytes: - // Measured: `301` - // Estimated: `42428` - // Minimum execution time: 24_870_000 picoseconds. - Weight::from_parts(25_837_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_preparing() -> Weight { - // Proof Size summary in bytes: - // Measured: `349` - // Estimated: `42428` - // Minimum execution time: 25_297_000 picoseconds. - Weight::from_parts(26_086_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - fn nudge_referendum_timed_out() -> Weight { - // Proof Size summary in bytes: - // Measured: `208` - // Estimated: `4365` - // Minimum execution time: 16_776_000 picoseconds. - Weight::from_parts(17_396_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_deciding_failing() -> Weight { - // Proof Size summary in bytes: - // Measured: `584` - // Estimated: `42428` - // Minimum execution time: 37_780_000 picoseconds. - Weight::from_parts(38_626_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda DecidingCount (r:1 w:1) - /// Proof: FellowshipReferenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_deciding_passing() -> Weight { - // Proof Size summary in bytes: - // Measured: `719` - // Estimated: `42428` - // Minimum execution time: 85_265_000 picoseconds. - Weight::from_parts(89_986_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_begin_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `770` - // Estimated: `42428` - // Minimum execution time: 143_283_000 picoseconds. - Weight::from_parts(158_540_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_end_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `755` - // Estimated: `42428` - // Minimum execution time: 143_736_000 picoseconds. - Weight::from_parts(162_755_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_continue_not_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `770` - // Estimated: `42428` - // Minimum execution time: 139_021_000 picoseconds. - Weight::from_parts(157_398_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_continue_confirming() -> Weight { - // Proof Size summary in bytes: - // Measured: `776` - // Estimated: `42428` - // Minimum execution time: 78_530_000 picoseconds. - Weight::from_parts(83_556_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:2 w:2) - /// 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) - fn nudge_referendum_approved() -> Weight { - // Proof Size summary in bytes: - // Measured: `776` - // Estimated: `83866` - // Minimum execution time: 174_165_000 picoseconds. - Weight::from_parts(188_496_000, 0) - .saturating_add(Weight::from_parts(0, 83866)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:1) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipCollective MemberCount (r:1 w:0) - /// Proof: FellowshipCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - fn nudge_referendum_rejected() -> Weight { - // Proof Size summary in bytes: - // Measured: `772` - // Estimated: `42428` - // Minimum execution time: 142_964_000 picoseconds. - Weight::from_parts(157_257_000, 0) - .saturating_add(Weight::from_parts(0, 42428)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: Preimage StatusFor (r:1 w:0) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:0 w:1) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn set_some_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `352` - // Estimated: `4365` - // Minimum execution time: 20_126_000 picoseconds. - Weight::from_parts(20_635_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: FellowshipReferenda ReferendumInfoFor (r:1 w:0) - /// Proof: FellowshipReferenda ReferendumInfoFor (max_values: None, max_size: Some(900), added: 3375, mode: MaxEncodedLen) - /// Storage: FellowshipReferenda MetadataOf (r:1 w:1) - /// Proof: FellowshipReferenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - fn clear_metadata() -> Weight { - // Proof Size summary in bytes: - // Measured: `285` - // Estimated: `4365` - // Minimum execution time: 17_716_000 picoseconds. - Weight::from_parts(18_324_000, 0) - .saturating_add(Weight::from_parts(0, 4365)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } -} diff --git a/polkadot/scripts/list-syscalls/execute-worker-syscalls b/polkadot/scripts/list-syscalls/execute-worker-syscalls index 349af783cf1..4b22f678786 100644 --- a/polkadot/scripts/list-syscalls/execute-worker-syscalls +++ b/polkadot/scripts/list-syscalls/execute-worker-syscalls @@ -20,6 +20,7 @@ 24 (sched_yield) 25 (mremap) 28 (madvise) +34 (pause) 39 (getpid) 41 (socket) 42 (connect) diff --git a/polkadot/scripts/list-syscalls/prepare-worker-syscalls b/polkadot/scripts/list-syscalls/prepare-worker-syscalls index 05281b61591..fd46a788537 100644 --- a/polkadot/scripts/list-syscalls/prepare-worker-syscalls +++ b/polkadot/scripts/list-syscalls/prepare-worker-syscalls @@ -20,6 +20,7 @@ 24 (sched_yield) 25 (mremap) 28 (madvise) +34 (pause) 39 (getpid) 41 (socket) 42 (connect) diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 1e90338a0f1..e3c470fcdee 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -245,7 +245,8 @@ impl CandidateData { pub fn attested( &self, validity_threshold: usize, - ) -> Option> { + ) -> Option> + { let valid_votes = self.validity_votes.len(); if valid_votes < validity_threshold { return None @@ -321,7 +322,8 @@ impl Table { digest: &Ctx::Digest, context: &Ctx, minimum_backing_votes: u32, - ) -> Option> { + ) -> Option> + { self.candidate_votes.get(digest).and_then(|data| { let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| { effective_minimum_backing_votes(len, minimum_backing_votes) diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs index 99f17693093..7cb230f6e00 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/parachain/xcm_config.rs @@ -152,7 +152,7 @@ impl pallet_xcm::Config for Runtime { // We turn off sending for these tests type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = super::super::network::ParachainXcmRouter; // Provided by xcm-simulator - // Anyone can execute XCM programs + // Anyone can execute XCM programs type ExecuteXcmOrigin = EnsureXcmOrigin; // We execute any type of program type XcmExecuteFilter = Everything; diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs index 987bb3f9ab6..a31e664d821 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/relay_chain/xcm_config.rs @@ -125,7 +125,7 @@ impl pallet_xcm::Config for Runtime { // No one can call `send` type SendXcmOrigin = EnsureXcmOrigin; type XcmRouter = super::super::network::RelayChainXcmRouter; // Provided by xcm-simulator - // Anyone can execute XCM programs + // Anyone can execute XCM programs type ExecuteXcmOrigin = EnsureXcmOrigin; // We execute any type of program type XcmExecuteFilter = Everything; diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 4a12bb7f47c..210b8f65637 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -72,7 +72,7 @@ pub fn generate_holding_assets(max_assets: u32) -> Assets { let fungibles_amount: u128 = 100; let holding_fungibles = max_assets / 2; let holding_non_fungibles = max_assets - holding_fungibles - 1; // -1 because of adding `Here` asset - // add count of `holding_fungibles` + // add count of `holding_fungibles` (0..holding_fungibles) .map(|i| { Asset { diff --git a/polkadot/xcm/procedural/tests/ui.rs b/polkadot/xcm/procedural/tests/ui.rs index b3469b520eb..4d0c8af4500 100644 --- a/polkadot/xcm/procedural/tests/ui.rs +++ b/polkadot/xcm/procedural/tests/ui.rs @@ -16,7 +16,6 @@ //! UI tests for XCM procedural macros -#[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. diff --git a/polkadot/xcm/xcm-builder/src/asset_conversion.rs b/polkadot/xcm/xcm-builder/src/asset_conversion.rs index 16ae05c2079..6d090b04886 100644 --- a/polkadot/xcm/xcm-builder/src/asset_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/asset_conversion.rs @@ -137,7 +137,13 @@ impl< ConvertClassId: MaybeEquivalence, ConvertInstanceId: MaybeEquivalence, > MatchesNonFungibles - for MatchedConvertedConcreteId + for MatchedConvertedConcreteId< + ClassId, + InstanceId, + MatchClassId, + ConvertClassId, + ConvertInstanceId, + > { fn matches_nonfungibles(a: &Asset) -> result::Result<(ClassId, InstanceId), MatchError> { let (instance, class) = match (&a.fun, &a.id) { diff --git a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs index b111a05a4f1..006c28954bc 100644 --- a/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/nonfungibles_adapter.rs @@ -270,7 +270,14 @@ impl< CheckAsset: AssetChecking, CheckingAccount: Get>, > TransactAsset - for NonFungiblesAdapter + for NonFungiblesAdapter< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + > { fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { NonFungiblesMutateAdapter::< diff --git a/polkadot/xcm/xcm-executor/src/traits/weight.rs b/polkadot/xcm/xcm-executor/src/traits/weight.rs index 72de3e0f433..61545c33062 100644 --- a/polkadot/xcm/xcm-executor/src/traits/weight.rs +++ b/polkadot/xcm/xcm-executor/src/traits/weight.rs @@ -29,13 +29,6 @@ pub trait WeightBounds { fn instr_weight(instruction: &Instruction) -> Result; } -/// A means of getting approximate weight consumption for a given destination message executor and a -/// message. -pub trait UniversalWeigher { - /// Get the upper limit of weight required for `dest` to execute `message`. - fn weigh(dest: impl Into, message: Xcm<()>) -> Result; -} - /// Charge for weight in order to execute XCM. /// /// A `WeightTrader` may also be put into a tuple, in which case the default behavior of diff --git a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs index c51a4a5376a..f0a70b0dacf 100644 --- a/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs +++ b/polkadot/xcm/xcm-runtime-apis/src/dry_run.rs @@ -57,7 +57,12 @@ sp_api::decl_runtime_apis! { /// Calls or XCMs might fail when executed, this doesn't mean the result of these calls will be an `Err`. /// In those cases, there might still be a valid result, with the execution error inside it. /// The only reasons why these calls might return an error are listed in the [`Error`] enum. - pub trait DryRunApi { + pub trait DryRunApi + where + Call: Encode, + Event: Decode, + OriginCaller: Encode + { /// Dry run call. fn dry_run_call(origin: OriginCaller, call: Call) -> Result, Error>; diff --git a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs index 889a50a2bab..2d14b4e571c 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs @@ -197,7 +197,7 @@ fn fee_estimation_for_teleport() { fn dry_run_reserve_asset_transfer() { sp_tracing::init_for_tests(); let who = 1; // AccountId = u64. - // Native token used for fees. + // Native token used for fees. let balances = vec![(who, DeliveryFees::get() + ExistentialDeposit::get())]; // Relay token is the one we want to transfer. let assets = vec![(1, who, 100)]; // id, account_id, balance. diff --git a/prdoc/pr_4851.prdoc b/prdoc/pr_4851.prdoc index 923ca4bfff5..2110a68d401 100644 --- a/prdoc/pr_4851.prdoc +++ b/prdoc/pr_4851.prdoc @@ -5,8 +5,8 @@ title: Add support for deprecation metadata in `RuntimeMetadataIr` entries. doc: - audience: - - Runtime dev - - Runtime user + - Runtime Dev + - Runtime User description: | Changes introduced are listed below. Adds `DeprecationStatusIR` enum to sp_metadata_ir. diff --git a/prdoc/pr_5676.prdoc b/prdoc/pr_5676.prdoc new file mode 100644 index 00000000000..dfe23e120b4 --- /dev/null +++ b/prdoc/pr_5676.prdoc @@ -0,0 +1,174 @@ +title: '[ci] Update CI image with rust 1.81.0 and 2024-09-11' +doc: +- audience: [Runtime Dev, Node Dev, Node Operator] + description: |- + cc https://github.com/paritytech/ci_cd/issues/1035 + + close https://github.com/paritytech/ci_cd/issues/1023 +crates: +- name: pallet-xcm-bridge-hub + bump: patch +- name: snowbridge-router-primitives + bump: patch +- name: snowbridge-runtime-common + bump: patch +- name: cumulus-pallet-parachain-system + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: asset-test-utils + bump: patch +- name: bridge-hub-test-utils + bump: patch +- name: cumulus-primitives-utility + bump: patch +- name: polkadot-node-core-approval-voting + bump: patch +- name: polkadot-node-core-pvf-common + bump: patch +- name: polkadot-approval-distribution + bump: patch +- name: polkadot-availability-recovery + bump: patch +- name: polkadot-node-subsystem-types + bump: patch +- name: polkadot-runtime-parachains + bump: patch +- name: westend-runtime + bump: patch +- name: polkadot-statement-table + bump: patch +- name: pallet-xcm-benchmarks + bump: patch +- name: staging-xcm-builder + bump: patch +- name: xcm-runtime-apis + bump: patch +- name: sc-cli + bump: patch +- name: sc-consensus-grandpa + bump: patch +- name: sc-network + bump: patch +- name: sc-network-sync + bump: patch +- name: sc-rpc-spec-v2 + bump: patch +- name: pallet-bags-list + bump: patch +- name: pallet-balances + bump: patch +- name: pallet-bounties + bump: patch +- name: pallet-child-bounties + bump: patch +- name: pallet-nis + bump: patch +- name: pallet-referenda + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: pallet-society + bump: patch +- name: pallet-staking + bump: patch +- name: frame-support-procedural + bump: patch +- name: frame-support + bump: patch +- name: pallet-transaction-payment + bump: patch +- name: pallet-utility + bump: patch +- name: pallet-vesting + bump: patch +- name: substrate-wasm-builder + bump: patch +- name: snowbridge-outbound-queue-merkle-tree + bump: patch +- name: shell-runtime + bump: patch +- name: polkadot-parachain-lib + bump: patch +- name: polkadot-cli + bump: patch +- name: polkadot-node-core-pvf + bump: patch +- name: polkadot-service + bump: patch +- name: polkadot-primitives + bump: patch +- name: staging-xcm-executor + bump: patch +- name: sc-consensus-beefy + bump: patch +- name: sc-consensus-slots + bump: patch +- name: frame-benchmarking-pallet-pov + bump: patch +- name: pallet-contracts + bump: patch +- name: frame-election-provider-support + bump: patch +- name: pallet-revive-mock-network + bump: patch +- name: frame-benchmarking-cli + bump: patch +- name: sc-utils + bump: patch +- name: pallet-beefy-mmr + bump: patch +- name: sp-state-machine + bump: patch +- name: fork-tree + bump: patch +- name: sc-transaction-pool + bump: patch +- name: pallet-delegated-staking + bump: patch +- name: sc-executor-wasmtime + bump: patch +- name: cumulus-pallet-xcmp-queue + bump: patch +- name: xcm-procedural + bump: patch +- name: sp-application-crypto + bump: patch +- name: sp-core + bump: patch +- name: sp-keyring + bump: patch +- name: polkadot-availability-distribution + bump: patch +- name: sp-runtime + bump: patch +- name: sc-authority-discovery + bump: patch +- name: frame-system + bump: patch +- name: sc-network-gossip + bump: patch +- name: pallet-authorship + bump: patch +- name: pallet-election-provider-multi-phase + bump: patch +- name: sp-runtime-interface + bump: patch +- name: pallet-bridge-grandpa + bump: patch +- name: pallet-elections-phragmen + bump: patch +- name: frame-executive + bump: patch +- name: bp-header-chain + bump: patch +- name: polkadot-overseer + bump: patch +- name: polkadot + bump: patch +- name: bridge-hub-westend-runtime + bump: major +- name: bp-messages + bump: patch diff --git a/prdoc/pr_5678.prdoc b/prdoc/pr_5678.prdoc index af1fac31c56..ebb5e5a0d79 100644 --- a/prdoc/pr_5678.prdoc +++ b/prdoc/pr_5678.prdoc @@ -1,6 +1,6 @@ title: 'rpc server: fix deny unsafe on RpcMethods::Auto' doc: -- audience: Node User +- audience: Node Operator description: |- Close #5677 diff --git a/prdoc/pr_5684.prdoc b/prdoc/pr_5684.prdoc index a17bacd2fb9..9800c85de2a 100644 --- a/prdoc/pr_5684.prdoc +++ b/prdoc/pr_5684.prdoc @@ -4,7 +4,7 @@ title: "[pallet-revive]" doc: - - audience: Runtime Devs + - audience: Runtime Dev description: | Update xcm runtime api, and fix pallet-revive xcm tests diff --git a/scripts/update-ui-tests.sh b/scripts/update-ui-tests.sh index a1f380c4712..c25b22fa7f7 100755 --- a/scripts/update-ui-tests.sh +++ b/scripts/update-ui-tests.sh @@ -32,10 +32,12 @@ export RUN_UI_TESTS=1 export SKIP_WASM_BUILD=1 # Let trybuild overwrite the .stderr files export TRYBUILD=overwrite +# Warnings are part of our UI and the CI also sets this. +export RUSTFLAGS="-C debug-assertions -D warnings" # ./substrate -$RUSTUP_RUN cargo test --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui -$RUSTUP_RUN cargo test -p sp-api-test ui -$RUSTUP_RUN cargo test -p frame-election-provider-solution-type ui -$RUSTUP_RUN cargo test -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui -$RUSTUP_RUN cargo test -p xcm-procedural ui \ No newline at end of file +$RUSTUP_RUN cargo test -q --locked --manifest-path substrate/primitives/runtime-interface/Cargo.toml ui +$RUSTUP_RUN cargo test -q --locked -p sp-api-test ui +$RUSTUP_RUN cargo test -q --locked -p frame-election-provider-solution-type ui +$RUSTUP_RUN cargo test -q --locked -p frame-support-test --features=no-metadata-docs,try-runtime,experimental ui +$RUSTUP_RUN cargo test -q --locked -p xcm-procedural ui diff --git a/substrate/bin/node/cli/tests/fees.rs b/substrate/bin/node/cli/tests/fees.rs index 9f82338b4fb..b49af4c1055 100644 --- a/substrate/bin/node/cli/tests/fees.rs +++ b/substrate/bin/node/cli/tests/fees.rs @@ -188,135 +188,3 @@ fn transaction_fee_is_correct() { assert_eq!(Balances::total_balance(&alice()), balance_alice); }); } - -#[test] -#[should_panic] -#[cfg(feature = "stress-test")] -fn block_weight_capacity_report() { - // Just report how many transfer calls you could fit into a block. The number should at least - // be a few hundred (250 at the time of writing but can change over time). Runs until panic. - use node_primitives::Nonce; - - // execution ext. - let mut t = new_test_ext(compact_code_unwrap()); - // setup ext. - let mut tt = new_test_ext(compact_code_unwrap()); - - let factor = 50; - let mut time = 10; - let mut nonce: Nonce = 0; - let mut block_number = 1; - let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); - - loop { - let num_transfers = block_number * factor; - let mut xts = (0..num_transfers) - .map(|i| CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce + i as Nonce, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { - dest: bob().into(), - value: 0, - }), - }) - .collect::>(); - - xts.insert( - 0, - CheckedExtrinsic { - signed: None, - function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { now: time * 1000 }), - }, - ); - - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - xts, - (time * 1000 / SLOT_DURATION).into(), - ); - - let len = block.0.len(); - print!( - "++ Executing block with {} transfers. Block size = {} bytes / {} kb / {} mb", - num_transfers, - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call(&mut t, "Core_execute_block", &block.0).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += num_transfers; - time += 10; - block_number += 1; - } -} - -#[test] -#[should_panic] -#[cfg(feature = "stress-test")] -fn block_length_capacity_report() { - // Just report how big a block can get. Executes until panic. Should be ignored unless if - // manually inspected. The number should at least be a few megabytes (5 at the time of - // writing but can change over time). - use node_primitives::Nonce; - - // execution ext. - let mut t = new_test_ext(compact_code_unwrap()); - // setup ext. - let mut tt = new_test_ext(compact_code_unwrap()); - - let factor = 256 * 1024; - let mut time = 10; - let mut nonce: Nonce = 0; - let mut block_number = 1; - let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); - - loop { - // NOTE: this is super slow. Can probably be improved. - let block = construct_block( - &mut tt, - block_number, - previous_hash, - vec![ - CheckedExtrinsic { - signed: None, - function: RuntimeCall::Timestamp(pallet_timestamp::Call::set { - now: time * 1000, - }), - }, - CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce, 0))), - function: RuntimeCall::System(frame_system::Call::remark { - remark: vec![0u8; (block_number * factor) as usize], - }), - }, - ], - (time * 1000 / SLOT_DURATION).into(), - ); - - let len = block.0.len(); - print!( - "++ Executing block with big remark. Block size = {} bytes / {} kb / {} mb", - len, - len / 1024, - len / 1024 / 1024, - ); - - let r = executor_call(&mut t, "Core_execute_block", &block.0).0; - - println!(" || Result = {:?}", r); - assert!(r.is_ok()); - - previous_hash = block.1; - nonce += 1; - time += 10; - block_number += 1; - } -} diff --git a/substrate/client/authority-discovery/src/worker/tests.rs b/substrate/client/authority-discovery/src/worker/tests.rs index b49615382b8..d71a85db8b8 100644 --- a/substrate/client/authority-discovery/src/worker/tests.rs +++ b/substrate/client/authority-discovery/src/worker/tests.rs @@ -117,10 +117,10 @@ sp_api::mock_impl_runtime_apis! { #[derive(Debug)] pub enum TestNetworkEvent { - GetCalled(KademliaKey), - PutCalled(KademliaKey, Vec), - PutToCalled(Record, HashSet, bool), - StoreRecordCalled(KademliaKey, Vec, Option, Option), + GetCalled, + PutCalled, + PutToCalled, + StoreRecordCalled, } pub struct TestNetwork { @@ -190,17 +190,11 @@ impl NetworkSigner for TestNetwork { impl NetworkDHTProvider for TestNetwork { fn put_value(&self, key: KademliaKey, value: Vec) { self.put_value_call.lock().unwrap().push((key.clone(), value.clone())); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::PutCalled(key, value)) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::PutCalled).unwrap(); } fn get_value(&self, key: &KademliaKey) { self.get_value_call.lock().unwrap().push(key.clone()); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::GetCalled(key.clone())) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::GetCalled).unwrap(); } fn put_record_to( @@ -214,10 +208,7 @@ impl NetworkDHTProvider for TestNetwork { peers.clone(), update_local_storage, )); - self.event_sender - .clone() - .unbounded_send(TestNetworkEvent::PutToCalled(record, peers, update_local_storage)) - .unwrap(); + self.event_sender.clone().unbounded_send(TestNetworkEvent::PutToCalled).unwrap(); } fn store_record( @@ -235,7 +226,7 @@ impl NetworkDHTProvider for TestNetwork { )); self.event_sender .clone() - .unbounded_send(TestNetworkEvent::StoreRecordCalled(key, value, publisher, expires)) + .unbounded_send(TestNetworkEvent::StoreRecordCalled) .unwrap(); } } @@ -536,7 +527,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { pool.run_until(async { // Assert worker to trigger a lookup for the one and only authority. - assert!(matches!(network_events.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(network_events.next().await, Some(TestNetworkEvent::GetCalled))); // Send an event that should generate an error dht_event_tx @@ -1137,7 +1128,7 @@ fn lookup_throttling() { async { // Assert worker to trigger MAX_IN_FLIGHT_LOOKUPS lookups. for _ in 0..MAX_IN_FLIGHT_LOOKUPS { - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); } assert_eq!( metrics.requests_pending.get(), @@ -1168,7 +1159,7 @@ fn lookup_throttling() { } // Assert worker to trigger another lookup. - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); assert_eq!( metrics.requests_pending.get(), (remote_public_keys.len() - MAX_IN_FLIGHT_LOOKUPS - 1) as u64 @@ -1181,7 +1172,7 @@ fn lookup_throttling() { dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); // Assert worker to trigger another lookup. - assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled(_)))); + assert!(matches!(receiver.next().await, Some(TestNetworkEvent::GetCalled))); assert_eq!( metrics.requests_pending.get(), (remote_public_keys.len() - MAX_IN_FLIGHT_LOOKUPS - 2) as u64 diff --git a/substrate/client/cli/src/commands/import_blocks_cmd.rs b/substrate/client/cli/src/commands/import_blocks_cmd.rs index 815c6ab18aa..6bd607901e3 100644 --- a/substrate/client/cli/src/commands/import_blocks_cmd.rs +++ b/substrate/client/cli/src/commands/import_blocks_cmd.rs @@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT; use std::{ fmt::Debug, fs, - io::{self, Read, Seek}, + io::{self, Read}, path::PathBuf, sync::Arc, }; @@ -58,11 +58,6 @@ pub struct ImportBlocksCmd { pub import_params: ImportParams, } -/// Internal trait used to cast to a dynamic type that implements Read and Seek. -trait ReadPlusSeek: Read + Seek {} - -impl ReadPlusSeek for T {} - impl ImportBlocksCmd { /// Run the import-blocks command pub async fn run(&self, client: Arc, import_queue: IQ) -> error::Result<()> diff --git a/substrate/client/cli/src/commands/vanity.rs b/substrate/client/cli/src/commands/vanity.rs index 330a59493ef..9acacb4b15b 100644 --- a/substrate/client/cli/src/commands/vanity.rs +++ b/substrate/client/cli/src/commands/vanity.rs @@ -166,8 +166,6 @@ mod tests { crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec}, sr25519, Pair, }; - #[cfg(feature = "bench")] - use test::Bencher; #[test] fn vanity() { @@ -225,16 +223,4 @@ mod tests { 0 ); } - - #[cfg(feature = "bench")] - #[bench] - fn bench_paranoiac(b: &mut Bencher) { - b.iter(|| generate_key("polk")); - } - - #[cfg(feature = "bench")] - #[bench] - fn bench_not_paranoiac(b: &mut Bencher) { - b.iter(|| generate_key("polk")); - } } diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs index cdd63788811..70671bff8c0 100644 --- a/substrate/client/cli/src/params/node_key_params.rs +++ b/substrate/client/cli/src/params/node_key_params.rs @@ -116,8 +116,8 @@ impl NodeKeyParams { .clone() .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE)); if !self.unsafe_force_node_key_generation && - role.is_authority() && !is_dev && - !key_path.exists() + role.is_authority() && + !is_dev && !key_path.exists() { return Err(Error::NetworkKeyNotFound(key_path)) } diff --git a/substrate/client/consensus/beefy/src/fisherman.rs b/substrate/client/consensus/beefy/src/fisherman.rs index faa4d34eff5..2b2683b35f0 100644 --- a/substrate/client/consensus/beefy/src/fisherman.rs +++ b/substrate/client/consensus/beefy/src/fisherman.rs @@ -32,9 +32,8 @@ use sp_runtime::{ }; use std::{marker::PhantomData, sync::Arc}; -/// Helper struct containing the id and the key ownership proof for a validator. -pub struct ProvedValidator<'a, AuthorityId: AuthorityIdBound> { - pub id: &'a AuthorityId, +/// Helper struct containing the key ownership proof for a validator. +pub struct ProvedValidator { pub key_owner_proof: OpaqueKeyOwnershipProof, } @@ -66,7 +65,7 @@ where at: BlockId, offender_ids: impl Iterator, validator_set_id: ValidatorSetId, - ) -> Result>, Error> { + ) -> Result, Error> { let hash = match at { BlockId::Hash(hash) => hash, BlockId::Number(number) => self @@ -91,7 +90,7 @@ where offender_id.clone(), ) { Ok(Some(key_owner_proof)) => { - proved_offenders.push(ProvedValidator { id: offender_id, key_owner_proof }); + proved_offenders.push(ProvedValidator { key_owner_proof }); }, Ok(None) => { debug!( diff --git a/substrate/client/consensus/grandpa/src/aux_schema.rs b/substrate/client/consensus/grandpa/src/aux_schema.rs index 8ec882591be..c42310dcd72 100644 --- a/substrate/client/consensus/grandpa/src/aux_schema.rs +++ b/substrate/client/consensus/grandpa/src/aux_schema.rs @@ -743,7 +743,9 @@ mod test { substrate_test_runtime_client::runtime::Block, _, _, - >(&client, H256::random(), 0, || unreachable!()) + >( + &client, H256::random(), 0, || unreachable!() + ) .unwrap(); assert_eq!( diff --git a/substrate/client/consensus/grandpa/src/voting_rule.rs b/substrate/client/consensus/grandpa/src/voting_rule.rs index c1d3cd2fbd6..6072f1895fd 100644 --- a/substrate/client/consensus/grandpa/src/voting_rule.rs +++ b/substrate/client/consensus/grandpa/src/voting_rule.rs @@ -82,7 +82,7 @@ where /// /// In the best case our vote is exactly N blocks /// behind the best block, but if there is a scenario where either -/// >34% of validators run without this rule or the fork-choice rule +/// \>34% of validators run without this rule or the fork-choice rule /// can prioritize shorter chains over longer ones, the vote may be /// closer to the best block than N. #[derive(Clone)] diff --git a/substrate/client/consensus/slots/build.rs b/substrate/client/consensus/slots/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/client/consensus/slots/build.rs +++ b/substrate/client/consensus/slots/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/client/consensus/slots/src/lib.rs b/substrate/client/consensus/slots/src/lib.rs index 06e0756fc96..4f7e8554177 100644 --- a/substrate/client/consensus/slots/src/lib.rs +++ b/substrate/client/consensus/slots/src/lib.rs @@ -227,7 +227,7 @@ pub trait SimpleSlotWorker { "⌛️ Discarding proposal for slot {}; block production took too long", slot, ); // If the node was compiled with debug, tell the user to use release optimizations. - #[cfg(build_type = "debug")] + #[cfg(build_profile = "debug")] info!( target: log_target, "👉 Recompile your node in `--release` mode to mitigate this problem.", diff --git a/substrate/client/executor/wasmtime/build.rs b/substrate/client/executor/wasmtime/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/client/executor/wasmtime/build.rs +++ b/substrate/client/executor/wasmtime/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/client/executor/wasmtime/src/tests.rs b/substrate/client/executor/wasmtime/src/tests.rs index f86a4275769..abf2b9509c2 100644 --- a/substrate/client/executor/wasmtime/src/tests.rs +++ b/substrate/client/executor/wasmtime/src/tests.rs @@ -455,7 +455,7 @@ fn test_max_memory_pages( // This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) // so it's ignored by default unless it was compiled with `--release`. -#[cfg_attr(build_type = "debug", ignore)] +#[cfg_attr(build_profile = "debug", ignore)] #[test] fn test_instances_without_reuse_are_not_leaked() { let runtime = crate::create_runtime::( diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index 414da9b2a58..a4bd922a76d 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -377,9 +377,6 @@ mod tests { #[derive(Clone, Default)] struct TestNetwork {} - #[derive(Clone, Default)] - struct TestNetworkInner {} - #[async_trait::async_trait] impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index 9a6324dafd3..5ecbec52d50 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -76,8 +76,6 @@ pub enum BehaviourOut { /// /// This event is generated for statistics purposes. InboundRequest { - /// Peer which sent us a request. - peer: PeerId, /// Protocol name of the request. protocol: ProtocolName, /// If `Ok`, contains the time elapsed between when we received the request and when we @@ -89,8 +87,6 @@ pub enum BehaviourOut { /// /// This event is generated for statistics purposes. RequestFinished { - /// Peer that we send a request to. - peer: PeerId, /// Name of the protocol in question. protocol: ProtocolName, /// Duration the request took. @@ -350,10 +346,10 @@ impl From for BehaviourOut { impl From for BehaviourOut { fn from(event: request_responses::Event) -> Self { match event { - request_responses::Event::InboundRequest { peer, protocol, result } => - BehaviourOut::InboundRequest { peer, protocol, result }, - request_responses::Event::RequestFinished { peer, protocol, duration, result } => - BehaviourOut::RequestFinished { peer, protocol, duration, result }, + request_responses::Event::InboundRequest { protocol, result, .. } => + BehaviourOut::InboundRequest { protocol, result }, + request_responses::Event::RequestFinished { protocol, duration, result, .. } => + BehaviourOut::RequestFinished { protocol, duration, result }, request_responses::Event::ReputationChanges { peer, changes } => BehaviourOut::ReputationChanges { peer, changes }, } diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index bf2005df34d..5fe944cadc0 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -95,15 +95,6 @@ pub enum DiscoveryEvent { /// Peer ID. peer: PeerId, - /// Identify protocol version. - protocol_version: Option, - - /// Identify user agent version. - user_agent: Option, - - /// Observed address. - observed_address: Multiaddr, - /// Listen addresses. listen_addresses: Vec, @@ -561,11 +552,10 @@ impl Stream for Discovery { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some(IdentifyEvent::PeerIdentified { peer, - protocol_version, - user_agent, listen_addresses, supported_protocols, observed_address, + .. })) => { if this.is_new_external_address(&observed_address, peer) { this.pending_events.push_back(DiscoveryEvent::ExternalAddressDiscovered { @@ -575,10 +565,7 @@ impl Stream for Discovery { return Poll::Ready(Some(DiscoveryEvent::Identified { peer, - protocol_version, - user_agent, listen_addresses, - observed_address, supported_protocols, })); }, diff --git a/substrate/client/network/src/protocol/notifications/behaviour.rs b/substrate/client/network/src/protocol/notifications/behaviour.rs index cb4f089995e..a562546145c 100644 --- a/substrate/client/network/src/protocol/notifications/behaviour.rs +++ b/substrate/client/network/src/protocol/notifications/behaviour.rs @@ -33,7 +33,7 @@ use bytes::BytesMut; use fnv::FnvHashMap; use futures::{future::BoxFuture, prelude::*, stream::FuturesUnordered}; use libp2p::{ - core::{ConnectedPoint, Endpoint, Multiaddr}, + core::{Endpoint, Multiaddr}, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, PollParameters, @@ -362,8 +362,6 @@ pub enum NotificationsOut { received_handshake: Vec, /// Object that permits sending notifications to the peer. notifications_sink: NotificationsSink, - /// Is the connection inbound. - inbound: bool, }, /// The [`NotificationsSink`] object used to send notifications with the given peer must be @@ -1223,33 +1221,20 @@ impl NetworkBehaviour for Notifications { &mut self, _connection_id: ConnectionId, peer: PeerId, - local_addr: &Multiaddr, - remote_addr: &Multiaddr, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - Ok(NotifsHandler::new( - peer, - ConnectedPoint::Listener { - local_addr: local_addr.clone(), - send_back_addr: remote_addr.clone(), - }, - self.notif_protocols.clone(), - Some(self.metrics.clone()), - )) + Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } fn handle_established_outbound_connection( &mut self, _connection_id: ConnectionId, peer: PeerId, - addr: &Multiaddr, - role_override: Endpoint, + _addr: &Multiaddr, + _role_override: Endpoint, ) -> Result, ConnectionDenied> { - Ok(NotifsHandler::new( - peer, - ConnectedPoint::Dialer { address: addr.clone(), role_override }, - self.notif_protocols.clone(), - Some(self.metrics.clone()), - )) + Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -2061,7 +2046,6 @@ impl NetworkBehaviour for Notifications { let event = NotificationsOut::CustomProtocolOpen { peer_id, set_id, - inbound, direction: if inbound { Direction::Inbound } else { @@ -2383,6 +2367,7 @@ mod tests { protocol::notifications::handler::tests::*, protocol_controller::{IncomingIndex, ProtoSetConfig, ProtocolController}, }; + use libp2p::core::ConnectedPoint; use sc_utils::mpsc::tracing_unbounded; use std::{collections::HashSet, iter}; @@ -2413,7 +2398,8 @@ mod tests { } fn development_notifs( - ) -> (Notifications, ProtocolController, Box) { + ) -> (Notifications, ProtocolController, Box) + { let (protocol_handle_pair, notif_service) = crate::protocol::notifications::service::notification_service("/proto/1".into()); let (to_notifications, from_controller) = @@ -2668,7 +2654,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -2868,7 +2854,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3021,7 +3007,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3065,7 +3051,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3135,7 +3121,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3188,7 +3174,7 @@ mod tests { assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); // open new substream - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -3261,7 +3247,7 @@ mod tests { notif.on_connection_handler_event( peer, *conn, - conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); } @@ -3283,7 +3269,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3354,7 +3340,7 @@ mod tests { notif.on_connection_handler_event( peer, conn, - conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { @@ -3409,7 +3395,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3483,7 +3469,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3546,7 +3532,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected.clone(), vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3560,7 +3546,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3614,7 +3600,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3672,7 +3658,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3733,7 +3719,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3786,7 +3772,7 @@ mod tests { notif.on_connection_handler_event( peer, conn1, - conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]), ); if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { @@ -3802,7 +3788,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3843,7 +3829,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -3966,7 +3952,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4015,10 +4001,6 @@ mod tests { let peer = PeerId::random(); let conn = ConnectionId::new_unchecked(0); let set_id = SetId::from(0); - let connected = ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }; let mut conn_yielder = ConnectionYielder::new(); // move the peer to `Enabled` state @@ -4052,7 +4034,7 @@ mod tests { notif.protocol_report_accept(IncomingIndex(0)); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4167,7 +4149,7 @@ mod tests { notif.peerset_report_connect(peer, set_id); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4280,7 +4262,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4521,7 +4503,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4623,7 +4605,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4681,7 +4663,7 @@ mod tests { notif.peerset_report_connect(peer, set_id); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); - let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + let event = conn_yielder.open_substream(peer, 0, vec![1, 2, 3, 4]); notif.on_connection_handler_event(peer, conn, event); assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); @@ -4705,7 +4687,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &endpoint.clone(), - handler: NotifsHandler::new(peer, endpoint, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4822,7 +4804,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4857,7 +4839,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4908,7 +4890,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -4955,7 +4937,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5005,7 +4987,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5048,7 +5030,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected.clone(), vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5059,7 +5041,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, connected, vec![], None), + handler: NotifsHandler::new(peer, vec![], None), remaining_established: 0usize, }, )); @@ -5071,16 +5053,12 @@ mod tests { fn open_result_ok_non_existent_peer() { let (mut notif, _controller, _notif_service) = development_notifs(); let conn = ConnectionId::new_unchecked(0); - let connected = ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }; let mut conn_yielder = ConnectionYielder::new(); notif.on_connection_handler_event( PeerId::random(), conn, - conn_yielder.open_substream(PeerId::random(), 0, connected, vec![1, 2, 3, 4]), + conn_yielder.open_substream(PeerId::random(), 0, vec![1, 2, 3, 4]), ); } } diff --git a/substrate/client/network/src/protocol/notifications/handler.rs b/substrate/client/network/src/protocol/notifications/handler.rs index 967ef614c55..bff60ba1125 100644 --- a/substrate/client/network/src/protocol/notifications/handler.rs +++ b/substrate/client/network/src/protocol/notifications/handler.rs @@ -73,7 +73,6 @@ use futures::{ prelude::*, }; use libp2p::{ - core::ConnectedPoint, swarm::{ handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, Stream, SubstreamProtocol, @@ -117,9 +116,6 @@ pub struct NotifsHandler { /// When the connection with the remote has been successfully established. when_connection_open: Instant, - /// Whether we are the connection dialer or listener. - endpoint: ConnectedPoint, - /// Remote we are connected to. peer_id: PeerId, @@ -136,7 +132,6 @@ impl NotifsHandler { /// Creates new [`NotifsHandler`]. pub fn new( peer_id: PeerId, - endpoint: ConnectedPoint, protocols: Vec, metrics: Option, ) -> Self { @@ -154,7 +149,6 @@ impl NotifsHandler { }) .collect(), peer_id, - endpoint, when_connection_open: Instant::now(), events_queue: VecDeque::with_capacity(16), metrics: metrics.map_or(None, |metrics| Some(Arc::new(metrics))), @@ -281,8 +275,6 @@ pub enum NotifsHandlerOut { protocol_index: usize, /// Name of the protocol that was actually negotiated, if the default one wasn't available. negotiated_fallback: Option, - /// The endpoint of the connection that is open for custom protocols. - endpoint: ConnectedPoint, /// Handshake that was sent to us. /// This is normally a "Status" message, but this out of the concern of this code. received_handshake: Vec, @@ -590,7 +582,6 @@ impl ConnectionHandler for NotifsHandler { NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: new_open.negotiated_fallback, - endpoint: self.endpoint.clone(), received_handshake: new_open.handshake, notifications_sink, inbound, @@ -889,7 +880,6 @@ pub mod tests { use libp2p::{ core::muxing::SubstreamBox, swarm::handler::{self, StreamUpgradeError}, - Multiaddr, Stream, }; use multistream_select::{dialer_select_proto, listener_select_proto, Negotiated, Version}; use std::{ @@ -925,7 +915,6 @@ pub mod tests { &mut self, peer: PeerId, protocol_index: usize, - endpoint: ConnectedPoint, received_handshake: Vec, ) -> NotifsHandlerOut { let (async_tx, async_rx) = @@ -954,7 +943,6 @@ pub mod tests { NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: None, - endpoint, received_handshake, notifications_sink, inbound: false, @@ -1110,10 +1098,6 @@ pub mod tests { NotifsHandler { protocols: vec![proto], when_connection_open: Instant::now(), - endpoint: ConnectedPoint::Listener { - local_addr: Multiaddr::empty(), - send_back_addr: Multiaddr::empty(), - }, peer_id: PeerId::random(), events_queue: VecDeque::new(), metrics: None, @@ -1131,7 +1115,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1158,7 +1141,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1191,7 +1173,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1225,7 +1206,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1265,7 +1245,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1316,7 +1295,6 @@ pub mod tests { codec.set_max_len(usize::MAX); let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1355,7 +1333,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1415,7 +1392,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1452,7 +1428,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1498,7 +1473,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1547,7 +1521,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1583,7 +1556,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::NotSent, @@ -1658,7 +1630,6 @@ pub mod tests { let notif_in = NotificationsInOpen { handshake: b"hello, world".to_vec(), - negotiated_fallback: None, substream: NotificationsInSubstream::new( Framed::new(io, codec), NotificationsInSubstreamHandshake::PendingSend(vec![1, 2, 3, 4]), diff --git a/substrate/client/network/src/protocol/notifications/service/mod.rs b/substrate/client/network/src/protocol/notifications/service/mod.rs index 4f6d32ae3b3..a7eb31fc579 100644 --- a/substrate/client/network/src/protocol/notifications/service/mod.rs +++ b/substrate/client/network/src/protocol/notifications/service/mod.rs @@ -89,9 +89,8 @@ impl MessageSink for NotificationSink { .await .map_err(|_| error::Error::ConnectionClosed)?; - permit.send(notification).map_err(|_| error::Error::ChannelClosed).map(|res| { + permit.send(notification).map_err(|_| error::Error::ChannelClosed).inspect(|_| { metrics::register_notification_sent(sink.0.metrics(), &sink.1, notification_len); - res }) } } @@ -263,13 +262,12 @@ impl NotificationService for NotificationHandle { .map_err(|_| error::Error::ConnectionClosed)? .send(notification) .map_err(|_| error::Error::ChannelClosed) - .map(|res| { + .inspect(|_| { metrics::register_notification_sent( sink.metrics(), &self.protocol, notification_len, ); - res }) } diff --git a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs index a8a9e453a7b..e01bcbe0bad 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -151,7 +151,7 @@ where type Future = Pin> + Send>>; type Error = NotificationsHandshakeError; - fn upgrade_inbound(self, mut socket: TSubstream, negotiated_name: Self::Info) -> Self::Future { + fn upgrade_inbound(self, mut socket: TSubstream, _negotiated_name: Self::Info) -> Self::Future { Box::pin(async move { let handshake_len = unsigned_varint::aio::read_usize(&mut socket).await?; if handshake_len > MAX_HANDSHAKE_SIZE { @@ -174,15 +174,7 @@ where handshake: NotificationsInSubstreamHandshake::NotSent, }; - Ok(NotificationsInOpen { - handshake, - negotiated_fallback: if negotiated_name == self.protocol_names[0] { - None - } else { - Some(negotiated_name) - }, - substream, - }) + Ok(NotificationsInOpen { handshake, substream }) }) } } @@ -191,9 +183,6 @@ where pub struct NotificationsInOpen { /// Handshake sent by the remote. pub handshake: Vec, - /// If the negotiated name is not the "main" protocol name but a fallback, contains the - /// name of the negotiated fallback. - pub negotiated_fallback: Option, /// Implementation of `Stream` that allows receives messages from the substream. pub substream: NotificationsInSubstream, } diff --git a/substrate/client/network/src/service/metrics.rs b/substrate/client/network/src/service/metrics.rs index 202dc7b2ed6..e48c53953ff 100644 --- a/substrate/client/network/src/service/metrics.rs +++ b/substrate/client/network/src/service/metrics.rs @@ -72,7 +72,6 @@ pub struct Metrics { pub distinct_peers_connections_opened_total: Counter, pub incoming_connections_errors_total: CounterVec, pub incoming_connections_total: Counter, - pub issued_light_requests: Counter, pub kademlia_query_duration: HistogramVec, pub kademlia_random_queries_total: Counter, pub kademlia_records_count: Gauge, @@ -126,10 +125,6 @@ impl Metrics { "substrate_sub_libp2p_incoming_connections_total", "Total number of incoming connections on the listening sockets" )?, registry)?, - issued_light_requests: prometheus::register(Counter::new( - "substrate_issued_light_requests", - "Number of light client requests that our node has issued.", - )?, registry)?, kademlia_query_duration: prometheus::register(HistogramVec::new( HistogramOpts { common_opts: Opts::new( diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 96c1750b311..dceea9954c6 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -812,7 +812,8 @@ where } if !self.default_peers_set_no_slot_connected_peers.remove(&peer_id) && - info.inbound && info.info.roles.is_full() + info.inbound && + info.info.roles.is_full() { match self.num_in_peers.checked_sub(1) { Some(value) => { diff --git a/substrate/client/network/sync/src/lib.rs b/substrate/client/network/sync/src/lib.rs index ca7280edba5..c458c7a5da4 100644 --- a/substrate/client/network/sync/src/lib.rs +++ b/substrate/client/network/sync/src/lib.rs @@ -26,7 +26,6 @@ mod block_announce_validator; mod futures_stream; mod justification_requests; mod pending_responses; -mod request_metrics; mod schema; pub mod types; diff --git a/substrate/client/network/sync/src/request_metrics.rs b/substrate/client/network/sync/src/request_metrics.rs deleted file mode 100644 index 455f57ec393..00000000000 --- a/substrate/client/network/sync/src/request_metrics.rs +++ /dev/null @@ -1,25 +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 . - -#[derive(Debug)] -pub struct Metrics { - pub pending_requests: u32, - pub active_requests: u32, - pub importing_requests: u32, - pub failed_requests: u32, -} diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 0f73e3194ba..06e243342fb 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -628,9 +628,8 @@ struct VerifierAdapter { impl Verifier for VerifierAdapter { async fn verify(&self, block: BlockImportParams) -> Result, String> { let hash = block.header.hash(); - self.verifier.lock().await.verify(block).await.map_err(|e| { + self.verifier.lock().await.verify(block).await.inspect_err(|e| { self.failed_verifications.lock().insert(hash, e.clone()); - e }) } } diff --git a/substrate/client/network/test/src/sync.rs b/substrate/client/network/test/src/sync.rs index 4244c49bf7f..91307d86928 100644 --- a/substrate/client/network/test/src/sync.rs +++ b/substrate/client/network/test/src/sync.rs @@ -749,24 +749,6 @@ async fn sync_blocks_when_block_announce_validator_says_it_is_new_best() { } } -/// Waits for some time until the validation is successful. -struct DeferredBlockAnnounceValidator; - -impl BlockAnnounceValidator for DeferredBlockAnnounceValidator { - fn validate( - &mut self, - _: &Header, - _: &[u8], - ) -> Pin>> + Send>> - { - async { - futures_timer::Delay::new(std::time::Duration::from_millis(500)).await; - Ok(Validation::Success { is_new_best: false }) - } - .boxed() - } -} - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn wait_until_deferred_block_announce_validation_is_ready() { sp_tracing::try_init_simple(); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs b/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs index 073ee34a79f..fa10fde388f 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/test_utils.rs @@ -343,7 +343,8 @@ where fn number( &self, hash: Block::Hash, - ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> + { self.client.number(hash) } diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index 13e63962fe8..55bbfcdd859 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -1748,11 +1748,9 @@ fn respects_block_rules() { } #[test] -#[cfg(disable_flaky)] -#[allow(dead_code)] -// FIXME: https://github.com/paritytech/substrate/issues/11321 +// FIXME: https://github.com/paritytech/polkadot-sdk/issues/48 fn returns_status_for_pruned_blocks() { - use sc_consensus::BlockStatus; + use sp_consensus::BlockStatus; sp_tracing::try_init_simple(); let tmp = tempfile::tempdir().unwrap(); diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 47ad22603e4..44c2c738ab1 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -119,10 +119,9 @@ where let new_bytes = val.size(); self.bytes.fetch_add(new_bytes as isize, AtomicOrdering::Relaxed); self.length.fetch_add(1, AtomicOrdering::Relaxed); - self.inner_guard.insert(key, val).map(|old_val| { + self.inner_guard.insert(key, val).inspect(|old_val| { self.bytes.fetch_sub(old_val.size() as isize, AtomicOrdering::Relaxed); self.length.fetch_sub(1, AtomicOrdering::Relaxed); - old_val }) } diff --git a/substrate/client/utils/src/mpsc.rs b/substrate/client/utils/src/mpsc.rs index 91db7e1e7b0..051cb5b387c 100644 --- a/substrate/client/utils/src/mpsc.rs +++ b/substrate/client/utils/src/mpsc.rs @@ -103,7 +103,7 @@ impl TracingUnboundedSender { /// Proxy function to `async_channel::Sender::try_send`. pub fn unbounded_send(&self, msg: T) -> Result<(), TrySendError> { - self.inner.try_send(msg).map(|s| { + self.inner.try_send(msg).inspect(|_| { UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, SENT_LABEL]).inc(); UNBOUNDED_CHANNELS_SIZE .with_label_values(&[self.name]) @@ -124,8 +124,6 @@ impl TracingUnboundedSender { Backtrace::force_capture(), ); } - - s }) } @@ -144,12 +142,11 @@ impl TracingUnboundedReceiver { /// Proxy function to [`async_channel::Receiver`] /// that discounts the messages taken out. pub fn try_recv(&mut self) -> Result { - self.inner.try_recv().map(|s| { + self.inner.try_recv().inspect(|_| { UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, RECEIVED_LABEL]).inc(); UNBOUNDED_CHANNELS_SIZE .with_label_values(&[self.name]) .set(self.inner.len().saturated_into()); - s }) } diff --git a/substrate/frame/authorship/src/lib.rs b/substrate/frame/authorship/src/lib.rs index a0cca806e78..1de2262a201 100644 --- a/substrate/frame/authorship/src/lib.rs +++ b/substrate/frame/authorship/src/lib.rs @@ -84,9 +84,8 @@ impl Pallet { let digest = >::digest(); let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - T::FindAuthor::find_author(pre_runtime_digests).map(|a| { + T::FindAuthor::find_author(pre_runtime_digests).inspect(|a| { >::put(&a); - a }) } } diff --git a/substrate/frame/bags-list/src/lib.rs b/substrate/frame/bags-list/src/lib.rs index f6af1da5e7b..ee36a3a3ebd 100644 --- a/substrate/frame/bags-list/src/lib.rs +++ b/substrate/frame/bags-list/src/lib.rs @@ -491,7 +491,7 @@ impl, I: 'static> ScoreProvider for Pallet { Node::::get(id).map(|node| node.score()).unwrap_or_default() } - frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { + frame_election_provider_support::runtime_benchmarks_or_std_enabled! { fn set_score_of(id: &T::AccountId, new_score: T::Score) { ListNodes::::mutate(id, |maybe_node| { if let Some(node) = maybe_node.as_mut() { diff --git a/substrate/frame/bags-list/src/list/tests.rs b/substrate/frame/bags-list/src/list/tests.rs index e5fff76d75c..fc4c4fbd088 100644 --- a/substrate/frame/bags-list/src/list/tests.rs +++ b/substrate/frame/bags-list/src/list/tests.rs @@ -778,7 +778,7 @@ mod bags { assert_eq!(bag_1000.iter().count(), 3); bag_1000.insert_node_unchecked(node(4, None, None, bag_1000.bag_upper)); // panics in debug assert_eq!(bag_1000.iter().count(), 3); // in release we expect it to silently ignore the - // request. + // request. }); } diff --git a/substrate/frame/bags-list/src/mock.rs b/substrate/frame/bags-list/src/mock.rs index ea677cb9e73..3690a876f62 100644 --- a/substrate/frame/bags-list/src/mock.rs +++ b/substrate/frame/bags-list/src/mock.rs @@ -41,7 +41,7 @@ impl frame_election_provider_support::ScoreProvider for StakingMock { *NextVoteWeightMap::get().get(id).unwrap_or(&NextVoteWeight::get()) } - frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { + frame_election_provider_support::runtime_benchmarks_or_std_enabled! { fn set_score_of(id: &AccountId, weight: Self::Score) { NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); } diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 87d2029d488..f6858347f24 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -1031,7 +1031,7 @@ pub mod pallet { } if did_provide && !does_provide { // This could reap the account so must go last. - frame_system::Pallet::::dec_providers(who).map_err(|r| { + frame_system::Pallet::::dec_providers(who).inspect_err(|_| { // best-effort revert consumer change. if did_consume && !does_consume { let _ = frame_system::Pallet::::inc_consumers(who).defensive(); @@ -1039,7 +1039,6 @@ pub mod pallet { if !did_consume && does_consume { let _ = frame_system::Pallet::::dec_consumers(who); } - r })?; } diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 2243859458b..a4984b34f6d 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -1017,7 +1017,7 @@ fn slash_consumed_slash_full_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); // Account is still alive assert!(System::account_exists(&1)); @@ -1029,7 +1029,7 @@ fn slash_consumed_slash_over_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(900), 100)); // Account is still alive assert!(System::account_exists(&1)); @@ -1041,7 +1041,7 @@ fn slash_consumed_slash_partial_works() { ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { Balances::make_free_balance_be(&1, 1_000); assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full + // Slashed completed in full assert_eq!(Balances::slash(&1, 800), (NegativeImbalance::new(800), 0)); // Account is still alive assert!(System::account_exists(&1)); diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs index 135f95eabb9..fea6a2078f0 100644 --- a/substrate/frame/beefy-mmr/src/benchmarking.rs +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -51,9 +51,7 @@ mod benchmarks { #[benchmark] fn extract_validation_context() { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); init_block::(1); let header = System::::finalize(); @@ -71,9 +69,7 @@ mod benchmarks { #[benchmark] fn read_peak() { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); init_block::(1); @@ -91,9 +87,7 @@ mod benchmarks { /// the verification. We need to account for the peaks separately. #[benchmark] fn n_items_proof_is_non_canonical(n: Linear<2, 512>) { - if !cfg!(feature = "test") { - pallet_mmr::UseLocalStorage::::set(true); - } + pallet_mmr::UseLocalStorage::::set(true); for block_num in 1..=n { init_block::(block_num); diff --git a/substrate/frame/benchmarking/pov/src/weights.rs b/substrate/frame/benchmarking/pov/src/weights.rs index c4fc03d1dd9..1f20d5f0b51 100644 --- a/substrate/frame/benchmarking/pov/src/weights.rs +++ b/substrate/frame/benchmarking/pov/src/weights.rs @@ -45,6 +45,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; /// Weight functions needed for `frame_benchmarking_pallet_pov`. +#[allow(dead_code)] pub trait WeightInfo { fn storage_single_value_read() -> Weight; fn storage_single_value_ignored_read() -> Weight; @@ -79,6 +80,7 @@ pub trait WeightInfo { } /// Weights for `frame_benchmarking_pallet_pov` using the Substrate node and recommended hardware. +#[allow(dead_code)] pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: `Pov::Value` (r:1 w:0) diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index 7b89a6e3e76..e30d6fa2d14 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -459,12 +459,12 @@ pub mod pallet { Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, - curator_deposit: &mut BalanceOf| { - let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; - T::OnSlash::on_unbalanced(imbalance); - *curator_deposit = Zero::zero(); - }; + let slash_curator = + |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; + T::OnSlash::on_unbalanced(imbalance); + *curator_deposit = Zero::zero(); + }; match bounty.status { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => { diff --git a/substrate/frame/child-bounties/src/lib.rs b/substrate/frame/child-bounties/src/lib.rs index 911fd4c4c49..660a30ca5d2 100644 --- a/substrate/frame/child-bounties/src/lib.rs +++ b/substrate/frame/child-bounties/src/lib.rs @@ -473,12 +473,13 @@ pub mod pallet { let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, - curator_deposit: &mut BalanceOf| { - let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; - T::OnSlash::on_unbalanced(imbalance); - *curator_deposit = Zero::zero(); - }; + let slash_curator = + |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let imbalance = + T::Currency::slash_reserved(curator, *curator_deposit).0; + T::OnSlash::on_unbalanced(imbalance); + *curator_deposit = Zero::zero(); + }; match child_bounty.status { ChildBountyStatus::Added => { diff --git a/substrate/frame/contracts/src/benchmarking/code.rs b/substrate/frame/contracts/src/benchmarking/code.rs index 1473022b553..b5918a5e182 100644 --- a/substrate/frame/contracts/src/benchmarking/code.rs +++ b/substrate/frame/contracts/src/benchmarking/code.rs @@ -114,7 +114,6 @@ pub struct ImportedFunction { pub struct WasmModule { pub code: Vec, pub hash: ::Output, - pub memory: Option, } impl From for WasmModule { @@ -233,7 +232,7 @@ impl From for WasmModule { let code = contract.build().into_bytes().unwrap(); let hash = T::Hashing::hash(&code); - Self { code: code.into(), hash, memory: def.memory } + Self { code: code.into(), hash } } } diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 31e0bf50b73..046affe32d9 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -454,9 +454,6 @@ pub trait Executable: Sized { /// The code hash of the executable. fn code_hash(&self) -> &CodeHash; - /// Size of the contract code in bytes. - fn code_len(&self) -> u32; - /// The code does not contain any instructions which could lead to indeterminism. fn is_deterministic(&self) -> bool; } @@ -1838,10 +1835,6 @@ mod tests { &self.code_info } - fn code_len(&self) -> u32 { - 0 - } - fn is_deterministic(&self) -> bool { true } diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index f4ee76459c4..c9786fa1516 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -488,10 +488,6 @@ impl Executable for WasmBlob { &self.code_info } - fn code_len(&self) -> u32 { - self.code.len() as u32 - } - fn is_deterministic(&self) -> bool { matches!(self.code_info.determinism, Determinism::Enforced) } diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 7b8d14b0a61..1d181eb29ca 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -71,8 +71,8 @@ //! - Migrate a `Nominator` account to an `agent` account. See [`Pallet::migrate_to_agent`]. //! Explained in more detail in the `Migration` section. //! - Migrate unclaimed delegated funds from `agent` to delegator. When a nominator migrates to an -//! agent, the funds are held in a proxy account. This function allows the delegator to claim their -//! share of the funds from the proxy account. See [`Pallet::migrate_delegation`]. +//! agent, the funds are held in a proxy account. This function allows the delegator to claim +//! their share of the funds from the proxy account. See [`Pallet::migrate_delegation`]. //! //! ## Lazy Slashing //! One of the reasons why direct nominators on staking pallet cannot scale well is because all diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 09248e77848..072cfe176b6 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -1208,9 +1208,8 @@ pub mod pallet { } let _ = Self::unsigned_pre_dispatch_checks(raw_solution) - .map_err(|err| { + .inspect_err(|err| { log!(debug, "unsigned transaction validation failed due to {:?}", err); - err }) .map_err(dispatch_error_to_invalid)?; diff --git a/substrate/frame/election-provider-support/src/lib.rs b/substrate/frame/election-provider-support/src/lib.rs index 394f58a3844..cb3249e388a 100644 --- a/substrate/frame/election-provider-support/src/lib.rs +++ b/substrate/frame/election-provider-support/src/lib.rs @@ -687,7 +687,7 @@ sp_core::generate_feature_enabled_macro!( ); sp_core::generate_feature_enabled_macro!( - runtime_benchmarks_fuzz_or_std_enabled, - any(feature = "runtime-benchmarks", feature = "fuzzing", feature = "std"), + runtime_benchmarks_or_std_enabled, + any(feature = "runtime-benchmarks", feature = "std"), $ ); diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index 6d91448fd18..a1c5f69e1b6 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -829,7 +829,7 @@ impl Pallet { T::Currency::unreserve(who, removed.deposit); } - let maybe_next_best = RunnersUp::::mutate(|r| r.pop()).map(|next_best| { + let maybe_next_best = RunnersUp::::mutate(|r| r.pop()).inspect(|next_best| { // defensive-only: Members and runners-up are disjoint. This will always be err and // give us an index to insert. if let Err(index) = members.binary_search_by(|m| m.who.cmp(&next_best.who)) { @@ -839,7 +839,6 @@ impl Pallet { // is already a member, so not much more to do. log::error!(target: LOG_TARGET, "A member seems to also be a runner-up."); } - next_best }); Ok(maybe_next_best) })?; diff --git a/substrate/frame/examples/offchain-worker/src/tests.rs b/substrate/frame/examples/offchain-worker/src/tests.rs index b665cbbb62a..741adbe6d26 100644 --- a/substrate/frame/examples/offchain-worker/src/tests.rs +++ b/substrate/frame/examples/offchain-worker/src/tests.rs @@ -266,11 +266,12 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() { { assert_eq!(body, price_payload); - let signature_valid = - ::Public, - frame_system::pallet_prelude::BlockNumberFor, - > as SignedPayload>::verify::(&price_payload, signature); + let signature_valid = ::Public, + frame_system::pallet_prelude::BlockNumberFor, + > as SignedPayload>::verify::( + &price_payload, signature + ); assert!(signature_valid); } @@ -320,11 +321,12 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() { { assert_eq!(body, price_payload); - let signature_valid = - ::Public, - frame_system::pallet_prelude::BlockNumberFor, - > as SignedPayload>::verify::(&price_payload, signature); + let signature_valid = ::Public, + frame_system::pallet_prelude::BlockNumberFor, + > as SignedPayload>::verify::( + &price_payload, signature + ); assert!(signature_valid); } diff --git a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs index 55cf7cef9a7..922c03afdd1 100644 --- a/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs +++ b/substrate/frame/examples/single-block-migrations/src/migrations/v1.rs @@ -60,7 +60,7 @@ impl UncheckedOnRuntimeUpgrade for InnerMigrateV0ToV1 { /// /// - If the value doesn't exist, there is nothing to do. /// - If the value exists, it is read and then written back to storage inside a - /// [`crate::CurrentAndPreviousValue`]. + /// [`crate::CurrentAndPreviousValue`]. fn on_runtime_upgrade() -> frame_support::weights::Weight { // Read the old value from storage if let Some(old_value) = v0::Value::::take() { diff --git a/substrate/frame/executive/src/lib.rs b/substrate/frame/executive/src/lib.rs index 1e7bac64e18..fe702e1fc39 100644 --- a/substrate/frame/executive/src/lib.rs +++ b/substrate/frame/executive/src/lib.rs @@ -382,9 +382,8 @@ where , >>::try_state(*header.number(), select.clone()) - .map_err(|e| { + .inspect_err(|e| { log::error!(target: LOG_TARGET, "failure: {:?}", e); - e })?; if select.any() { let res = AllPalletsWithSystem::try_decode_entire_state(); diff --git a/substrate/frame/nis/src/lib.rs b/substrate/frame/nis/src/lib.rs index 016daa4cb78..87e2276e768 100644 --- a/substrate/frame/nis/src/lib.rs +++ b/substrate/frame/nis/src/lib.rs @@ -756,15 +756,13 @@ pub mod pallet { .map(|_| ()) // We ignore this error as it just means the amount we're trying to deposit is // dust and the beneficiary account doesn't exist. - .or_else( - |e| { - if e == TokenError::CannotCreate.into() { - Ok(()) - } else { - Err(e) - } - }, - )?; + .or_else(|e| { + if e == TokenError::CannotCreate.into() { + Ok(()) + } else { + Err(e) + } + })?; summary.receipts_on_hold.saturating_reduce(on_hold); } T::Currency::release(&HoldReason::NftReceipt.into(), &who, amount, Exact)?; diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index bf0fa4e1a12..c96a50af865 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -24,7 +24,6 @@ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, - SortedMembers, }, weights::Weight, }; @@ -98,14 +97,6 @@ ord_parameter_types! { pub const Five: u64 = 5; pub const Six: u64 = 6; } -pub struct OneToFive; -impl SortedMembers for OneToFive { - fn sorted_members() -> Vec { - vec![1, 2, 3, 4, 5] - } - #[cfg(feature = "runtime-benchmarks")] - fn add(_m: &u64) {} -} pub struct TestTracksInfo; impl TracksInfo for TestTracksInfo { diff --git a/substrate/frame/referenda/src/types.rs b/substrate/frame/referenda/src/types.rs index 1039b288b2a..e83f28b472c 100644 --- a/substrate/frame/referenda/src/types.rs +++ b/substrate/frame/referenda/src/types.rs @@ -258,7 +258,8 @@ impl< Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone, - > ReferendumInfo + > + ReferendumInfo { /// Take the Decision Deposit from `self`, if there is one. Returns an `Err` if `self` is not /// in a valid state for the Decision Deposit to be refunded. diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 95f4110a2d7..012b4bfab9a 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -349,18 +349,19 @@ where let Some(ident) = path.path.get_ident() else { panic!("Type needs to be ident"); }; - let size = - if ident == "i8" || - ident == "i16" || ident == "i32" || - ident == "u8" || ident == "u16" || - ident == "u32" - { - 1 - } else if ident == "i64" || ident == "u64" { - 2 - } else { - panic!("Pass by value only supports primitives"); - }; + let size = if ident == "i8" || + ident == "i16" || + ident == "i32" || + ident == "u8" || + ident == "u16" || + ident == "u32" + { + 1 + } else if ident == "i64" || ident == "u64" { + 2 + } else { + panic!("Pass by value only supports primitives"); + }; registers_used += size; if registers_used > ALLOWED_REGISTERS { return quote! { diff --git a/substrate/frame/society/src/lib.rs b/substrate/frame/society/src/lib.rs index b4c5c88af3d..04879cd8709 100644 --- a/substrate/frame/society/src/lib.rs +++ b/substrate/frame/society/src/lib.rs @@ -1387,18 +1387,6 @@ impl_ensure_origin_with_arg_ignoring_arg! { {} } -struct InputFromRng<'a, T>(&'a mut T); -impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { - fn remaining_len(&mut self) -> Result, codec::Error> { - return Ok(None) - } - - fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { - self.0.fill_bytes(into); - Ok(()) - } -} - pub enum Period { Voting { elapsed: BlockNumber, more: BlockNumber }, Claim { elapsed: BlockNumber, more: BlockNumber }, diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index df8e844cdad..2a13f99855b 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -281,7 +281,7 @@ fn bidding_works() { // No more candidates satisfy the requirements assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - // Next period + // Next period run_to_block(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index ab2c00ca9cc..dd178a95bec 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -7995,7 +7995,7 @@ mod ledger_recovery { assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // OK assert_eq!(Bonded::::get(&333), Some(444)); // OK assert!(Payee::::get(&333).is_some()); // OK - // however, ledger associated with its controller was killed. + // however, ledger associated with its controller was killed. assert!(Ledger::::get(&444).is_none()); // NOK // side effects on 444 - ledger, bonded, payee, lock should be completely removed. diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 3e38adcc3c5..729a803a302 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -592,8 +592,6 @@ pub struct Pallet { pub cfg_pattern: Vec, /// The doc literals pub docs: Vec, - /// attributes - pub attrs: Vec, } impl Pallet { @@ -764,7 +762,6 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; diff --git a/substrate/frame/support/procedural/src/derive_impl.rs b/substrate/frame/support/procedural/src/derive_impl.rs index 69117c02681..5d39c2707de 100644 --- a/substrate/frame/support/procedural/src/derive_impl.rs +++ b/substrate/frame/support/procedural/src/derive_impl.rs @@ -304,6 +304,7 @@ fn test_derive_impl_attr_args_parsing() { #[test] fn test_runtime_type_with_doc() { + #[allow(dead_code)] trait TestTrait { type Test; } diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index d40a571c9ea..a2c1e6eec7f 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -321,9 +321,10 @@ pub fn derive_debug_no_bound(input: TokenStream) -> TokenStream { /// This behaviour is useful to prevent bloating the runtime WASM blob from unneeded code. #[proc_macro_derive(RuntimeDebugNoBound)] pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { - if cfg!(any(feature = "std", feature = "try-runtime")) { - no_bound::debug::derive_debug_no_bound(input) - } else { + let try_runtime_or_std_impl: proc_macro2::TokenStream = + no_bound::debug::derive_debug_no_bound(input.clone()).into(); + + let stripped_impl = { let input = syn::parse_macro_input!(input as syn::DeriveInput); let name = &input.ident; @@ -338,8 +339,22 @@ pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { } }; ) - .into() - } + }; + + let frame_support = match generate_access_from_frame_or_crate("frame-support") { + Ok(frame_support) => frame_support, + Err(e) => return e.to_compile_error().into(), + }; + + quote::quote!( + #frame_support::try_runtime_or_std_enabled! { + #try_runtime_or_std_impl + } + #frame_support::try_runtime_and_std_not_enabled! { + #stripped_impl + } + ) + .into() } /// Derive [`PartialEq`] but do not bound any generic. diff --git a/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs b/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs index 248e8346943..b71aed680dc 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/genesis_build.rs @@ -35,7 +35,7 @@ pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let where_clause = &genesis_build.where_clause; quote::quote_spanned!(genesis_build.attr_span => - #[cfg(feature = "std")] + #frame_support::std_enabled! { impl<#type_impl_gen> #frame_support::sp_runtime::BuildStorage for #gen_cfg_ident<#gen_cfg_use_gen> #where_clause { fn assimilate_storage(&self, storage: &mut #frame_support::sp_runtime::Storage) -> std::result::Result<(), std::string::String> { @@ -45,5 +45,6 @@ pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { }) } } + } ) } diff --git a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs index 1b0c09c4e36..c31ddd8a47b 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/hooks.rs @@ -324,15 +324,13 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { Self as #frame_support::traits::Hooks< #frame_system::pallet_prelude::BlockNumberFor:: > - >::try_state(n).map_err(|err| { + >::try_state(n).inspect_err(|err| { #frame_support::__private::log::error!( target: #frame_support::LOG_TARGET, "❌ {:?} try_state checks failed: {:?}", #pallet_name, err ); - - err }) } } diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs index 346dff46f12..68ced1bc0ed 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/call.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs @@ -400,18 +400,19 @@ impl CallDef { } for (feeless_arg, arg) in feeless_check.inputs.iter().skip(1).zip(args.iter()) { - let feeless_arg_type = - if let syn::Pat::Type(syn::PatType { ty, .. }) = feeless_arg.clone() { - if let syn::Type::Reference(pat) = *ty { - pat.elem.clone() - } else { - let msg = "Invalid pallet::call, feeless_if closure argument must be a reference"; - return Err(syn::Error::new(ty.span(), msg)); - } + let feeless_arg_type = if let syn::Pat::Type(syn::PatType { ty, .. }) = + feeless_arg.clone() + { + if let syn::Type::Reference(pat) = *ty { + pat.elem.clone() } else { - let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern"; - return Err(syn::Error::new(feeless_arg.span(), msg)); - }; + let msg = "Invalid pallet::call, feeless_if closure argument must be a reference"; + return Err(syn::Error::new(ty.span(), msg)); + } + } else { + let msg = "Invalid pallet::call, feeless_if closure argument must be a type ascription pattern"; + return Err(syn::Error::new(feeless_arg.span(), msg)); + }; if feeless_arg_type != arg.2 { let msg = diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs index de1efa267c8..52f57cd2cd8 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs @@ -91,7 +91,6 @@ impl Pallet { cfg_pattern, pallet_parts, docs, - attrs: item.attrs.clone(), }) } } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 28283f2a5a0..269867e38c5 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -2523,6 +2523,8 @@ pub use frame_support_procedural::register_default_impl; sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); // Generate a macro that will enable/disable code based on `try-runtime` feature being active. sp_core::generate_feature_enabled_macro!(try_runtime_enabled, feature = "try-runtime", $); +sp_core::generate_feature_enabled_macro!(try_runtime_or_std_enabled, any(feature = "try-runtime", feature = "std"), $); +sp_core::generate_feature_enabled_macro!(try_runtime_and_std_not_enabled, all(not(feature = "try-runtime"), not(feature = "std")), $); // Helper for implementing GenesisBuilder runtime API pub mod genesis_builder_helper; diff --git a/substrate/frame/support/src/storage/generator/double_map.rs b/substrate/frame/support/src/storage/generator/double_map.rs index b68f3fa495f..a9116f1f66b 100644 --- a/substrate/frame/support/src/storage/generator/double_map.rs +++ b/substrate/frame/support/src/storage/generator/double_map.rs @@ -346,9 +346,8 @@ where final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_double_map_final_key(key1, key2).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/generator/map.rs b/substrate/frame/support/src/storage/generator/map.rs index e905df41a5a..2d1f6c9f73a 100644 --- a/substrate/frame/support/src/storage/generator/map.rs +++ b/substrate/frame/support/src/storage/generator/map.rs @@ -311,9 +311,8 @@ impl> storage::StorageMap final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_map_final_key(key).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/generator/nmap.rs b/substrate/frame/support/src/storage/generator/nmap.rs index 0466583a279..9083aba9d32 100755 --- a/substrate/frame/support/src/storage/generator/nmap.rs +++ b/substrate/frame/support/src/storage/generator/nmap.rs @@ -305,9 +305,8 @@ where final_key }; - unhashed::take(old_key.as_ref()).map(|value| { + unhashed::take(old_key.as_ref()).inspect(|value| { unhashed::put(Self::storage_n_map_final_key::(key).as_ref(), &value); - value }) } } diff --git a/substrate/frame/support/src/storage/types/double_map.rs b/substrate/frame/support/src/storage/types/double_map.rs index c70d9de5446..24aad3de0b3 100644 --- a/substrate/frame/support/src/storage/types/double_map.rs +++ b/substrate/frame/support/src/storage/types/double_map.rs @@ -129,7 +129,8 @@ impl OnEmpty, MaxValues, >, - > where + > +where Prefix: StorageInstance, Hasher1: crate::hash::StorageHasher, Hasher2: crate::hash::StorageHasher, diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 5e1bcc777df..7c90a12d416 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -769,5 +769,6 @@ fn derive_partial_eq_no_bound_core_mod() { crate::DefaultNoBound, crate::EqNoBound, )] + #[allow(dead_code)] struct Test; } diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 492475d6f63..4d3b122daf6 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -488,7 +488,7 @@ pub trait DefensiveMin { /// assert_eq!(4, 4_u32.defensive_min(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMin; /// // min(4, 3) panics. /// 4_u32.defensive_min(3_u32); @@ -505,7 +505,7 @@ pub trait DefensiveMin { /// assert_eq!(3, 3_u32.defensive_strict_min(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMin; /// // min(4, 4) panics. /// 4_u32.defensive_strict_min(4_u32); @@ -552,7 +552,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_max(4_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMax; /// // max(4, 5) panics. /// 4_u32.defensive_max(5_u32); @@ -569,7 +569,7 @@ pub trait DefensiveMax { /// assert_eq!(4, 4_u32.defensive_strict_max(3_u32)); /// ``` /// - /// ```#[cfg_attr(debug_assertions, should_panic)] + /// ```should_panic /// use frame_support::traits::DefensiveMax; /// // max(4, 4) panics. /// 4_u32.defensive_strict_max(4_u32); diff --git a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs index 8dbeecd8e86..a7465c87fb2 100644 --- a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs +++ b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs @@ -197,7 +197,8 @@ impl TryDecodeEntireS QueryKind, OnEmpty, MaxValues, - > where + > +where Prefix: CountedStorageMapInstance, Hasher: StorageHasher, Key: FullCodec, @@ -229,7 +230,8 @@ impl QueryKind, OnEmpty, MaxValues, - > where + > +where Prefix: StorageInstance, Hasher1: StorageHasher, Key1: FullCodec, diff --git a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr index 7e0a02be649..04203e4b684 100644 --- a/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr +++ b/substrate/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr @@ -8,3 +8,7 @@ error[E0277]: the `?` operator can only be used in a function that returns `Resu | ^ cannot use the `?` operator in a function that returns `()` | = help: the trait `FromResidual>` is not implemented for `()` +help: consider adding return type + | +31 | fn bench() -> Result<(), Box> { + | +++++++++++++++++++++++++++++++++++++++++ diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index b28cae2ddef..59e36775d46 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -53,7 +53,15 @@ note: required by a bound in `frame_system::Event` | ^^^^^^ required by this bound in `Event` = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | ^ the trait `Config` is not implemented for `Runtime` + | + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -63,9 +71,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `TryFrom` is not implemented for `RawOrigin<_>` | -note: required because it appears within the type `RuntimeEvent` + = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -75,15 +86,25 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Clone` - --> $RUST/core/src/clone.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | - | pub trait Clone: Sized { - | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime` + | +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -93,9 +114,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `frame_system::Event` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub enum Event { + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0369]: binary operation `==` cannot be applied to type `&frame_system::Event` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -106,14 +134,21 @@ note: required because it appears within the type `RuntimeEvent` 27 | | } 28 | | } | |_^ -note: required by a bound in `EncodeLike` - --> $CARGO/parity-scale-codec-3.6.12/src/encode_like.rs | - | pub trait EncodeLike: Sized + Encode {} - | ^^^^^ required by this bound in `EncodeLike` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +note: an implementation of `Config` might be missing for `Runtime` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where + | |______________________^ must implement `Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `PartialEq` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -123,9 +158,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `frame_system::Event` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub enum Event { + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `frame_system::Event: Encode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -135,15 +177,12 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Decode` - --> $CARGO/parity-scale-codec-3.6.12/src/codec.rs + | |_^ the trait `Encode` is not implemented for `frame_system::Event` | - | pub trait Decode: Sized { - | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Encode` is implemented for `frame_system::Event` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -153,21 +192,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_syste ... | 27 | | } 28 | | } - | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `frame_system::Event` +note: required by a bound in `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { - | ^^^^^ -note: required by a bound in `From` - --> $RUST/core/src/convert/mod.rs - | - | pub trait From: Sized { - | ^ required by this bound in `From` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` +error[E0277]: the trait bound `frame_system::Event: Decode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -177,29 +211,40 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_syste ... | 27 | | } 28 | | } - | |_^ within `frame_system::Event`, the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: Sized` + | |_^ the trait `Decode` is not implemented for `frame_system::Event` | -note: required because it appears within the type `frame_system::Event` + = help: the trait `Decode` is implemented for `frame_system::Event` + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:11 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^^^^^^^ the trait `Config` is not implemented for `Runtime` + | +note: required by a bound in `frame_system::Event` --> $WORKSPACE/substrate/frame/system/src/lib.rs | | pub enum Event { - | ^^^^^ -note: required by a bound in `TryInto` - --> $RUST/core/src/convert/mod.rs - | - | pub trait TryInto: Sized { - | ^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^ required by this bound in `Event` error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -20 | construct_runtime! { - | ^ the trait `Config` is not implemented for `Runtime` +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Event: std::fmt::Debug` | - = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::fmt::Debug` is implemented for `frame_system::Event` + = note: required for `frame_system::Event` to implement `std::fmt::Debug` + = note: required for the cast from `&frame_system::Event` to `&dyn std::fmt::Debug` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -209,10 +254,12 @@ error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satis ... | 27 | | } 28 | | } - | |_^ the trait `TryFrom` is not implemented for `RawOrigin<_>` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `frame_system::Error: std::fmt::Debug` | - = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::fmt::Debug` is implemented for `frame_system::Error` + = note: required for `frame_system::Error` to implement `std::fmt::Debug` + = note: required for the cast from `&frame_system::Error` to `&dyn std::fmt::Debug` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -224,11 +271,15 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` + | |_^ the trait `Config` is not implemented for `Runtime` | - = help: the trait `Callable` is implemented for `Pallet` - = note: required for `Pallet` to implement `Callable` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:21:13 + | +21 | pub struct Runtime where + | ^^^^^^^ the trait `Config` is not implemented for `Runtime` error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -240,10 +291,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RawOrigin<_>: Into<_>` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: required for `RawOrigin<_>` to implement `Into` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -253,13 +306,9 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Clone` - --> $RUST/core/src/clone.rs + | |_^ the trait `Config` is not implemented for `Runtime` | - | pub trait Clone: Sized { - | ^^^^^ required by this bound in `Clone` - = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -271,10 +320,17 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:26:11 + | +26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^^^^^^^ the trait `Config` is not implemented for `Runtime` + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -284,12 +340,10 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `EncodeLike` - --> $CARGO/parity-scale-codec-3.6.12/src/encode_like.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: PalletInfoAccess` | - | pub trait EncodeLike: Sized + Encode {} - | ^^^^^ required by this bound in `EncodeLike` + = help: the trait `PalletInfoAccess` is implemented for `Pallet` + = note: required for `Pallet` to implement `PalletInfoAccess` = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied @@ -302,10 +356,13 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | + = help: the trait `Callable` is implemented for `Pallet` = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -315,15 +372,13 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Decode` - --> $CARGO/parity-scale-codec-3.6.12/src/codec.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` | - | pub trait Decode: Sized { - | ^^^^^ required by this bound in `Decode` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied +error[E0369]: binary operation `==` cannot be applied to type `&frame_system::Call` --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -333,10 +388,22 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` +note: an implementation of `Config` might be missing for `Runtime` + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | / construct_runtime! { +21 | | pub struct Runtime where + | |______________________^ must implement `Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `PartialEq` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `frame_system::Call: Encode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -346,27 +413,31 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `frame_support::sp_runtime::traits::Dispatchable::Config` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | |_^ the trait `Encode` is not implemented for `frame_system::Call` | - | type Config; - | ^^^^^^^^^^^^ required by this bound in `Dispatchable::Config` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `Encode` is implemented for `frame_system::Call` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied - --> tests/construct_runtime_ui/deprecated_where_block.rs:26:3 + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | -26 | System: frame_system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ the trait `Config` is not implemented for `Runtime` +20 | / construct_runtime! { +21 | | pub struct Runtime where +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required by a bound in `GenesisConfig` +note: required by a bound in `frame_system::Call` --> $WORKSPACE/substrate/frame/system/src/lib.rs | - | pub struct GenesisConfig { - | ^^^^^^ required by this bound in `GenesisConfig` + | #[pallet::call] + | ^^^^ required by this bound in `Call` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied +error[E0277]: the trait bound `frame_system::Call: Decode` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -376,10 +447,12 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` + | |_^ the trait `Decode` is not implemented for `frame_system::Call` | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + = help: the trait `Decode` is implemented for `frame_system::Call` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -389,15 +462,256 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `frame_support::pallet_prelude::ValidateUnsigned::Call` - --> $WORKSPACE/substrate/primitives/runtime/src/traits.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `Pallet: Callable` + | + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::RuntimeDebug` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the method `get_dispatch_info` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: GetDispatchInfo` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: GetDispatchInfo` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the method `is_feeless` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: CheckIfFeeless` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: CheckIfFeeless` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs | - | type Call; - | ^^^^^^^^^^ required by this bound in `ValidateUnsigned::Call` + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0599]: the method `get_call_name` exists for reference `&Call`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ method cannot be called on `&Call` due to unsatisfied trait bounds + | + ::: $WORKSPACE/substrate/frame/system/src/lib.rs + | + | #[pallet::call] + | ---- doesn't satisfy `frame_system::Call: GetCallName` + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` + which is required by `frame_system::Call: GetCallName` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `storage_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `call_functions` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the variant or associated item `event_metadata` exists for enum `Event`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ variant or associated item cannot be called on `Event` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `pallet_constants_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `error_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0599]: the function or associated item `pallet_documentation_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -407,9 +721,21 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` - | -note: required because it appears within the type `RuntimeEvent` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `GenesisConfig: Serialize` + | + = help: the trait `Serialize` is implemented for `GenesisConfig` + = note: required for `GenesisConfig` to implement `Serialize` +note: required by a bound in `frame_support::sp_runtime::serde::ser::SerializeStruct::serialize_field` + --> $CARGO/serde-1.0.210/src/ser/mod.rs + | + | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + | --------------- required by a bound in this associated function + | where + | T: ?Sized + Serialize; + | ^^^^^^^^^ required by this bound in `SerializeStruct::serialize_field` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -419,15 +745,16 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Result` - --> $RUST/core/src/result.rs + | |_^ the trait `Config` is not implemented for `Runtime` | - | pub enum Result { - | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -437,9 +764,16 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEven ... | 27 | | } 28 | | } - | |_^ within `RuntimeEvent`, the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeEvent: Sized` + | |_^ the trait `Config` is not implemented for `Runtime` | -note: required because it appears within the type `RuntimeEvent` +note: required by a bound in `GenesisConfig` + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::serde::Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -449,13 +783,11 @@ note: required because it appears within the type `RuntimeEvent` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `TryInto` - --> $RUST/core/src/convert/mod.rs + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `GenesisConfig: std::default::Default` | - | pub trait TryInto: Sized { - | ^^^^^ required by this bound in `TryInto` - = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = help: the trait `std::default::Default` is implemented for `GenesisConfig` + = note: required for `GenesisConfig` to implement `std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 @@ -467,10 +799,24 @@ error[E0277]: the trait bound `Runtime: Config` is not satisfied ... | 27 | | } 28 | | } - | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `RuntimeCall: Sized` - | - = note: required for `Pallet` to implement `Callable` -note: required because it appears within the type `RuntimeCall` + | |_^ the trait `Config` is not implemented for `Runtime`, which is required by `(Pallet,): OnGenesis` + | + = help: the following other types implement trait `OnGenesis`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and $N others + = note: required for `Pallet` to implement `OnGenesis` + = note: 1 redundant requirement hidden + = note: required for `(Pallet,)` to implement `OnGenesis` + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0282]: type annotations needed --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | 20 | / construct_runtime! { @@ -480,10 +826,8 @@ note: required because it appears within the type `RuntimeCall` ... | 27 | | } 28 | | } - | |_^ -note: required by a bound in `Result` - --> $RUST/core/src/result.rs + | |_^ cannot infer type | - | pub enum Result { - | ^ required by this bound in `Result` - = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::__private::codec::Decode` which comes from the expansion of the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:168:90: Failed to normalize std::rc::Rc::RuntimeCall,)>), bound_vars: [Region(BrAnon)] }, Binder { value: Projection(Output = bool), bound_vars: [Region(BrAnon)] }] + '{erased}, std::alloc::Global>, std::alloc::Global>, maybe try to call `try_normalize_erasing_regions` instead diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index 0f7afb2b990..c50cba71d4e 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `Event` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these enums | 18 + use frame_support_test::Event; | @@ -48,7 +48,7 @@ error[E0433]: failed to resolve: could not find `Event` in `pallet` | |_^ could not find `Event` in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these enums | 18 + use frame_support_test::Event; | diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index 10093b26f5a..2aa794edc3c 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `GenesisConfig` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these structs | 18 + use frame_system::GenesisConfig; | diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index 30005c07cb6..d8dc7bd45bc 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -54,8 +54,8 @@ error[E0599]: no function or associated item named `is_inherent` found for struc | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `is_inherent`, perhaps you need to implement one of them: - candidate #1: `ProvideInherent` - candidate #2: `IsInherent` + candidate #1: `IsInherent` + candidate #2: `ProvideInherent` = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `check_inherent` found for struct `pallet::Pallet` in the current scope diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index d0f4b44ab0d..58c42311b87 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -28,7 +28,7 @@ error[E0412]: cannot find type `Origin` in module `pallet` | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing one of these items +help: consider importing one of these type aliases | 18 + use frame_support_test::Origin; | diff --git a/substrate/frame/support/test/tests/derive_impl.rs b/substrate/frame/support/test/tests/derive_impl.rs index 675e85f4bfc..3514593c856 100644 --- a/substrate/frame/support/test/tests/derive_impl.rs +++ b/substrate/frame/support/test/tests/derive_impl.rs @@ -25,15 +25,9 @@ struct SomeRectangle {} #[frame_support::register_default_impl(SomeRectangle)] impl Shape for SomeRectangle { - #[cfg(not(feature = "feature-frame-testing"))] fn area(&self) -> u32 { 10 } - - #[cfg(feature = "feature-frame-testing")] - fn area(&self) -> u32 { - 0 - } } struct SomeSquare {} @@ -44,9 +38,5 @@ impl Shape for SomeSquare {} #[test] fn test_feature_parsing() { let square = SomeSquare {}; - #[cfg(not(feature = "feature-frame-testing"))] assert_eq!(square.area(), 10); - - #[cfg(feature = "feature-frame-testing")] - assert_eq!(square.area(), 0); } diff --git a/substrate/frame/support/test/tests/derive_no_bound.rs b/substrate/frame/support/test/tests/derive_no_bound.rs index b1914707805..6fc4ea12c51 100644 --- a/substrate/frame/support/test/tests/derive_no_bound.rs +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -159,6 +159,7 @@ fn test_struct_unnamed() { PartialOrdNoBound, OrdNoBound, )] +#[allow(dead_code)] struct StructNoGenerics { field1: u32, field2: u64, diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr index db8a5079607..8bf82bff780 100644 --- a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr @@ -23,3 +23,13 @@ note: required by a bound in `std::cmp::Eq` | | pub trait Eq: PartialEq { | ^^^^^^^^^^^^^^^ required by this bound in `Eq` + +error[E0599]: `::C` is not an iterator + --> tests/derive_no_bound_ui/ord.rs:24:2 + | +24 | c: T::C, + | ^ `::C` is not an iterator + | + = note: the following trait bounds were not satisfied: + `::C: Iterator` + which is required by `&mut ::C: Iterator` diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 7f1ce0556ea..3d6aa1d8374 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -2431,9 +2431,10 @@ fn post_runtime_upgrade_detects_storage_version_issues() { // any storage version "enabled". assert!( ExecutiveWithUpgradePallet4::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost) - .unwrap_err() == "On chain storage version set, while the pallet \ + .unwrap_err() == + "On chain storage version set, while the pallet \ doesn't have the `#[pallet::storage_version(VERSION)]` attribute." - .into() + .into() ); }); } diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 2a4ceecd8fa..1f91f774023 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -33,3 +33,12 @@ error[E0369]: binary operation `==` cannot be applied to type `&, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound.rs:38:14 + | +38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index fc993e9ff68..4657c0a0c60 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -34,7 +34,7 @@ error[E0369]: binary operation `==` cannot be applied to type `&, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^ -error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied +error[E0277]: the trait bound `::Bar: Encode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:36 | 18 | #[frame_support::pallet] @@ -45,10 +45,19 @@ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is | = note: required for `::Bar` to implement `Encode` -error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied +error[E0277]: the trait bound `::Bar: Decode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:42 | 38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar`, which is required by `::Bar: Decode` | = note: required for `::Bar` to implement `Decode` + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:14 + | +38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index d6486a49079..f829baeb4c1 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -27,3 +27,12 @@ help: consider annotating `Bar` with `#[derive(Debug)]` 34 + #[derive(Debug)] 35 | struct Bar; | + +error: unused variable: `origin` + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:40:14 + | +40 | pub fn foo(origin: OriginFor, _bar: Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_origin` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr b/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr index 3256e69528a..8049c07648c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr @@ -5,3 +5,9 @@ error[E0369]: binary operation `!=` cannot be applied to type `NoStorageVersionS | ------------------------------- ^^ -------------------------------- StorageVersion | | | NoStorageVersionSet + | +note: the foreign item type `NoStorageVersionSet` doesn't implement `PartialEq` + --> $WORKSPACE/substrate/frame/support/src/traits/metadata.rs + | + | pub struct NoStorageVersionSet; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not implement `PartialEq` diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index 629fefebbe2..2fcc3328214 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -38,13 +38,13 @@ error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied | |__________________^ the trait `MaxEncodedLen` is not implemented for `Vec`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage, Vec>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr index 44d8d3fcadb..92fb5b9cb38 100644 --- a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -5,12 +5,12 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied | ^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` | = help: the following other types implement trait `PalletError`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others diff --git a/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr b/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr index b7327943ee2..c04499dbbd1 100644 --- a/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr @@ -13,3 +13,9 @@ help: add missing generic argument | 29 | impl Hooks for Pallet {} | +++++++++++++ + +error[E0277]: the trait bound `pallet::Pallet: Hooks<<<::Block as frame_support::sp_runtime::traits::Block>::Header as frame_support::sp_runtime::traits::Header>::Number>` is not satisfied + --> tests/pallet_ui/hooks_invalid_item.rs:28:12 + | +28 | #[pallet::hooks] + | ^^^^^ the trait `Hooks<<<::Block as frame_support::sp_runtime::traits::Block>::Header as frame_support::sp_runtime::traits::Header>::Number>` is not implemented for `pallet::Pallet` diff --git a/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr b/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr index 5ea3be470a0..516bddd2c61 100644 --- a/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/inherent_check_inner_span.stderr @@ -6,6 +6,6 @@ error[E0046]: not all trait items implemented, missing: `Call`, `Error`, `INHERE | = help: implement the missing item: `type Call = /* Type */;` = help: implement the missing item: `type Error = /* Type */;` - = help: implement the missing item: `const INHERENT_IDENTIFIER: [u8; 8] = value;` + = help: implement the missing item: `const INHERENT_IDENTIFIER: [u8; 8] = [42; 8];` = help: implement the missing item: `fn create_inherent(_: &InherentData) -> std::option::Option<::Call> { todo!() }` = help: implement the missing item: `fn is_inherent(_: &::Call) -> bool { todo!() }` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index c8c41e80501..fa6b7284d88 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -12,10 +12,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -34,14 +34,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -61,14 +61,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -84,14 +84,14 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: - bool - char - i8 - i16 - i32 - i64 - i128 - u8 + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -105,10 +105,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -122,14 +122,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -144,14 +144,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -167,10 +167,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` @@ -184,14 +184,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -206,14 +206,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 08b35eb8ed1..944b194b7bc 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -12,10 +12,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -34,14 +34,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -61,14 +61,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: PartialStorageInfoTrait` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -84,14 +84,14 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied | |____________^ the trait `TypeInfo` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `TypeInfo`: - bool - char - i8 - i16 - i32 - i64 - i128 - u8 + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) and $N others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -105,10 +105,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -122,14 +122,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -144,14 +144,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageEntryMetadataBuilder` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` @@ -167,10 +167,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeDecode`: + Arc Box - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` @@ -184,14 +184,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied | |____________^ the trait `EncodeLike` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `EncodeLike`: - - - - - - - - + `&&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&T` implements `EncodeLike` + `&[(K, V)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[(T,)]` implements `EncodeLike>` + `&[T]` implements `EncodeLike>` and $N others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` @@ -206,14 +206,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: TryDecodeEntireStorage` | = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc Box - bytes::bytes::Bytes Cow<'a, T> - parity_scale_codec::Ref<'a, T, U> - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes Rc - Arc Vec + bytes::bytes::Bytes and $N others = note: required for `Bar` to implement `Encode` = note: required for `Bar` to implement `FullEncode` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 042a6f67fd3..95ec76e29c0 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -12,13 +12,13 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 9f57b85f3a8..8351dd92d59 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -12,14 +12,14 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar`, which is required by `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>: StorageInfoTrait` | = help: the following other types implement trait `MaxEncodedLen`: - bool - i8 - i16 - i32 - i64 - i128 - u8 - u16 + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) and $N others = note: required for `NMapKey` to implement `KeyGeneratorMaxEncodedLen` = note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, NMapKey, u32>` to implement `StorageInfoTrait` diff --git a/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr b/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr index 41dcd273d96..0b13dcff90c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/type_value_error_in_block.stderr @@ -3,3 +3,9 @@ error[E0599]: no function or associated item named `new` found for type `u32` in | 37 | u32::new() | ^^^ function or associated item not found in `u32` + | +help: there is a method `ne` with a similar name, but with different arguments + --> $RUST/core/src/cmp.rs + | + | fn ne(&self, other: &Rhs) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 534ba1e863f..aa1094e3fe4 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -848,6 +848,7 @@ pub fn from_post_weight_info(ref_time: Option, pays_fee: Pays) -> PostDispa #[docify::export] #[test] fn last_runtime_upgrade_spec_version_usage() { + #[allow(dead_code)] struct Migration; impl OnRuntimeUpgrade for Migration { diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index 35d5322a6f3..bac89967d6a 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -273,8 +273,10 @@ fn signed_ext_length_fee_is_also_updated_per_congestion() { NextFeeMultiplier::::put(Multiplier::saturating_from_rational(3, 2)); let len = 10; - assert_ok!(ChargeTransactionPayment::::from(10) // tipped - .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(3, 0)), len)); + assert_ok!( + ChargeTransactionPayment::::from(10) // tipped + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(3, 0)), len) + ); assert_eq!( Balances::free_balance(1), 100 // original diff --git a/substrate/frame/utility/src/lib.rs b/substrate/frame/utility/src/lib.rs index ed5544fe55c..a4f66298f3f 100644 --- a/substrate/frame/utility/src/lib.rs +++ b/substrate/frame/utility/src/lib.rs @@ -134,8 +134,8 @@ pub mod pallet { fn batched_calls_limit() -> u32 { let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION; let call_size = ((core::mem::size_of::<::RuntimeCall>() as u32 + - CALL_ALIGN - 1) / CALL_ALIGN) * - CALL_ALIGN; + CALL_ALIGN - 1) / + CALL_ALIGN) * CALL_ALIGN; // The margin to take into account vec doubling capacity. let margin_factor = 3; diff --git a/substrate/frame/vesting/src/tests.rs b/substrate/frame/vesting/src/tests.rs index 004da0dfbfa..57cb59f27a4 100644 --- a/substrate/frame/vesting/src/tests.rs +++ b/substrate/frame/vesting/src/tests.rs @@ -182,7 +182,7 @@ fn unvested_balance_should_not_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); // Account 1 cannot send more than vested amount... assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 56), TokenError::Frozen); @@ -194,7 +194,7 @@ fn vested_balance_should_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest(Some(1).into())); assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); @@ -232,7 +232,7 @@ fn vested_balance_should_transfer_using_vest_other() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest_other(Some(2).into(), 1)); assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); @@ -286,7 +286,7 @@ fn extra_balance_should_transfer() { assert_eq!(Vesting::vesting_balance(&2), Some(200)); assert_ok!(Vesting::vest(Some(2).into())); assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100)); // Account 2 can send extra - // units gained + // units gained }); } @@ -296,7 +296,7 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { let user12_free_balance = Balances::free_balance(&12); assert_eq!(user12_free_balance, 2560); // Account 12 has free balance - // Account 12 has liquid funds + // Account 12 has liquid funds assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - 256 * 5)); // Account 12 has delayed vesting diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index 121ce6b9993..1d21f23eb80 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -41,3 +41,4 @@ harness = false [features] "enable-staging-api" = [] +disable-ui-tests = [] diff --git a/substrate/primitives/api/test/tests/trybuild.rs b/substrate/primitives/api/test/tests/trybuild.rs index b0a334eb7a2..b13e5df9d6f 100644 --- a/substrate/primitives/api/test/tests/trybuild.rs +++ b/substrate/primitives/api/test/tests/trybuild.rs @@ -15,18 +15,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. - if env::var("RUN_UI_TESTS").is_err() { + if std::env::var("RUN_UI_TESTS").is_err() { return } // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); + + // Warnings are part of our UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); diff --git a/substrate/primitives/api/test/tests/ui/deprecation_info.stderr b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr index 2466c3ea5d5..78c687e876d 100644 --- a/substrate/primitives/api/test/tests/ui/deprecation_info.stderr +++ b/substrate/primitives/api/test/tests/ui/deprecation_info.stderr @@ -12,6 +12,21 @@ error: Invalid deprecation attribute: missing `note` 20 | #[deprecated(unknown_kw = "test")] | ^ +error: malformed `deprecated` attribute input + --> tests/ui/deprecation_info.rs:24:3 + | +24 | #[deprecated = 5] + | ^^^^^^^^^^^^^^^^^ + | +help: the following are the possible correct uses + | +24 | #[deprecated = "reason"] + | +24 | #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")] + | +24 | #[deprecated] + | + error[E0541]: unknown meta item 'unknown_kw' --> tests/ui/deprecation_info.rs:20:16 | diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 535bbb178d5..d625020fe4d 100644 --- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -22,10 +22,7 @@ error[E0053]: method `test` has an incompatible type for trait --> tests/ui/impl_incorrect_method_signature.rs:33:17 | 33 | fn test(data: String) {} - | ^^^^^^ - | | - | expected `u64`, found `std::string::String` - | help: change the parameter type to match the trait: `u64` + | ^^^^^^ expected `u64`, found `std::string::String` | note: type in trait --> tests/ui/impl_incorrect_method_signature.rs:27:17 @@ -34,6 +31,10 @@ note: type in trait | ^^^ = note: expected signature `fn(u64)` found signature `fn(std::string::String)` +help: change the parameter type to match the trait + | +33 | fn test(data: u64) {} + | ~~~ error[E0308]: mismatched types --> tests/ui/impl_incorrect_method_signature.rs:33:11 @@ -53,3 +54,12 @@ note: associated function defined here | 27 | fn test(data: u64); | ^^^^ + +error: unused variable: `data` + --> tests/ui/impl_incorrect_method_signature.rs:33:11 + | +33 | fn test(data: String) {} + | ^^^^ help: if this is intentional, prefix it with an underscore: `_data` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr index 84575577187..764a0bafaa4 100644 --- a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -21,8 +21,7 @@ error[E0050]: method `test` has 2 parameters but the declaration in trait `Api:: 29 | / sp_api::mock_impl_runtime_apis! { 30 | | impl Api for MockApi { 31 | | fn test(self, data: u64) {} -32 | | -33 | | fn test2(&mut self, data: u64) {} +... | 34 | | } 35 | | } | |_^ expected 3 parameters, found 2 @@ -41,8 +40,7 @@ error[E0050]: method `test2` has 2 parameters but the declaration in trait `Api: 29 | / sp_api::mock_impl_runtime_apis! { 30 | | impl Api for MockApi { 31 | | fn test(self, data: u64) {} -32 | | -33 | | fn test2(&mut self, data: u64) {} +... | 34 | | } 35 | | } | |_^ expected 3 parameters, found 2 diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index f4e0f3b0afb..26be311c02f 100644 --- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -22,10 +22,7 @@ error[E0053]: method `test` has an incompatible type for trait --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:17 | 33 | fn test(data: &u64) { - | ^^^^ - | | - | expected `u64`, found `&u64` - | help: change the parameter type to match the trait: `u64` + | ^^^^ expected `u64`, found `&u64` | note: type in trait --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:27:17 @@ -34,6 +31,10 @@ note: type in trait | ^^^ = note: expected signature `fn(_)` found signature `fn(&_)` +help: change the parameter type to match the trait + | +33 | fn test(data: u64) { + | ~~~ error[E0308]: mismatched types --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:11 @@ -57,3 +58,12 @@ help: consider removing the borrow | 33 | fn test(data: &u64) { | + +error: unused variable: `data` + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:11 + | +33 | fn test(data: &u64) { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_data` + | + = note: `-D unused-variables` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_variables)]` diff --git a/substrate/primitives/runtime-interface/tests/ui.rs b/substrate/primitives/runtime-interface/tests/ui.rs index 821d0b73f26..408ddbc981e 100644 --- a/substrate/primitives/runtime-interface/tests/ui.rs +++ b/substrate/primitives/runtime-interface/tests/ui.rs @@ -15,18 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; - #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. - if env::var("RUN_UI_TESTS").is_err() { + if std::env::var("RUN_UI_TESTS").is_err() { return } // As trybuild is using `cargo check`, we don't need the real WASM binaries. - env::set_var("SKIP_WASM_BUILD", "1"); + std::env::set_var("SKIP_WASM_BUILD", "1"); let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); diff --git a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr index 10012ede793..1c1649d011e 100644 --- a/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr +++ b/substrate/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr @@ -9,9 +9,41 @@ note: found an item that was configured out | 25 | fn bar() {} | ^^^ - = note: the item is gated behind the `bar-feature` feature +note: the item is gated behind the `bar-feature` feature + --> tests/ui/no_feature_gated_method.rs:24:8 + | +24 | #[cfg(feature = "bar-feature")] + | ^^^^^^^^^^^^^^^^^^^^^^^ note: found an item that was configured out --> tests/ui/no_feature_gated_method.rs:25:5 | 25 | fn bar() {} | ^^^ +note: the item is gated here + --> tests/ui/no_feature_gated_method.rs:20:1 + | +20 | #[runtime_interface] + | ^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the attribute macro `runtime_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected `cfg` condition value: `bar-feature` + --> tests/ui/no_feature_gated_method.rs:24:8 + | +24 | #[cfg(feature = "bar-feature")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std` + = help: consider adding `bar-feature` as a feature in `Cargo.toml` + = note: see for more information about checking conditional configuration + = note: `-D unexpected-cfgs` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unexpected_cfgs)]` + +error: unexpected `cfg` condition value: `bar-feature` + --> tests/ui/no_feature_gated_method.rs:27:12 + | +27 | #[cfg(not(feature = "bar-feature"))] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: expected values for `feature` are: `default`, `disable_target_static_assertions`, and `std` + = help: consider adding `bar-feature` as a feature in `Cargo.toml` + = note: see for more information about checking conditional configuration diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index 25ef15eaf56..fc63bc76dec 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -2342,8 +2342,6 @@ impl BlockNumberProvider for () { mod tests { use super::*; use crate::codec::{Decode, Encode, Input}; - #[cfg(feature = "bls-experimental")] - use sp_core::{bls377, bls381}; use sp_core::{ crypto::{Pair, UncheckedFrom}, ecdsa, ed25519, sr25519, @@ -2486,14 +2484,4 @@ mod tests { fn ecdsa_verify_works() { signature_verify_test!(ecdsa); } - - #[cfg(feature = "bls-experimental")] - fn bls377_verify_works() { - signature_verify_test!(bls377) - } - - #[cfg(feature = "bls-experimental")] - fn bls381_verify_works() { - signature_verify_test!(bls381) - } } diff --git a/substrate/primitives/state-machine/src/ext.rs b/substrate/primitives/state-machine/src/ext.rs index 7a79c4e8a1f..baad7e621be 100644 --- a/substrate/primitives/state-machine/src/ext.rs +++ b/substrate/primitives/state-machine/src/ext.rs @@ -713,6 +713,7 @@ where } /// Implement `Encode` by forwarding the stored raw vec. +#[allow(dead_code)] struct EncodeOpaqueValue(Vec); impl Encode for EncodeOpaqueValue { diff --git a/substrate/test-utils/cli/build.rs b/substrate/test-utils/cli/build.rs index a68cb706e8f..c63f0b8b667 100644 --- a/substrate/test-utils/cli/build.rs +++ b/substrate/test-utils/cli/build.rs @@ -20,6 +20,6 @@ use std::env; fn main() { if let Ok(profile) = env::var("PROFILE") { - println!("cargo:rustc-cfg=build_type=\"{}\"", profile); + println!("cargo:rustc-cfg=build_profile=\"{}\"", profile); } } diff --git a/substrate/test-utils/cli/src/lib.rs b/substrate/test-utils/cli/src/lib.rs index d77a89b4dbf..70d68f6f183 100644 --- a/substrate/test-utils/cli/src/lib.rs +++ b/substrate/test-utils/cli/src/lib.rs @@ -130,7 +130,7 @@ pub fn start_node() -> Child { /// build_substrate(&["--features=try-runtime"]); /// ``` pub fn build_substrate(args: &[&str]) { - let is_release_build = !cfg!(build_type = "debug"); + let is_release_build = !cfg!(build_profile = "debug"); // Get the root workspace directory from the CARGO_MANIFEST_DIR environment variable let mut cmd = Command::new("cargo"); diff --git a/substrate/utils/fork-tree/src/lib.rs b/substrate/utils/fork-tree/src/lib.rs index ff86467c85d..fe349b6c29a 100644 --- a/substrate/utils/fork-tree/src/lib.rs +++ b/substrate/utils/fork-tree/src/lib.rs @@ -810,12 +810,11 @@ impl<'a, H, N, V> Iterator for ForkTreeIterator<'a, H, N, V> { type Item = &'a Node; fn next(&mut self) -> Option { - self.stack.pop().map(|node| { + self.stack.pop().inspect(|node| { // child nodes are stored ordered by max branch height (decreasing), // we want to keep this ordering while iterating but since we're // using a stack for iterator state we need to reverse it. self.stack.extend(node.children.iter().rev()); - node }) } } diff --git a/substrate/utils/frame/benchmarking-cli/build.rs b/substrate/utils/frame/benchmarking-cli/build.rs index 1545d1e0c21..06cdb7973ab 100644 --- a/substrate/utils/frame/benchmarking-cli/build.rs +++ b/substrate/utils/frame/benchmarking-cli/build.rs @@ -24,8 +24,12 @@ use std::env; pub fn main() { if let Ok(opt_level) = env::var("OPT_LEVEL") { println!("cargo:rustc-cfg=build_opt_level={:?}", opt_level); + } else { + println!("cargo:rustc-cfg=build_opt_level={:?}", "unknown"); } if let Ok(profile) = env::var("PROFILE") { println!("cargo:rustc-cfg=build_profile={:?}", profile); + } else { + println!("cargo:rustc-cfg=build_profile={:?}", "unknown"); } } diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index a6eda078fde..dcd5e6f1ade 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -601,9 +601,10 @@ fn project_enabled_features( // We don't want to enable the `std`/`default` feature for the wasm build and // we need to check if the feature is enabled by checking the env variable. *f != "std" && - *f != "default" && env::var(format!("CARGO_FEATURE_{}", feature_env)) - .map(|v| v == "1") - .unwrap_or_default() + *f != "default" && + env::var(format!("CARGO_FEATURE_{feature_env}")) + .map(|v| v == "1") + .unwrap_or_default() }) .map(|d| d.0.clone()) .collect::>(); diff --git a/templates/parachain/pallets/template/src/benchmarking.rs b/templates/parachain/pallets/template/src/benchmarking.rs index 5acad6e60de..29572c3ff60 100644 --- a/templates/parachain/pallets/template/src/benchmarking.rs +++ b/templates/parachain/pallets/template/src/benchmarking.rs @@ -1,5 +1,4 @@ //! Benchmarking setup for pallet-template -#![cfg(feature = "runtime-benchmarks")] use super::*; use frame_benchmarking::v2::*; diff --git a/templates/solochain/pallets/template/src/benchmarking.rs b/templates/solochain/pallets/template/src/benchmarking.rs index d1a9554aed6..8af5d246f76 100644 --- a/templates/solochain/pallets/template/src/benchmarking.rs +++ b/templates/solochain/pallets/template/src/benchmarking.rs @@ -1,5 +1,5 @@ //! Benchmarking setup for pallet-template -#![cfg(feature = "runtime-benchmarks")] + use super::*; #[allow(unused)] -- GitLab From 8edc2cadedb6a6b4508682731b983ac25837f105 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:37:14 +0300 Subject: [PATCH 016/127] Remove jaeger from approval-voting and approval-distribution (#5830) Jaeger spans were not usable for debugging, see https://github.com/paritytech/polkadot-sdk/issues/4995, but we pay a price in CPU cost, subsystem-benchmarks show this brings a reduction of about 10-15% in CPU usage per subsystem, so remove it. --------- Signed-off-by: Alexandru Gheorghe --- Cargo.lock | 2 - polkadot/node/core/approval-voting/Cargo.toml | 1 - .../approval-voting-regression-bench.rs | 5 +- .../node/core/approval-voting/src/import.rs | 9 - polkadot/node/core/approval-voting/src/lib.rs | 173 +----------------- .../node/core/approval-voting/src/tests.rs | 3 +- .../network/approval-distribution/Cargo.toml | 1 - .../network/approval-distribution/src/lib.rs | 68 +------ prdoc/pr_5830.prdoc | 13 ++ 9 files changed, 23 insertions(+), 252 deletions(-) create mode 100644 prdoc/pr_5830.prdoc diff --git a/Cargo.lock b/Cargo.lock index c20c8f71c80..2e8eda3ca9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13902,7 +13902,6 @@ dependencies = [ "futures-timer", "itertools 0.11.0", "log", - "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -14240,7 +14239,6 @@ dependencies = [ "merlin", "parity-scale-codec", "parking_lot 0.12.3", - "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index e678118440f..2c3db866566 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -29,7 +29,6 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } sc-keystore = { workspace = true } sp-consensus = { workspace = true } diff --git a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs index db0396a8319..e202d1ee229 100644 --- a/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs +++ b/polkadot/node/core/approval-voting/benches/approval-voting-regression-bench.rs @@ -83,8 +83,9 @@ fn main() -> Result<(), String> { ("Sent to peers", 63995.2200, 0.01), ])); messages.extend(average_usage.check_cpu_usage(&[ - ("approval-distribution", 12.2736, 0.1), - ("approval-voting", 2.7174, 0.1), + ("approval-distribution", 0.1, 0.1), + ("approval-voting", 0.1, 0.1), + ("approval-voting-parallel", 18.0758, 0.1), ])); if messages.is_empty() { diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index 5c456d22b21..e50a2f91148 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -28,7 +28,6 @@ //! //! We maintain a rolling window of session indices. This starts as empty -use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ approval::{ self as approval_types, @@ -348,13 +347,6 @@ pub(crate) async fn handle_new_head< finalized_number: &Option, ) -> SubsystemResult> { const MAX_HEADS_LOOK_BACK: BlockNumber = MAX_FINALITY_LAG; - let _handle_new_head_span = state - .spans - .get(&head) - .map(|span| span.child("handle-new-head")) - .unwrap_or_else(|| jaeger::Span::new(head, "handle-new-head")) - .with_string_tag("head", format!("{:?}", head)) - .with_stage(jaeger::Stage::ApprovalChecking); let header = { let (h_tx, h_rx) = oneshot::channel(); @@ -666,7 +658,6 @@ pub(crate) mod tests { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::default()), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 23f052b95f6..83509829305 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,9 +21,6 @@ //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. -use itertools::Itertools; -use jaeger::{hash_to_trace_identifier, PerLeafSpan}; -use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ approval::{ v1::{BlockApprovalMeta, DelayTranche}, @@ -634,11 +631,7 @@ impl Wakeups { self.wakeups.entry(tick).or_default().push((block_hash, candidate_hash)); } - fn prune_finalized_wakeups( - &mut self, - finalized_number: BlockNumber, - spans: &mut HashMap, - ) { + fn prune_finalized_wakeups(&mut self, finalized_number: BlockNumber) { let after = self.block_numbers.split_off(&(finalized_number + 1)); let pruned_blocks: HashSet<_> = std::mem::replace(&mut self.block_numbers, after) .into_iter() @@ -662,9 +655,6 @@ impl Wakeups { } } } - - // Remove all spans that are associated with pruned blocks. - spans.retain(|h, _| !pruned_blocks.contains(h)); } // Get the wakeup for a particular block/candidate combo, if any. @@ -841,7 +831,6 @@ struct State { slot_duration_millis: u64, clock: Arc, assignment_criteria: Box, - spans: HashMap, // Per block, candidate records about how long we take until we gather enough // assignments, this is relevant because it gives us a good idea about how many // tranches we trigger and why. @@ -1203,7 +1192,6 @@ where slot_duration_millis: subsystem.slot_duration_millis, clock: subsystem.clock, assignment_criteria, - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), @@ -1525,18 +1513,8 @@ async fn handle_actions< continue } - let mut launch_approval_span = state - .spans - .get(&relay_block_hash) - .map(|span| span.child("launch-approval")) - .unwrap_or_else(|| jaeger::Span::new(candidate_hash, "launch-approval")) - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - metrics.on_assignment_produced(assignment_tranche); let block_hash = indirect_cert.block_hash; - launch_approval_span.add_string_tag("block-hash", format!("{:?}", block_hash)); let validator_index = indirect_cert.validator; if distribute_assignment { @@ -1580,7 +1558,6 @@ async fn handle_actions< backing_group, executor_params, core_index, - &launch_approval_span, ) .await }, @@ -1591,15 +1568,6 @@ async fn handle_actions< } }, Action::NoteApprovedInChainSelection(block_hash) => { - let _span = state - .spans - .get(&block_hash) - .map(|span| span.child("note-approved-in-chain-selection")) - .unwrap_or_else(|| { - jaeger::Span::new(block_hash, "note-approved-in-chain-selection") - }) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); sender.send_message(ChainSelectionMessage::Approved(block_hash)).await; }, Action::BecomeActive => { @@ -1704,15 +1672,6 @@ async fn distribution_messages_for_activation b, None => { @@ -1722,9 +1681,6 @@ async fn distribution_messages_for_activation c, None => { @@ -1936,9 +1890,6 @@ async fn handle_from_overseer< let mut actions = Vec::new(); if let Some(activated) = update.activated { let head = activated.hash; - let approval_voting_span = - jaeger::PerLeafSpan::new(activated.span, "approval-voting"); - state.spans.insert(head, approval_voting_span); match import::handle_new_head( sender, approval_voting_sender, @@ -2009,7 +1960,7 @@ async fn handle_from_overseer< // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans // accordingly. - wakeups.prune_finalized_wakeups(block_number, &mut state.spans); + wakeups.prune_finalized_wakeups(block_number); state.cleanup_assignments_gathering_timestamp(block_number); // // `prune_finalized_wakeups` prunes all finalized block hashes. We prune spans @@ -2051,23 +2002,8 @@ async fn handle_from_overseer< result.0 }, ApprovalVotingMessage::ApprovedAncestor(target, lower_bound, res) => { - let mut approved_ancestor_span = state - .spans - .get(&target) - .map(|span| span.child("approved-ancestor")) - .unwrap_or_else(|| jaeger::Span::new(target, "approved-ancestor")) - .with_stage(jaeger::Stage::ApprovalChecking) - .with_string_tag("leaf", format!("{:?}", target)); - match handle_approved_ancestor( - sender, - db, - target, - lower_bound, - wakeups, - &mut approved_ancestor_span, - &metrics, - ) - .await + match handle_approved_ancestor(sender, db, target, lower_bound, wakeups, &metrics) + .await { Ok(v) => { let _ = res.send(v); @@ -2260,15 +2196,11 @@ async fn handle_approved_ancestor>( target: Hash, lower_bound: BlockNumber, wakeups: &Wakeups, - span: &mut jaeger::Span, metrics: &Metrics, ) -> SubsystemResult> { const MAX_TRACING_WINDOW: usize = 200; const ABNORMAL_DEPTH_THRESHOLD: usize = 5; const LOGGING_DEPTH_THRESHOLD: usize = 10; - let mut span = span - .child("handle-approved-ancestor") - .with_stage(jaeger::Stage::ApprovalChecking); let mut all_approved_max = None; @@ -2284,8 +2216,6 @@ async fn handle_approved_ancestor>( } }; - span.add_uint_tag("leaf-number", target_number as u64); - span.add_uint_tag("lower-bound", lower_bound as u64); if target_number <= lower_bound { return Ok(None) } @@ -2317,9 +2247,6 @@ async fn handle_approved_ancestor>( let mut bits: BitVec = Default::default(); for (i, block_hash) in std::iter::once(target).chain(ancestry).enumerate() { - let mut entry_span = - span.child("load-block-entry").with_stage(jaeger::Stage::ApprovalChecking); - entry_span.add_string_tag("block-hash", format!("{:?}", block_hash)); // Block entries should be present as the assumption is that // nothing here is finalized. If we encounter any missing block // entries we can fail. @@ -2386,7 +2313,6 @@ async fn handle_approved_ancestor>( ) } metrics.on_unapproved_candidates_in_unfinalized_chain(unapproved.len()); - entry_span.add_uint_tag("unapproved-candidates", unapproved.len() as u64); for candidate_hash in unapproved { match db.load_candidate_entry(&candidate_hash)? { None => { @@ -2507,15 +2433,6 @@ async fn handle_approved_ancestor>( number: block_number, descriptions: block_descriptions, }); - match all_approved_max { - Some(HighestApprovedAncestorBlock { ref hash, ref number, .. }) => { - span.add_uint_tag("highest-approved-number", *number as u64); - span.add_string_fmt_debug_tag("highest-approved-hash", hash); - }, - None => { - span.add_string_tag("reached-lower-bound", "true"); - }, - } Ok(all_approved_max) } @@ -2622,17 +2539,6 @@ where let assignment = checked_assignment.assignment(); let candidate_indices = checked_assignment.candidate_indices(); let tranche = checked_assignment.tranche(); - let mut import_assignment_span = state - .spans - .get(&assignment.block_hash) - .map(|span| span.child("import-assignment")) - .unwrap_or_else(|| jaeger::Span::new(assignment.block_hash, "import-assignment")) - .with_relay_parent(assignment.block_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - - for candidate_index in candidate_indices.iter_ones() { - import_assignment_span.add_uint_tag("candidate-index", candidate_index as u64); - } let block_entry = match db.load_block_entry(&assignment.block_hash)? { Some(b) => b, @@ -2712,13 +2618,6 @@ where )), // no candidate at core. }; - import_assignment_span - .add_string_tag("candidate-hash", format!("{:?}", assigned_candidate_hash)); - import_assignment_span.add_string_tag( - "traceID", - format!("{:?}", jaeger::hash_to_trace_identifier(assigned_candidate_hash.0)), - ); - if candidate_entry.approval_entry_mut(&assignment.block_hash).is_none() { return Ok(( AssignmentCheckResult::Bad(AssignmentCheckError::Internal( @@ -2776,7 +2675,6 @@ where }; is_duplicate &= approval_entry.is_assigned(assignment.validator); approval_entry.import_assignment(tranche, assignment.validator, tick_now); - import_assignment_span.add_uint_tag("tranche", tranche as u64); // We've imported a new assignment, so we need to schedule a wake-up for when that might // no-show. @@ -2850,14 +2748,6 @@ where return Ok((Vec::new(), $e)) }}; } - let mut span = state - .spans - .get(&approval.block_hash) - .map(|span| span.child("import-approval")) - .unwrap_or_else(|| jaeger::Span::new(approval.block_hash, "import-approval")) - .with_string_fmt_debug_tag("candidate-index", approval.candidate_indices.clone()) - .with_relay_parent(approval.block_hash) - .with_stage(jaeger::Stage::ApprovalChecking); let block_entry = match db.load_block_entry(&approval.block_hash)? { Some(b) => b, @@ -2887,20 +2777,6 @@ where }, }; - span.add_string_tag("candidate-hashes", format!("{:?}", approved_candidates_info)); - span.add_string_tag( - "traceIDs", - format!( - "{:?}", - approved_candidates_info - .iter() - .map(|(_, approved_candidate_hash)| hash_to_trace_identifier( - approved_candidate_hash.0 - )) - .collect_vec() - ), - ); - gum::trace!( target: LOG_TARGET, "Received approval for num_candidates {:}", @@ -3255,16 +3131,6 @@ async fn process_wakeup>( metrics: &Metrics, wakeups: &Wakeups, ) -> SubsystemResult> { - let mut span = state - .spans - .get(&relay_block) - .map(|span| span.child("process-wakeup")) - .unwrap_or_else(|| jaeger::Span::new(candidate_hash, "process-wakeup")) - .with_trace_id(candidate_hash) - .with_relay_parent(relay_block) - .with_candidate(candidate_hash) - .with_stage(jaeger::Stage::ApprovalChecking); - let block_entry = db.load_block_entry(&relay_block)?; let candidate_entry = db.load_candidate_entry(&candidate_hash)?; @@ -3293,7 +3159,7 @@ async fn process_wakeup>( Slot::from(u64::from(session_info.no_show_slots)), ); let tranche_now = state.clock.tranche_now(state.slot_duration_millis, block_entry.slot()); - span.add_uint_tag("tranche", tranche_now as u64); + gum::trace!( target: LOG_TARGET, tranche = tranche_now, @@ -3456,7 +3322,6 @@ async fn launch_approval< backing_group: GroupIndex, executor_params: ExecutorParams, core_index: Option, - span: &jaeger::Span, ) -> SubsystemResult> { let (a_tx, a_rx) = oneshot::channel(); let (code_tx, code_rx) = oneshot::channel(); @@ -3490,13 +3355,6 @@ async fn launch_approval< let para_id = candidate.descriptor.para_id; gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data."); - let request_validation_data_span = span - .child("request-validation-data") - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); - let timer = metrics.time_recover_and_approve(); sender .send_message(AvailabilityRecoveryMessage::RecoverAvailableData( @@ -3508,13 +3366,6 @@ async fn launch_approval< )) .await; - let request_validation_result_span = span - .child("request-validation-result") - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_stage(jaeger::Stage::ApprovalChecking); - sender .send_message(RuntimeApiMessage::Request( block_hash, @@ -3578,7 +3429,6 @@ async fn launch_approval< return ApprovalState::failed(validator_index, candidate_hash) }, }; - drop(request_validation_data_span); let validation_code = match code_rx.await { Err(_) => return ApprovalState::failed(validator_index, candidate_hash), @@ -3650,7 +3500,6 @@ async fn launch_approval< "Failed to validate candidate due to internal error", ); metrics_guard.take().on_approval_error(); - drop(request_validation_result_span); return ApprovalState::failed(validator_index, candidate_hash) }, } @@ -3678,17 +3527,6 @@ async fn issue_approval< ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest, wakeups: &Wakeups, ) -> SubsystemResult> { - let mut issue_approval_span = state - .spans - .get(&block_hash) - .map(|span| span.child("issue-approval")) - .unwrap_or_else(|| jaeger::Span::new(block_hash, "issue-approval")) - .with_trace_id(candidate_hash) - .with_string_tag("block-hash", format!("{:?}", block_hash)) - .with_candidate(candidate_hash) - .with_validator_index(validator_index) - .with_stage(jaeger::Stage::ApprovalChecking); - let mut block_entry = match db.load_block_entry(&block_hash)? { Some(b) => b, None => { @@ -3713,7 +3551,6 @@ async fn issue_approval< }, Some(idx) => idx, }; - issue_approval_span.add_int_tag("candidate_index", candidate_index as i64); let candidate_hash = match block_entry.candidate(candidate_index as usize) { Some((_, h)) => *h, diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index ec825b800c7..db5ffd441c0 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -17,6 +17,7 @@ use self::test_helpers::mock::new_leaf; use super::*; use crate::backend::V1ReadBackend; +use itertools::Itertools; use overseer::prometheus::{ prometheus::{IntCounter, IntCounterVec}, Histogram, HistogramOpts, HistogramVec, Opts, @@ -4922,7 +4923,6 @@ fn test_gathering_assignments_statements() { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), @@ -5017,7 +5017,6 @@ fn test_observe_assignment_gathering_status() { slot_duration_millis: 6_000, clock: Arc::new(MockClock::default()), assignment_criteria: Box::new(MockAssignmentCriteria::check_only(|_| Ok(0))), - spans: HashMap::new(), per_block_assignments_gathering_times: LruMap::new(ByLength::new( MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS, )), diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index 51478dfa4a4..8d674a73347 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -16,7 +16,6 @@ polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } itertools = { workspace = true } diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 2fcb639338e..876cc59b9c2 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -27,7 +27,6 @@ use self::metrics::Metrics; use futures::{select, FutureExt as _}; use itertools::Itertools; use net_protocol::peer_set::{ProtocolVersion, ValidationVersion}; -use polkadot_node_jaeger as jaeger; use polkadot_node_network_protocol::{ self as net_protocol, filter_by_peer_version, grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology}, @@ -359,9 +358,6 @@ pub struct State { /// Tracks recently finalized blocks. recent_outdated_blocks: RecentlyOutdated, - /// HashMap from active leaves to spans - spans: HashMap, - /// Aggression configuration. aggression_config: AggressionConfig, @@ -872,18 +868,9 @@ impl State { ); for meta in metas { - let mut span = self - .spans - .get(&meta.hash) - .map(|span| span.child(&"handle-new-blocks")) - .unwrap_or_else(|| jaeger::Span::new(meta.hash, &"handle-new-blocks")) - .with_string_tag("block-hash", format!("{:?}", meta.hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - match self.blocks.entry(meta.hash) { hash_map::Entry::Vacant(entry) => { let candidates_count = meta.candidates.len(); - span.add_uint_tag("candidates-count", candidates_count as u64); let mut candidates = Vec::with_capacity(candidates_count); candidates.resize_with(candidates_count, Default::default); @@ -1330,7 +1317,6 @@ impl State { if let Some(block_entry) = self.blocks.remove(relay_block) { self.topologies.dec_session_refs(block_entry.session); } - self.spans.remove(&relay_block); }); // If a block was finalized, this means we may need to move our aggression @@ -1357,21 +1343,6 @@ impl State { RA: overseer::SubsystemSender, R: CryptoRng + Rng, { - let _span = self - .spans - .get(&assignment.block_hash) - .map(|span| { - span.child(if source.peer_id().is_some() { - "peer-import-and-distribute-assignment" - } else { - "local-import-and-distribute-assignment" - }) - }) - .unwrap_or_else(|| jaeger::Span::new(&assignment.block_hash, "distribute-assignment")) - .with_string_tag("block-hash", format!("{:?}", assignment.block_hash)) - .with_optional_peer_id(source.peer_id().as_ref()) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_hash = assignment.block_hash; let validator_index = assignment.validator; @@ -1837,21 +1808,6 @@ impl State { vote: IndirectSignedApprovalVoteV2, session_info_provider: &mut RuntimeInfo, ) { - let _span = self - .spans - .get(&vote.block_hash) - .map(|span| { - span.child(if source.peer_id().is_some() { - "peer-import-and-distribute-approval" - } else { - "local-import-and-distribute-approval" - }) - }) - .unwrap_or_else(|| jaeger::Span::new(&vote.block_hash, "distribute-approval")) - .with_string_tag("block-hash", format!("{:?}", vote.block_hash)) - .with_optional_peer_id(source.peer_id().as_ref()) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_hash = vote.block_hash; let validator_index = vote.validator; let candidate_indices = &vote.candidate_indices; @@ -2091,14 +2047,6 @@ impl State { ) -> HashMap, ValidatorSignature)> { let mut all_sigs = HashMap::new(); for (hash, index) in indices { - let _span = self - .spans - .get(&hash) - .map(|span| span.child("get-approval-signatures")) - .unwrap_or_else(|| jaeger::Span::new(&hash, "get-approval-signatures")) - .with_string_tag("block-hash", format!("{:?}", hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - let block_entry = match self.blocks.get(&hash) { None => { gum::debug!( @@ -2776,18 +2724,12 @@ impl ApprovalDistribution { session_info_provider, ) .await, - FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => { + FromOrchestra::Signal(OverseerSignal::ActiveLeaves(_update)) => { gum::trace!(target: LOG_TARGET, "active leaves signal (ignored)"); // the relay chain blocks relevant to the approval subsystems // are those that are available, but not finalized yet // activated and deactivated heads hence are irrelevant to this subsystem, other // than for tracing purposes. - if let Some(activated) = update.activated { - let head = activated.hash; - let approval_distribution_span = - jaeger::PerLeafSpan::new(activated.span, "approval-distribution"); - state.spans.insert(head, approval_distribution_span); - } }, FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, number)) => { gum::trace!(target: LOG_TARGET, number = %number, "finalized signal"); @@ -2846,14 +2788,6 @@ impl ApprovalDistribution { .await; }, ApprovalDistributionMessage::DistributeAssignment(cert, candidate_indices) => { - let _span = state - .spans - .get(&cert.block_hash) - .map(|span| span.child("import-and-distribute-assignment")) - .unwrap_or_else(|| jaeger::Span::new(&cert.block_hash, "distribute-assignment")) - .with_string_tag("block-hash", format!("{:?}", cert.block_hash)) - .with_stage(jaeger::Stage::ApprovalDistribution); - gum::debug!( target: LOG_TARGET, ?candidate_indices, diff --git a/prdoc/pr_5830.prdoc b/prdoc/pr_5830.prdoc new file mode 100644 index 00000000000..10b586e4a4a --- /dev/null +++ b/prdoc/pr_5830.prdoc @@ -0,0 +1,13 @@ +title: "Remove jaeger from approval-voting and approval-distribution" + +doc: + - audience: Node Dev + description: | + Jaeger was remove from approval-voting and approval-distribution because + it did not prove to improve the debugging and it wasted precious cpu cycles. + +crates: + - name: polkadot-approval-distribution + bump: none + - name: polkadot-node-core-approval-voting + bump: none -- GitLab From e85099e23c35a4614640b2c2c576f4385055033a Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Fri, 27 Sep 2024 06:07:40 -0300 Subject: [PATCH 017/127] Disable flaky tests reported in 5848/5844 (#5851) Reported in: #5844 #5848 --- .gitlab/pipeline/zombienet/polkadot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index e25bc4ca229..9a3bed24cfa 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -108,7 +108,7 @@ zombienet-polkadot-functional-0004-parachains-disputes-garbage-candidate: --local-dir="${LOCAL_DIR}/functional" --test="0004-parachains-garbage-candidate.zndsl" -zombienet-polkadot-functional-0005-parachains-disputes-past-session: +.zombienet-polkadot-functional-0005-parachains-disputes-past-session: extends: - .zombienet-polkadot-common script: @@ -351,7 +351,7 @@ zombienet-polkadot-malus-0001-dispute-valid: --local-dir="${LOCAL_DIR}/integrationtests" --test="0001-dispute-valid-block.zndsl" -zombienet-polkadot-coretime-revenue: +.zombienet-polkadot-coretime-revenue: extends: - .zombienet-polkadot-common needs: -- GitLab From 6d1943be7630b693397ed8e5db9d061afe0dd898 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:33:37 +0200 Subject: [PATCH 018/127] make frame omni bencher to install from path (#5853) - install frame-omni-bencher from the path - fix bench_features config --- .github/workflows/cmd.yml | 4 +++- .github/workflows/runtimes-matrix.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 5498beb50cc..e416b120269 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -336,7 +336,9 @@ jobs: - name: Install dependencies for bench if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') - run: cargo install subweight frame-omni-bencher --locked + run: | + cargo install subweight --locked + cargo install --path substrate/utils/frame/omni-bencher --locked - name: Run cmd id: cmd diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index b868a410a16..b0a307ab315 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -44,6 +44,7 @@ "package": "asset-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", "header": "cumulus/file_header.txt", + "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", "is_relay": false -- GitLab From a5e40d0cd0a0d941d6fe58aa278fedfcb9102710 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Fri, 27 Sep 2024 13:24:25 +0200 Subject: [PATCH 019/127] Remove fixed workaround in `impl_runtime_apis` (#5839) This PR removes a workaround which had a reference comment to a rust compiler issue. The issue has been fixed and we should be able to remove that trait bound. --- .../polkadot-parachain-lib/src/command.rs | 4 +-- prdoc/pr_5839.prdoc | 21 +++++++++++ .../procedural/src/runtime/expand/mod.rs | 2 +- .../api/proc-macro/src/impl_runtime_apis.rs | 22 ++---------- .../primitives/api/proc-macro/src/utils.rs | 35 ++----------------- 5 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 prdoc/pr_5839.prdoc diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index 43fb551f80d..63562f192ae 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -39,7 +39,6 @@ use sc_cli::{Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; -use std::panic::{RefUnwindSafe, UnwindSafe}; /// Structure that can be used in order to provide customizers for different functionalities of the /// node binary that is being built using this library. @@ -55,8 +54,7 @@ pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, ) -> Box where - Block: NodeBlock + UnwindSafe + RefUnwindSafe, - Block::BoundedHeader: UnwindSafe + RefUnwindSafe, + Block: NodeBlock, { match aura_id { AuraConsensusId::Sr25519 => crate::service::new_aura_node_spec::< diff --git a/prdoc/pr_5839.prdoc b/prdoc/pr_5839.prdoc new file mode 100644 index 00000000000..1dc95fe5c33 --- /dev/null +++ b/prdoc/pr_5839.prdoc @@ -0,0 +1,21 @@ +# 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 internal workaround for compiler bug + +doc: + - audience: + - Runtime Dev + - Node Dev + description: | + Remove a workaround we had in the `impl_runtime_apis` macro for a compiler bug that has been long fixed. + No impact on downstream users is expected, except relaxed trait bounds in a few places where the compiler + is now able to deduce more type info itself. + +crates: + - name: sp-api-proc-macro + bump: patch + - name: frame-support-procedural + bump: patch + - name: polkadot-parachain-lib + bump: patch diff --git a/substrate/frame/support/procedural/src/runtime/expand/mod.rs b/substrate/frame/support/procedural/src/runtime/expand/mod.rs index f34ab1cef54..666bc03aa41 100644 --- a/substrate/frame/support/procedural/src/runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/runtime/expand/mod.rs @@ -77,7 +77,7 @@ pub fn expand(def: Def, legacy_ordering: bool) -> TokenStream2 { }; let res = expander::Expander::new("construct_runtime") - .dry(std::env::var("FRAME_EXPAND").is_err()) + .dry(std::env::var("EXPAND_MACROS").is_err()) .verbose(true) .write_to_out_dir(res) .expect("Does not fail because of IO in OUT_DIR; qed"); 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 21397abc8fc..de922e3253e 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -18,7 +18,7 @@ use crate::{ common::API_VERSION_ATTRIBUTE, utils::{ - extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, + extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, @@ -632,11 +632,6 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { } fn fold_item_impl(&mut self, mut input: ItemImpl) -> ItemImpl { - // All this `UnwindSafe` magic below here is required for this rust bug: - // https://github.com/rust-lang/rust/issues/24159 - // Before we directly had the final block type and rust could determine that it is unwind - // safe, but now we just have a generic parameter `Block`. - let crate_ = generate_crate_access(); // Implement the trait for the `RuntimeApiImpl` @@ -644,9 +639,9 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { Box::new(parse_quote!( RuntimeApiImpl<__SrApiBlock__, RuntimeApiImplCall> )); input.generics.params.push(parse_quote!( - __SrApiBlock__: #crate_::BlockT + std::panic::UnwindSafe + - std::panic::RefUnwindSafe + __SrApiBlock__: #crate_::BlockT )); + input .generics .params @@ -661,17 +656,6 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { where_clause.predicates.push(parse_quote! { &'static RuntimeApiImplCall: Send }); - // Require that all types used in the function signatures are unwind safe. - extract_all_signature_types(&input.items).iter().for_each(|i| { - where_clause.predicates.push(parse_quote! { - #i: std::panic::UnwindSafe + std::panic::RefUnwindSafe - }); - }); - - where_clause.predicates.push(parse_quote! { - __SrApiBlock__::Header: std::panic::UnwindSafe + std::panic::RefUnwindSafe - }); - input.attrs = filter_cfg_attrs(&input.attrs); fold::fold_item_impl(self, input) diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 94da6748cbd..dc993c2ac42 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -22,8 +22,8 @@ use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, token::And, Attribute, Error, Expr, - ExprLit, FnArg, GenericArgument, Ident, ImplItem, ItemImpl, Lit, Meta, MetaNameValue, Pat, - Path, PathArguments, Result, ReturnType, Signature, Token, Type, TypePath, + ExprLit, FnArg, GenericArgument, Ident, ItemImpl, Lit, Meta, MetaNameValue, Pat, Path, + PathArguments, Result, ReturnType, Signature, Token, Type, TypePath, }; /// Generates the access to the `sc_client` crate. @@ -159,37 +159,6 @@ pub fn prefix_function_with_trait(trait_: &Ident, function: &F) -> format!("{}_{}", trait_, function.to_string()) } -/// Extract all types that appear in signatures in the given `ImplItem`'s. -/// -/// If a type is a reference, the inner type is extracted (without the reference). -pub fn extract_all_signature_types(items: &[ImplItem]) -> Vec { - items - .iter() - .filter_map(|i| match i { - ImplItem::Fn(method) => Some(&method.sig), - _ => None, - }) - .flat_map(|sig| { - let ret_ty = match &sig.output { - ReturnType::Default => None, - ReturnType::Type(_, ty) => Some((**ty).clone()), - }; - - sig.inputs - .iter() - .filter_map(|i| match i { - FnArg::Typed(arg) => Some(&arg.ty), - _ => None, - }) - .map(|ty| match &**ty { - Type::Reference(t) => (*t.elem).clone(), - _ => (**ty).clone(), - }) - .chain(ret_ty) - }) - .collect() -} - /// Extracts the block type from a trait path. /// /// It is expected that the block type is the first type in the generic arguments. -- GitLab From 58ade7a62bc18f1453c9abb037dcd9fdf95f5c25 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:47:13 +0300 Subject: [PATCH 020/127] substrate/utils: enable wasm builder diagnostics propagation (#5838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description `substrate-wasm-builder` can be a build dependency for crates which develop FRAME runtimes. I had a tough time seeing errors happening in such crates (e.g. runtimes from the `templates` directory) in my IDE. I use a combination of rust-analyzer + nvim-lsp + nvim-lspconfig + rustacean.vim and all of this stack is not able to correctly parse errors emitted during the `build` phase. As a matter of fact there is also a cargo issue tracking specifically this issue where cargo doesn't propagate the `--message-format` type to the build phase: [here](https://github.com/rust-lang/cargo/issues/14246) initially and then [here](https://github.com/rust-lang/cargo/issues/8283). It feels like a solution for this use case isn't very close, so if it comes to runtimes development (both as an SDK user and developer), enabling wasm builder to emit diagnostics messages friendly to IDEs would be useful for regular workflows where IDEs are used for finding errors instead of manually running `cargo` commands. ## Integration It can be an issue if Substrate/FRAME SDKs users and developers rely on the runtimes' crates build phase output in certain ways. Emitting compilation messages as json will pollute the regular compilation output so people that would manually run `cargo build` or `cargo check` on their crates will have a tougher time extracting the non JSON output. ## Review Notes Rust IDEs based on rust-analyzer rely on cargo check/clippy to extract diagnostic information. The information is generated by passing flags like `--messages-format=json` to the `cargo` commands. The messages are extracted by rust-analyzer and published to LSP clients that will populate UIs accordingly. We need to build against the wasm target by using `message-format=json` too so that IDEs can show the errors for crates that have a build dependency on `substrate-wasm-builder`. --------- Signed-off-by: Iulian Barbu Co-authored-by: Bastian Köcher --- Cargo.lock | 1 + Cargo.toml | 1 + prdoc/pr_5838.prdoc | 20 +++++++++++++++++++ substrate/utils/wasm-builder/Cargo.toml | 1 + substrate/utils/wasm-builder/src/lib.rs | 7 +++++++ .../utils/wasm-builder/src/wasm_project.rs | 13 ++++++++++++ 6 files changed, 43 insertions(+) create mode 100644 prdoc/pr_5838.prdoc diff --git a/Cargo.lock b/Cargo.lock index 2e8eda3ca9e..c10d1e93907 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24090,6 +24090,7 @@ dependencies = [ "parity-wasm", "polkavm-linker 0.9.2", "sc-executor 0.32.0", + "shlex", "sp-core 28.0.0", "sp-io 30.0.0", "sp-maybe-compressed-blob 11.0.0", diff --git a/Cargo.toml b/Cargo.toml index c40d59fce3c..a30b5b57d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1200,6 +1200,7 @@ sha1 = { version = "0.10.6" } sha2 = { version = "0.10.7", default-features = false } sha3 = { version = "0.10.0", default-features = false } shell-runtime = { path = "cumulus/parachains/runtimes/starters/shell" } +shlex = { version = "1.3.0" } slot-range-helper = { path = "polkadot/runtime/common/slot_range_helper", default-features = false } slotmap = { version = "1.0" } smallvec = { version = "1.11.0", default-features = false } diff --git a/prdoc/pr_5838.prdoc b/prdoc/pr_5838.prdoc new file mode 100644 index 00000000000..f6ce091a12d --- /dev/null +++ b/prdoc/pr_5838.prdoc @@ -0,0 +1,20 @@ +# 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: enable wasm builder diagnostics propagation + +doc: + - audience: Runtime Dev + description: | + `substrate-wasm-builder` is used as a build dependency by crates that implement FRAME runtimes. + Errors that occur in these crates can not be detected by IDEs that use rust-analyzer as a language + server because rust-analyzer needs the errors to be reported as diagnostic message in json format to + be able to publish them to language server clients. This PR adds `WASM_BUILD_CARGO_ARGS` environment + variable, which can hold a space separated list of args that will be parsed and passed to the `cargo` + command that it is used for building against wasm target. It can be used for the stated initial case, + but it is also flexible enough to allow passing other arguments or formatting the messages using another + available type. +crates: + - name: substrate-wasm-builder + bump: patch + diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index 15a1fd007ca..8f0e8a23e54 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -39,6 +39,7 @@ frame-metadata = { features = ["current"], optional = true, workspace = true, de codec = { optional = true, workspace = true, default-features = true } array-bytes = { optional = true, workspace = true, default-features = true } sp-tracing = { optional = true, workspace = true, default-features = true } +shlex = { workspace = true } [features] # Enable support for generating the metadata hash. diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 07de4c15831..e3f2ff5cd73 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -84,6 +84,9 @@ //! - `WASM_BUILD_STD` - Sets whether the Rust's standard library crates will also be built. This is //! necessary to make sure the standard library crates only use the exact WASM feature set that //! our executor supports. Enabled by default. +//! - `WASM_BUILD_CARGO_ARGS` - This can take a string as space separated list of `cargo` arguments. +//! It was added specifically for the use case of enabling JSON diagnostic messages during the +//! build phase, to be used by IDEs that parse them, but it might be useful for other cases too. //! - `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to //! prevent network access. Useful in offline environments. //! @@ -161,6 +164,10 @@ 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"; +/// Environment variable to set additional cargo arguments that might be useful +/// during the build phase. +const WASM_BUILD_CARGO_ARGS: &str = "WASM_BUILD_CARGO_ARGS"; + /// The target to use for the runtime. Valid values are `wasm` (default) or `riscv`. const RUNTIME_TARGET: &str = "SUBSTRATE_RUNTIME_TARGET"; diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index dcd5e6f1ade..26edd2ea1f2 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -869,6 +869,18 @@ fn build_bloaty_blob( // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); + let cargo_args = env::var(crate::WASM_BUILD_CARGO_ARGS).unwrap_or_default(); + if !cargo_args.is_empty() { + let Some(args) = shlex::split(&cargo_args) else { + build_helper::warning(format!( + "the {} environment variable is not a valid shell string", + crate::WASM_BUILD_CARGO_ARGS + )); + std::process::exit(1); + }; + build_cmd.args(args); + } + #[cfg(feature = "metadata-hash")] if let Some(hash) = metadata_hash { build_cmd.env("RUNTIME_METADATA_HASH", array_bytes::bytes2hex("0x", &hash)); @@ -1140,6 +1152,7 @@ fn generate_rerun_if_changed_instructions( 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); + println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_CARGO_ARGS); } /// Track files and paths related to the given package to rerun `build.rs` on any relevant change. -- GitLab From 0a569963042a205d886acaee8a7f65a53c57bf1d Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Sat, 28 Sep 2024 11:53:55 +0200 Subject: [PATCH 021/127] Update runtimes-matrix.json (#5829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just a tiny config fix Co-authored-by: Bastian Köcher --- .github/workflows/runtimes-matrix.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index b0a307ab315..50fa44b1b07 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -52,7 +52,7 @@ { "name": "bridge-hub-rococo", "package": "bridge-hub-rococo-runtime", - "path": "cumulus/parachains/runtimes/bridges/bridge-hub-rococo", + "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo", "header": "cumulus/file_header.txt", "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", @@ -62,7 +62,7 @@ { "name": "bridge-hub-westend", "package": "bridge-hub-rococo-runtime", - "path": "cumulus/parachains/runtimes/bridges/bridge-hub-westend", + "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend", "header": "cumulus/file_header.txt", "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", -- GitLab From df12fd34e36848a535892b1e88281faa59bf34b6 Mon Sep 17 00:00:00 2001 From: Facundo Farall <37149322+ffarall@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:43:38 -0400 Subject: [PATCH 022/127] Clarify firing of `import_notification_stream` in doc comment (#5811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Updates the doc comment on the `import_notification_stream` to make its behaviour clearer. Closes [Unexpected behaviour of block `import_notification_stream`](https://github.com/paritytech/polkadot-sdk/issues/5596). ## Integration Doesn't apply. ## Review Notes The old comment docs caused some confusion to myself and some members of my team, on when this notification stream is triggered. This is reflected in the linked [issue](https://github.com/paritytech/polkadot-sdk/issues/5596), and as discussed there, this PR aims to prevent this confusion in future devs looking to make use of this functionality. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [x] I have made corresponding changes to the documentation (if applicable) * [x] I have added tests that prove my fix is effective or that my feature works (if applicable) You can remove the "Checklist" section once all have been checked. Thank you for your contribution! --------- Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: Bastian Köcher --- prdoc/pr_5811.prdoc | 13 +++++++++++++ substrate/client/api/src/client.rs | 13 ++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_5811.prdoc diff --git a/prdoc/pr_5811.prdoc b/prdoc/pr_5811.prdoc new file mode 100644 index 00000000000..103fef4bb8b --- /dev/null +++ b/prdoc/pr_5811.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: "Improve `import_notification_stream` documentation" + +doc: + - audience: Node Dev + description: | + "Updates the doc comment on the `import_notification_stream` to make its behaviour clearer. Now it specifically states that this notification stream is fired on every import notification after the initial sync, and only when there are re-orgs in the initial sync." + +crates: + - name: sc-client-api + bump: patch diff --git a/substrate/client/api/src/client.rs b/substrate/client/api/src/client.rs index 45cfafb2584..764930984ed 100644 --- a/substrate/client/api/src/client.rs +++ b/substrate/client/api/src/client.rs @@ -65,9 +65,16 @@ pub trait BlockOf { pub trait BlockchainEvents { /// Get block import event stream. /// - /// Not guaranteed to be fired for every imported block, only fired when the node - /// has synced to the tip or there is a re-org. Use `every_import_notification_stream()` - /// if you want a notification of every imported block regardless. + /// Not guaranteed to be fired for every imported block. Use + /// `every_import_notification_stream()` if you want a notification of every imported block + /// regardless. + /// + /// The events for this notification stream are emitted: + /// - During initial sync process: if there is a re-org while importing blocks. See + /// [here](https://github.com/paritytech/substrate/pull/7118#issuecomment-694091901) for the + /// rationale behind this. + /// - After initial sync process: on every imported block, regardless of whether it is + /// the new best block or not, causes a re-org or not. fn import_notification_stream(&self) -> ImportNotifications; /// Get a stream of every imported block. -- GitLab From 05b5fb2bd4ba587f5c478f39eb82906691d8ab06 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sun, 29 Sep 2024 19:20:56 -0400 Subject: [PATCH 023/127] Improve APIs for Tries in Runtime (#5756) This is a refactor and improvement from: https://github.com/paritytech/polkadot-sdk/pull/3881 - `sp_runtime::proving_trie` now exposes a `BasicProvingTrie` for both `base2` and `base16`. - APIs for `base16` are more focused on single value proofs, also aligning their APIs with the `base2` trie - A `ProvingTrie` trait is included which wraps both the `base2` and `base16` trie, and exposes all APIs needed for an end to end scenario. - A `ProofToHashes` trait is exposed which can allow us to write proper benchmarks for the merkle trie. --------- Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> Co-authored-by: Adrian Catangiu --- Cargo.lock | 1 + prdoc/pr_5756.prdoc | 16 + substrate/frame/support/src/traits/proving.rs | 124 +++++- substrate/primitives/runtime/Cargo.toml | 2 + .../primitives/runtime/src/proving_trie.rs | 391 ------------------ .../runtime/src/proving_trie/base16.rs | 327 +++++++++++++++ .../runtime/src/proving_trie/base2.rs | 288 +++++++++++++ .../runtime/src/proving_trie/mod.rs | 187 +++++++++ 8 files changed, 930 insertions(+), 406 deletions(-) create mode 100644 prdoc/pr_5756.prdoc delete mode 100644 substrate/primitives/runtime/src/proving_trie.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/base16.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/base2.rs create mode 100644 substrate/primitives/runtime/src/proving_trie/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c10d1e93907..97bdc935135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22523,6 +22523,7 @@ dependencies = [ name = "sp-runtime" version = "31.0.1" dependencies = [ + "binary-merkle-tree", "docify", "either", "hash256-std-hasher", diff --git a/prdoc/pr_5756.prdoc b/prdoc/pr_5756.prdoc new file mode 100644 index 00000000000..525f955d3ac --- /dev/null +++ b/prdoc/pr_5756.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: Improve APIs for Tries in Runtime + +doc: + - audience: Runtime Dev + description: | + This PR introduces a trait `ProvingTrie` which has all the function you need to use tries in the runtime. + This trait includes the ability to create, query, and prove data in a trie. Another trait `ProofToHashes` + allows developers to express the computational complexity of proof verification using the proof data. +crates: + - name: sp-runtime + bump: major + - name: frame-support + bump: major diff --git a/substrate/frame/support/src/traits/proving.rs b/substrate/frame/support/src/traits/proving.rs index dc44f4cd68e..84e37bde38d 100644 --- a/substrate/frame/support/src/traits/proving.rs +++ b/substrate/frame/support/src/traits/proving.rs @@ -20,6 +20,10 @@ use alloc::vec::Vec; use codec::{Decode, Encode}; use sp_core::Hasher; +use sp_runtime::DispatchError; + +// Re-export the `proving_trie` types and traits. +pub use sp_runtime::proving_trie::*; /// Something that can verify the existence of some data in a given proof. pub trait VerifyExistenceProof { @@ -31,7 +35,7 @@ pub trait VerifyExistenceProof { /// Verify the given `proof`. /// /// Ensures that the `proof` was build for `root` and returns the proved data. - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()>; + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError>; } /// Implements [`VerifyExistenceProof`] using a binary merkle tree. @@ -44,9 +48,9 @@ where type Proof = binary_merkle_tree::MerkleProof>; type Hash = H::Out; - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError> { if proof.root != *root { - return Err(()); + return Err(TrieError::RootMismatch.into()); } if binary_merkle_tree::verify_proof::( @@ -58,13 +62,25 @@ where ) { Ok(proof.leaf) } else { - Err(()) + Err(TrieError::IncompleteProof.into()) } } } +impl ProofToHashes for BinaryMerkleTreeProver { + type Proof = binary_merkle_tree::MerkleProof>; + + // This base 2 merkle trie includes a `proof` field which is a `Vec`. + // The length of this vector tells us the depth of the proof, and how many + // hashes we need to calculate. + fn proof_to_hashes(proof: &Self::Proof) -> Result { + let depth = proof.proof.len(); + Ok(depth as u32) + } +} + /// Proof used by [`SixteenPatriciaMerkleTreeProver`] for [`VerifyExistenceProof`]. -#[derive(Encode, Decode)] +#[derive(Encode, Decode, Clone)] pub struct SixteenPatriciaMerkleTreeExistenceProof { /// The key of the value to prove. pub key: Vec, @@ -81,21 +97,35 @@ impl VerifyExistenceProof for SixteenPatriciaMerkleTreeProver { type Proof = SixteenPatriciaMerkleTreeExistenceProof; type Hash = H::Out; - fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, ()> { + fn verify_proof(proof: Self::Proof, root: &Self::Hash) -> Result, DispatchError> { sp_trie::verify_trie_proof::, _, _, _>( &root, &proof.proof, [&(&proof.key, Some(&proof.value))], ) - .map_err(drop) + .map_err(|err| TrieError::from(err).into()) .map(|_| proof.value) } } +impl ProofToHashes for SixteenPatriciaMerkleTreeProver { + type Proof = SixteenPatriciaMerkleTreeExistenceProof; + + // This base 16 trie uses a raw proof of `Vec`, where the length of the first `Vec` + // is the depth of the trie. We can use this to predict the number of hashes. + fn proof_to_hashes(proof: &Self::Proof) -> Result { + let depth = proof.proof.len(); + Ok(depth as u32) + } +} + #[cfg(test)] mod tests { use super::*; - use sp_runtime::{proving_trie::BasicProvingTrie, traits::BlakeTwo256}; + use sp_runtime::{ + proving_trie::{base16::BasicProvingTrie, ProvingTrie}, + traits::BlakeTwo256, + }; #[test] fn verify_binary_merkle_tree_prover_works() { @@ -113,23 +143,87 @@ mod tests { #[test] fn verify_sixteen_patricia_merkle_tree_prover_works() { - let trie = BasicProvingTrie::::generate_for(vec![ - (0u32, &b"hey"[..]), - (1u32, &b"yes"[..]), + let trie = BasicProvingTrie::::generate_for(vec![ + (0u32, String::from("hey")), + (1u32, String::from("yes")), ]) .unwrap(); - let proof = trie.create_single_value_proof(1u32).unwrap(); + let proof = trie.create_proof(&1u32).unwrap(); + let structured_proof: Vec> = Decode::decode(&mut &proof[..]).unwrap(); let root = *trie.root(); let proof = SixteenPatriciaMerkleTreeExistenceProof { key: 1u32.encode(), - value: b"yes"[..].encode(), - proof, + value: String::from("yes").encode(), + proof: structured_proof, }; assert_eq!( SixteenPatriciaMerkleTreeProver::::verify_proof(proof, &root).unwrap(), - b"yes"[..].encode() + String::from("yes").encode() ); } + + #[test] + fn proof_to_hashes_sixteen() { + let mut i: u32 = 1; + + // Compute log base 16 and round up + let log16 = |x: u32| -> u32 { + let x_f64 = x as f64; + let log16_x = (x_f64.ln() / 16_f64.ln()).ceil(); + log16_x as u32 + }; + + while i < 10_000_000 { + let trie = BasicProvingTrie::::generate_for( + (0..i).map(|i| (i, u128::from(i))), + ) + .unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let structured_proof: Vec> = Decode::decode(&mut &proof[..]).unwrap(); + let root = *trie.root(); + + let proof = SixteenPatriciaMerkleTreeExistenceProof { + key: 0u32.encode(), + value: 0u128.encode(), + proof: structured_proof, + }; + let hashes = + SixteenPatriciaMerkleTreeProver::::proof_to_hashes(&proof).unwrap(); + let log16 = log16(i).max(1); + assert_eq!(hashes, log16); + + assert_eq!( + SixteenPatriciaMerkleTreeProver::::verify_proof(proof.clone(), &root) + .unwrap(), + proof.value + ); + + i = i * 10; + } + } + + #[test] + fn proof_to_hashes_binary() { + let mut i: u32 = 1; + while i < 10_000_000 { + let proof = binary_merkle_tree::merkle_proof::( + (0..i).map(|i| u128::from(i).encode()), + 0, + ); + let root = proof.root; + + let hashes = BinaryMerkleTreeProver::::proof_to_hashes(&proof).unwrap(); + let log2 = (i as f64).log2().ceil() as u32; + assert_eq!(hashes, log2); + + assert_eq!( + BinaryMerkleTreeProver::::verify_proof(proof, &root).unwrap(), + 0u128.encode() + ); + + i = i * 10; + } + } } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 800bf4bd073..c3413775b1c 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -36,6 +36,7 @@ sp-trie = { workspace = true } sp-weights = { workspace = true } docify = { workspace = true } tracing = { workspace = true, features = ["log"], default-features = false } +binary-merkle-tree = { workspace = true } simple-mermaid = { version = "0.1.1", optional = true } @@ -53,6 +54,7 @@ runtime-benchmarks = [] try-runtime = [] default = ["std"] std = [ + "binary-merkle-tree/std", "codec/std", "either/use_std", "hash256-std-hasher/std", diff --git a/substrate/primitives/runtime/src/proving_trie.rs b/substrate/primitives/runtime/src/proving_trie.rs deleted file mode 100644 index 9a423f18284..00000000000 --- a/substrate/primitives/runtime/src/proving_trie.rs +++ /dev/null @@ -1,391 +0,0 @@ -// 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 for a compact base-16 merkle trie used for checking and generating proofs within the -//! runtime. The `sp-trie` crate exposes all of these same functionality (and more), but this -//! library is designed to work more easily with runtime native types, which simply need to -//! implement `Encode`/`Decode`. It also exposes a runtime friendly `TrieError` type which can be -//! use inside of a FRAME Pallet. -//! -//! Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible with -//! proofs using `LayoutV0`. - -use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; -#[cfg(feature = "serde")] -use crate::{Deserialize, Serialize}; - -use sp_std::vec::Vec; -use sp_trie::{ - trie_types::{TrieDBBuilder, TrieDBMutBuilderV1, TrieError as SpTrieError}, - LayoutV1, MemoryDB, Trie, TrieMut, VerifyError, -}; - -type HashOf = ::Out; - -/// A runtime friendly error type for tries. -#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TrieError { - /* From TrieError */ - /// Attempted to create a trie with a state root not in the DB. - InvalidStateRoot, - /// Trie item not found in the database, - IncompleteDatabase, - /// A value was found in the trie with a nibble key that was not byte-aligned. - ValueAtIncompleteKey, - /// Corrupt Trie item. - DecoderError, - /// Hash is not value. - InvalidHash, - /* From VerifyError */ - /// The statement being verified contains multiple key-value pairs with the same key. - DuplicateKey, - /// The proof contains at least one extraneous node. - ExtraneousNode, - /// The proof contains at least one extraneous value which should have been omitted from the - /// proof. - ExtraneousValue, - /// The proof contains at least one extraneous hash reference the should have been omitted. - ExtraneousHashReference, - /// The proof contains an invalid child reference that exceeds the hash length. - InvalidChildReference, - /// The proof indicates that an expected value was not found in the trie. - ValueMismatch, - /// The proof is missing trie nodes required to verify. - IncompleteProof, - /// The root hash computed from the proof is incorrect. - RootMismatch, - /// One of the proof nodes could not be decoded. - DecodeError, -} - -impl From> for TrieError { - fn from(error: SpTrieError) -> Self { - match error { - SpTrieError::InvalidStateRoot(..) => Self::InvalidStateRoot, - SpTrieError::IncompleteDatabase(..) => Self::IncompleteDatabase, - SpTrieError::ValueAtIncompleteKey(..) => Self::ValueAtIncompleteKey, - SpTrieError::DecoderError(..) => Self::DecoderError, - SpTrieError::InvalidHash(..) => Self::InvalidHash, - } - } -} - -impl From> for TrieError { - fn from(error: VerifyError) -> Self { - match error { - VerifyError::DuplicateKey(..) => Self::DuplicateKey, - VerifyError::ExtraneousNode => Self::ExtraneousNode, - VerifyError::ExtraneousValue(..) => Self::ExtraneousValue, - VerifyError::ExtraneousHashReference(..) => Self::ExtraneousHashReference, - VerifyError::InvalidChildReference(..) => Self::InvalidChildReference, - VerifyError::ValueMismatch(..) => Self::ValueMismatch, - VerifyError::IncompleteProof => Self::IncompleteProof, - VerifyError::RootMismatch(..) => Self::RootMismatch, - VerifyError::DecodeError(..) => Self::DecodeError, - } - } -} - -impl From for &'static str { - fn from(e: TrieError) -> &'static str { - match e { - TrieError::InvalidStateRoot => "The state root is not in the database.", - TrieError::IncompleteDatabase => "A trie item was not found in the database.", - TrieError::ValueAtIncompleteKey => - "A value was found with a key that is not byte-aligned.", - TrieError::DecoderError => "A corrupt trie item was encountered.", - TrieError::InvalidHash => "The hash does not match the expected value.", - TrieError::DuplicateKey => "The proof contains duplicate keys.", - TrieError::ExtraneousNode => "The proof contains extraneous nodes.", - TrieError::ExtraneousValue => "The proof contains extraneous values.", - TrieError::ExtraneousHashReference => "The proof contains extraneous hash references.", - TrieError::InvalidChildReference => "The proof contains an invalid child reference.", - TrieError::ValueMismatch => "The proof indicates a value mismatch.", - TrieError::IncompleteProof => "The proof is incomplete.", - TrieError::RootMismatch => "The root hash computed from the proof is incorrect.", - TrieError::DecodeError => "One of the proof nodes could not be decoded.", - } - } -} - -/// A helper structure for building a basic base-16 merkle trie and creating compact proofs for that -/// trie. Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible -/// with proofs using `LayoutV0`. -pub struct BasicProvingTrie -where - Hashing: sp_core::Hasher, -{ - db: MemoryDB, - root: HashOf, - _phantom: core::marker::PhantomData<(Key, Value)>, -} - -impl BasicProvingTrie -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. - pub fn generate_for(items: I) -> Result - where - I: IntoIterator, - { - let mut db = MemoryDB::default(); - let mut root = Default::default(); - - { - let mut trie = TrieDBMutBuilderV1::new(&mut db, &mut root).build(); - for (key, value) in items.into_iter() { - key.using_encoded(|k| value.using_encoded(|v| trie.insert(k, v))) - .map_err(|_| "failed to insert into trie")?; - } - } - - Ok(Self { db, root, _phantom: Default::default() }) - } - - /// Access the underlying trie root. - pub fn root(&self) -> &HashOf { - &self.root - } - - /// Query a value contained within the current trie. Returns `None` if the - /// nodes within the current `MemoryDB` are insufficient to query the item. - pub fn query(&self, key: Key) -> Option - where - Value: Decode, - { - let trie = TrieDBBuilder::new(&self.db, &self.root).build(); - key.using_encoded(|s| trie.get(s)) - .ok()? - .and_then(|raw| Value::decode(&mut &*raw).ok()) - } - - /// Create a compact merkle proof needed to prove all `keys` and their values are in the trie. - /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a - /// proof. - /// - /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not - /// compatible with `LayoutV0`. - /// - /// When verifying the proof created by this function, you must include all of the keys and - /// values of the proof, else the verifier will complain that extra nodes are provided in the - /// proof that are not needed. - pub fn create_proof(&self, keys: &[Key]) -> Result>, DispatchError> { - sp_trie::generate_trie_proof::, _, _, _>( - &self.db, - self.root, - &keys.into_iter().map(|k| k.encode()).collect::>>(), - ) - .map_err(|err| TrieError::from(*err).into()) - } - - /// Create a compact merkle proof needed to prove a single key and its value are in the trie. - /// Returns `None` if the nodes within the current `MemoryDB` are insufficient to create a - /// proof. - /// - /// This function makes a proof with latest substrate trie format (`LayoutV1`), and is not - /// compatible with `LayoutV0`. - pub fn create_single_value_proof(&self, key: Key) -> Result>, DispatchError> { - self.create_proof(&[key]) - } -} - -/// Verify the existence or non-existence of `key` and `value` in a given trie root and proof. -/// -/// Proofs must be created with latest substrate trie format (`LayoutV1`). -pub fn verify_single_value_proof( - root: HashOf, - proof: &[Vec], - key: Key, - maybe_value: Option, -) -> Result<(), DispatchError> -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - sp_trie::verify_trie_proof::, _, _, _>( - &root, - proof, - &[(key.encode(), maybe_value.map(|value| value.encode()))], - ) - .map_err(|err| TrieError::from(err).into()) -} - -/// Verify the existence or non-existence of multiple `items` in a given trie root and proof. -/// -/// Proofs must be created with latest substrate trie format (`LayoutV1`). -pub fn verify_proof( - root: HashOf, - proof: &[Vec], - items: &[(Key, Option)], -) -> Result<(), DispatchError> -where - Hashing: sp_core::Hasher, - Key: Encode, - Value: Encode, -{ - let items_encoded = items - .into_iter() - .map(|(key, maybe_value)| (key.encode(), maybe_value.as_ref().map(|value| value.encode()))) - .collect::, Option>)>>(); - - sp_trie::verify_trie_proof::, _, _, _>(&root, proof, &items_encoded) - .map_err(|err| TrieError::from(err).into()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::traits::BlakeTwo256; - use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; - - // A trie which simulates a trie of accounts (u32) and balances (u128). - type BalanceTrie = BasicProvingTrie; - - // The expected root hash for an empty trie. - fn empty_root() -> H256 { - sp_trie::empty_trie_root::>() - } - - fn create_balance_trie() -> BalanceTrie { - // Create a map of users and their balances. - let mut map = BTreeMap::::new(); - for i in 0..100u32 { - map.insert(i, i.into()); - } - - // Put items into the trie. - let balance_trie = BalanceTrie::generate_for(map).unwrap(); - - // Root is changed. - let root = *balance_trie.root(); - assert!(root != empty_root()); - - // Assert valid keys are queryable. - assert_eq!(balance_trie.query(6u32), Some(6u128)); - assert_eq!(balance_trie.query(9u32), Some(9u128)); - assert_eq!(balance_trie.query(69u32), Some(69u128)); - // Invalid key returns none. - assert_eq!(balance_trie.query(6969u32), None); - - balance_trie - } - - #[test] - fn empty_trie_works() { - let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); - assert_eq!(*empty_trie.root(), empty_root()); - } - - #[test] - fn basic_end_to_end_single_value() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid key. - let proof = balance_trie.create_single_value_proof(6u32).unwrap(); - - // Assert key is provable, all other keys are invalid. - for i in 0..200u32 { - if i == 6 { - assert_eq!( - verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i)) - ), - Ok(()) - ); - // Wrong value is invalid. - assert_eq!( - verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i + 1)) - ), - Err(TrieError::RootMismatch.into()) - ); - } else { - assert!(verify_single_value_proof::( - root, - &proof, - i, - Some(u128::from(i)) - ) - .is_err()); - assert!(verify_single_value_proof::( - root, - &proof, - i, - None:: - ) - .is_err()); - } - } - } - - #[test] - fn basic_end_to_end_multi_value() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid and invalid key. - let proof = balance_trie.create_proof(&[6u32, 69u32, 6969u32]).unwrap(); - let items = [(6u32, Some(6u128)), (69u32, Some(69u128)), (6969u32, None)]; - - assert_eq!(verify_proof::(root, &proof, &items), Ok(())); - } - - #[test] - fn proof_fails_with_bad_data() { - let balance_trie = create_balance_trie(); - let root = *balance_trie.root(); - - // Create a proof for a valid key. - let proof = balance_trie.create_single_value_proof(6u32).unwrap(); - - // Correct data verifies successfully - assert_eq!( - verify_single_value_proof::(root, &proof, 6u32, Some(6u128)), - Ok(()) - ); - - // Fail to verify proof with wrong root - assert_eq!( - verify_single_value_proof::( - Default::default(), - &proof, - 6u32, - Some(6u128) - ), - Err(TrieError::RootMismatch.into()) - ); - - // Fail to verify proof with wrong data - assert_eq!( - verify_single_value_proof::(root, &[], 6u32, Some(6u128)), - Err(TrieError::IncompleteProof.into()) - ); - } -} diff --git a/substrate/primitives/runtime/src/proving_trie/base16.rs b/substrate/primitives/runtime/src/proving_trie/base16.rs new file mode 100644 index 00000000000..da05c551c6d --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/base16.rs @@ -0,0 +1,327 @@ +// 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 for a compact base-16 merkle trie used for checking and generating proofs within the +//! runtime. The `sp-trie` crate exposes all of these same functionality (and more), but this +//! library is designed to work more easily with runtime native types, which simply need to +//! implement `Encode`/`Decode`. It also exposes a runtime friendly `TrieError` type which can be +//! use inside of a FRAME Pallet. +//! +//! Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible with +//! proofs using `LayoutV0`. + +use super::{ProofToHashes, ProvingTrie, TrieError}; +use crate::{Decode, DispatchError, Encode}; +use codec::MaxEncodedLen; +use sp_std::vec::Vec; +use sp_trie::{ + trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, + LayoutV1, MemoryDB, Trie, TrieMut, +}; + +/// A helper structure for building a basic base-16 merkle trie and creating compact proofs for that +/// trie. Proofs are created with latest substrate trie format (`LayoutV1`), and are not compatible +/// with proofs using `LayoutV0`. +pub struct BasicProvingTrie +where + Hashing: sp_core::Hasher, +{ + db: MemoryDB, + root: Hashing::Out, + _phantom: core::marker::PhantomData<(Key, Value)>, +} + +impl BasicProvingTrie +where + Hashing: sp_core::Hasher, + Key: Encode, +{ + /// Create a compact merkle proof needed to prove all `keys` and their values are in the trie. + /// + /// When verifying the proof created by this function, you must include all of the keys and + /// values of the proof, else the verifier will complain that extra nodes are provided in the + /// proof that are not needed. + pub fn create_multi_proof(&self, keys: &[Key]) -> Result, DispatchError> { + sp_trie::generate_trie_proof::, _, _, _>( + &self.db, + self.root, + &keys.into_iter().map(|k| k.encode()).collect::>>(), + ) + .map_err(|err| TrieError::from(*err).into()) + .map(|structured_proof| structured_proof.encode()) + } +} + +impl ProvingTrie for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode + Decode, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator, + { + let mut db = MemoryDB::default(); + let mut root = Default::default(); + + { + let mut trie = TrieDBMutBuilderV1::new(&mut db, &mut root).build(); + for (key, value) in items.into_iter() { + key.using_encoded(|k| value.using_encoded(|v| trie.insert(k, v))) + .map_err(|_| "failed to insert into trie")?; + } + } + + Ok(Self { db, root, _phantom: Default::default() }) + } + + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out { + &self.root + } + + /// Query a value contained within the current trie. Returns `None` if the + /// nodes within the current `MemoryDB` are insufficient to query the item. + fn query(&self, key: &Key) -> Option { + let trie = TrieDBBuilder::new(&self.db, &self.root).build(); + key.using_encoded(|s| trie.get(s)) + .ok()? + .and_then(|raw| Value::decode(&mut &*raw).ok()) + } + + /// Create a compact merkle proof needed to prove a single key and its value are in the trie. + fn create_proof(&self, key: &Key) -> Result, DispatchError> { + sp_trie::generate_trie_proof::, _, _, _>( + &self.db, + self.root, + &[key.encode()], + ) + .map_err(|err| TrieError::from(*err).into()) + .map(|structured_proof| structured_proof.encode()) + } + + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError> { + verify_proof::(root, proof, key, value) + } +} + +impl ProofToHashes for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: MaxEncodedLen, +{ + // Our proof is just raw bytes. + type Proof = [u8]; + // This base 16 trie uses a raw proof of `Vec`, where the length of the first `Vec` + // is the depth of the trie. We can use this to predict the number of hashes. + fn proof_to_hashes(proof: &[u8]) -> Result { + use codec::DecodeLength; + let depth = + > as DecodeLength>::len(proof).map_err(|_| TrieError::DecodeError)?; + Ok(depth as u32) + } +} + +/// Verify the existence of `key` and `value` in a given trie root and proof. +pub fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + let structured_proof: Vec> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::DecodeError)?; + sp_trie::verify_trie_proof::, _, _, _>( + &root, + &structured_proof, + &[(key.encode(), Some(value.encode()))], + ) + .map_err(|err| TrieError::from(err).into()) +} + +/// Verify the existence of multiple `items` in a given trie root and proof. +pub fn verify_multi_proof( + root: &Hashing::Out, + proof: &[u8], + items: &[(Key, Value)], +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Key: Encode, + Value: Encode, +{ + let structured_proof: Vec> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::DecodeError)?; + let items_encoded = items + .into_iter() + .map(|(key, value)| (key.encode(), Some(value.encode()))) + .collect::, Option>)>>(); + + sp_trie::verify_trie_proof::, _, _, _>( + &root, + &structured_proof, + &items_encoded, + ) + .map_err(|err| TrieError::from(err).into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + use sp_core::H256; + use sp_std::collections::btree_map::BTreeMap; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie = BasicProvingTrie; + + // The expected root hash for an empty trie. + fn empty_root() -> H256 { + sp_trie::empty_trie_root::>() + } + + fn create_balance_trie() -> BalanceTrie { + // Create a map of users and their balances. + let mut map = BTreeMap::::new(); + for i in 0..100u32 { + map.insert(i, i.into()); + } + + // Put items into the trie. + let balance_trie = BalanceTrie::generate_for(map).unwrap(); + + // Root is changed. + let root = *balance_trie.root(); + assert!(root != empty_root()); + + // Assert valid keys are queryable. + assert_eq!(balance_trie.query(&6u32), Some(6u128)); + assert_eq!(balance_trie.query(&9u32), Some(9u128)); + assert_eq!(balance_trie.query(&69u32), Some(69u128)); + // Invalid key returns none. + assert_eq!(balance_trie.query(&6969u32), None); + + balance_trie + } + + #[test] + fn empty_trie_works() { + let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); + assert_eq!(*empty_trie.root(), empty_root()); + } + + #[test] + fn basic_end_to_end_single_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Assert key is provable, all other keys are invalid. + for i in 0..200u32 { + if i == 6 { + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i)), + Ok(()) + ); + // Wrong value is invalid. + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i + 1)), + Err(TrieError::RootMismatch.into()) + ); + } else { + assert!( + verify_proof::(&root, &proof, &i, &u128::from(i)).is_err() + ); + } + } + } + + #[test] + fn basic_end_to_end_multi() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid and invalid key. + let proof = balance_trie.create_multi_proof(&[6u32, 9u32, 69u32]).unwrap(); + let items = [(6u32, 6u128), (9u32, 9u128), (69u32, 69u128)]; + + assert_eq!(verify_multi_proof::(&root, &proof, &items), Ok(())); + } + + #[test] + fn proof_fails_with_bad_data() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Correct data verifies successfully + assert_eq!(verify_proof::(&root, &proof, &6u32, &6u128), Ok(())); + + // Fail to verify proof with wrong root + assert_eq!( + verify_proof::(&Default::default(), &proof, &6u32, &6u128), + Err(TrieError::RootMismatch.into()) + ); + + // Crete a bad proof. + let bad_proof = balance_trie.create_proof(&99u32).unwrap(); + + // Fail to verify data with the wrong proof + assert_eq!( + verify_proof::(&root, &bad_proof, &6u32, &6u128), + Err(TrieError::ExtraneousHashReference.into()) + ); + } + + #[test] + fn proof_to_hashes() { + let mut i: u32 = 1; + // Compute log base 16 and round up + let log16 = |x: u32| -> u32 { + let x_f64 = x as f64; + let log16_x = (x_f64.ln() / 16_f64.ln()).ceil(); + log16_x as u32 + }; + + while i < 10_000_000 { + let trie = BalanceTrie::generate_for((0..i).map(|i| (i, u128::from(i)))).unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let hashes = BalanceTrie::proof_to_hashes(&proof).unwrap(); + let log16 = log16(i).max(1); + + assert_eq!(hashes, log16); + i = i * 10; + } + } +} diff --git a/substrate/primitives/runtime/src/proving_trie/base2.rs b/substrate/primitives/runtime/src/proving_trie/base2.rs new file mode 100644 index 00000000000..2b14a59ab05 --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/base2.rs @@ -0,0 +1,288 @@ +// 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 for a base-2 merkle tree used for checking and generating proofs within the +//! runtime. The `binary-merkle-tree` crate exposes all of these same functionality (and more), but +//! this library is designed to work more easily with runtime native types, which simply need to +//! implement `Encode`/`Decode`. + +use super::{ProofToHashes, ProvingTrie, TrieError}; +use crate::{Decode, DispatchError, Encode}; +use binary_merkle_tree::{merkle_proof, merkle_root, MerkleProof}; +use codec::MaxEncodedLen; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +/// A helper structure for building a basic base-2 merkle trie and creating compact proofs for that +/// trie. +pub struct BasicProvingTrie +where + Hashing: sp_core::Hasher, +{ + db: BTreeMap, + root: Hashing::Out, + _phantom: core::marker::PhantomData<(Key, Value)>, +} + +impl ProvingTrie for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: Encode + Decode, + Key: Encode + Decode + Ord, + Value: Encode + Decode + Clone, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator, + { + let mut db = BTreeMap::default(); + for (key, value) in items.into_iter() { + db.insert(key, value); + } + let root = merkle_root::(db.iter().map(|item| item.encode())); + Ok(Self { db, root, _phantom: Default::default() }) + } + + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out { + &self.root + } + + /// Query a value contained within the current trie. Returns `None` if the + /// nodes within the current `db` are insufficient to query the item. + fn query(&self, key: &Key) -> Option { + self.db.get(&key).cloned() + } + + /// Create a compact merkle proof needed to prove a single key and its value are in the trie. + /// Returns an error if the nodes within the current `db` are insufficient to create a proof. + fn create_proof(&self, key: &Key) -> Result, DispatchError> { + let mut encoded = Vec::with_capacity(self.db.len()); + let mut found_index = None; + + // Find the index of our key, and encode the (key, value) pair. + for (i, (k, v)) in self.db.iter().enumerate() { + // If we found the key we are looking for, save it. + if k == key { + found_index = Some(i); + } + + encoded.push((k, v).encode()); + } + + let index = found_index.ok_or(TrieError::IncompleteDatabase)?; + let proof = merkle_proof::>, Vec>(encoded, index as u32); + Ok(proof.encode()) + } + + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError> { + verify_proof::(root, proof, key, value) + } +} + +impl ProofToHashes for BasicProvingTrie +where + Hashing: sp_core::Hasher, + Hashing::Out: MaxEncodedLen + Decode, + Key: Decode, + Value: Decode, +{ + // Our proof is just raw bytes. + type Proof = [u8]; + // This base 2 merkle trie includes a `proof` field which is a `Vec`. + // The length of this vector tells us the depth of the proof, and how many + // hashes we need to calculate. + fn proof_to_hashes(proof: &[u8]) -> Result { + let decoded_proof: MerkleProof> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::IncompleteProof)?; + let depth = decoded_proof.proof.len(); + Ok(depth as u32) + } +} + +/// Verify the existence of `key` and `value` in a given trie root and proof. +pub fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, +) -> Result<(), DispatchError> +where + Hashing: sp_core::Hasher, + Hashing::Out: Decode, + Key: Encode + Decode, + Value: Encode + Decode, +{ + let decoded_proof: MerkleProof> = + Decode::decode(&mut &proof[..]).map_err(|_| TrieError::IncompleteProof)?; + if *root != decoded_proof.root { + return Err(TrieError::RootMismatch.into()); + } + + if (key, value).encode() != decoded_proof.leaf { + return Err(TrieError::ValueMismatch.into()); + } + + if binary_merkle_tree::verify_proof::( + &decoded_proof.root, + decoded_proof.proof, + decoded_proof.number_of_leaves, + decoded_proof.leaf_index, + &decoded_proof.leaf, + ) { + Ok(()) + } else { + Err(TrieError::IncompleteProof.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + use sp_core::H256; + use sp_std::collections::btree_map::BTreeMap; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie = BasicProvingTrie; + + // The expected root hash for an empty trie. + fn empty_root() -> H256 { + let tree = BalanceTrie::generate_for(Vec::new()).unwrap(); + *tree.root() + } + + fn create_balance_trie() -> BalanceTrie { + // Create a map of users and their balances. + let mut map = BTreeMap::::new(); + for i in 0..100u32 { + map.insert(i, i.into()); + } + + // Put items into the trie. + let balance_trie = BalanceTrie::generate_for(map).unwrap(); + + // Root is changed. + let root = *balance_trie.root(); + assert!(root != empty_root()); + + // Assert valid keys are queryable. + assert_eq!(balance_trie.query(&6u32), Some(6u128)); + assert_eq!(balance_trie.query(&9u32), Some(9u128)); + assert_eq!(balance_trie.query(&69u32), Some(69u128)); + + balance_trie + } + + #[test] + fn empty_trie_works() { + let empty_trie = BalanceTrie::generate_for(Vec::new()).unwrap(); + assert_eq!(*empty_trie.root(), empty_root()); + } + + #[test] + fn basic_end_to_end_single_value() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Assert key is provable, all other keys are invalid. + for i in 0..200u32 { + if i == 6 { + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i)), + Ok(()) + ); + // Wrong value is invalid. + assert_eq!( + verify_proof::(&root, &proof, &i, &u128::from(i + 1)), + Err(TrieError::ValueMismatch.into()) + ); + } else { + assert!( + verify_proof::(&root, &proof, &i, &u128::from(i)).is_err() + ); + } + } + } + + #[test] + fn proof_fails_with_bad_data() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + + // Correct data verifies successfully + assert_eq!(verify_proof::(&root, &proof, &6u32, &6u128), Ok(())); + + // Fail to verify proof with wrong root + assert_eq!( + verify_proof::(&Default::default(), &proof, &6u32, &6u128), + Err(TrieError::RootMismatch.into()) + ); + + // Fail to verify proof with wrong data + assert_eq!( + verify_proof::(&root, &[], &6u32, &6u128), + Err(TrieError::IncompleteProof.into()) + ); + } + + // We make assumptions about the structure of the merkle proof in order to provide the + // `proof_to_hashes` function. This test keeps those assumptions checked. + #[test] + fn assert_structure_of_merkle_proof() { + let balance_trie = create_balance_trie(); + let root = *balance_trie.root(); + // Create a proof for a valid key. + let proof = balance_trie.create_proof(&6u32).unwrap(); + let decoded_proof: MerkleProof> = Decode::decode(&mut &proof[..]).unwrap(); + + let constructed_proof = MerkleProof::> { + root, + proof: decoded_proof.proof.clone(), + number_of_leaves: 100, + leaf_index: 6, + leaf: (6u32, 6u128).encode(), + }; + assert_eq!(constructed_proof, decoded_proof); + } + + #[test] + fn proof_to_hashes() { + let mut i: u32 = 1; + while i < 10_000_000 { + let trie = BalanceTrie::generate_for((0..i).map(|i| (i, u128::from(i)))).unwrap(); + let proof = trie.create_proof(&0).unwrap(); + let hashes = BalanceTrie::proof_to_hashes(&proof).unwrap(); + let log2 = (i as f64).log2().ceil() as u32; + + assert_eq!(hashes, log2); + i = i * 10; + } + } +} diff --git a/substrate/primitives/runtime/src/proving_trie/mod.rs b/substrate/primitives/runtime/src/proving_trie/mod.rs new file mode 100644 index 00000000000..009aa6d4935 --- /dev/null +++ b/substrate/primitives/runtime/src/proving_trie/mod.rs @@ -0,0 +1,187 @@ +// 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 for merkle tries compatible with the runtime. + +pub mod base16; +pub mod base2; + +use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; +#[cfg(feature = "serde")] +use crate::{Deserialize, Serialize}; +use sp_std::vec::Vec; +use sp_trie::{trie_types::TrieError as SpTrieError, VerifyError}; + +/// A runtime friendly error type for tries. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum TrieError { + /* From TrieError */ + /// Attempted to create a trie with a state root not in the DB. + InvalidStateRoot, + /// Trie item not found in the database, + IncompleteDatabase, + /// A value was found in the trie with a nibble key that was not byte-aligned. + ValueAtIncompleteKey, + /// Corrupt Trie item. + DecoderError, + /// Hash is not value. + InvalidHash, + /* From VerifyError */ + /// The statement being verified contains multiple key-value pairs with the same key. + DuplicateKey, + /// The proof contains at least one extraneous node. + ExtraneousNode, + /// The proof contains at least one extraneous value which should have been omitted from the + /// proof. + ExtraneousValue, + /// The proof contains at least one extraneous hash reference the should have been omitted. + ExtraneousHashReference, + /// The proof contains an invalid child reference that exceeds the hash length. + InvalidChildReference, + /// The proof indicates that an expected value was not found in the trie. + ValueMismatch, + /// The proof is missing trie nodes required to verify. + IncompleteProof, + /// The root hash computed from the proof is incorrect. + RootMismatch, + /// One of the proof nodes could not be decoded. + DecodeError, +} + +impl From> for TrieError { + fn from(error: SpTrieError) -> Self { + match error { + SpTrieError::InvalidStateRoot(..) => Self::InvalidStateRoot, + SpTrieError::IncompleteDatabase(..) => Self::IncompleteDatabase, + SpTrieError::ValueAtIncompleteKey(..) => Self::ValueAtIncompleteKey, + SpTrieError::DecoderError(..) => Self::DecoderError, + SpTrieError::InvalidHash(..) => Self::InvalidHash, + } + } +} + +impl From> for TrieError { + fn from(error: VerifyError) -> Self { + match error { + VerifyError::DuplicateKey(..) => Self::DuplicateKey, + VerifyError::ExtraneousNode => Self::ExtraneousNode, + VerifyError::ExtraneousValue(..) => Self::ExtraneousValue, + VerifyError::ExtraneousHashReference(..) => Self::ExtraneousHashReference, + VerifyError::InvalidChildReference(..) => Self::InvalidChildReference, + VerifyError::ValueMismatch(..) => Self::ValueMismatch, + VerifyError::IncompleteProof => Self::IncompleteProof, + VerifyError::RootMismatch(..) => Self::RootMismatch, + VerifyError::DecodeError(..) => Self::DecodeError, + } + } +} + +impl From for &'static str { + fn from(e: TrieError) -> &'static str { + match e { + TrieError::InvalidStateRoot => "The state root is not in the database.", + TrieError::IncompleteDatabase => "A trie item was not found in the database.", + TrieError::ValueAtIncompleteKey => + "A value was found with a key that is not byte-aligned.", + TrieError::DecoderError => "A corrupt trie item was encountered.", + TrieError::InvalidHash => "The hash does not match the expected value.", + TrieError::DuplicateKey => "The proof contains duplicate keys.", + TrieError::ExtraneousNode => "The proof contains extraneous nodes.", + TrieError::ExtraneousValue => "The proof contains extraneous values.", + TrieError::ExtraneousHashReference => "The proof contains extraneous hash references.", + TrieError::InvalidChildReference => "The proof contains an invalid child reference.", + TrieError::ValueMismatch => "The proof indicates a value mismatch.", + TrieError::IncompleteProof => "The proof is incomplete.", + TrieError::RootMismatch => "The root hash computed from the proof is incorrect.", + TrieError::DecodeError => "One of the proof nodes could not be decoded.", + } + } +} + +/// An interface for creating, interacting with, and creating proofs in a merkle trie. +pub trait ProvingTrie +where + Self: Sized, + Hashing: sp_core::Hasher, +{ + /// Create a new instance of a `ProvingTrie` using an iterator of key/value pairs. + fn generate_for(items: I) -> Result + where + I: IntoIterator; + /// Access the underlying trie root. + fn root(&self) -> &Hashing::Out; + /// Query a value contained within the current trie. Returns `None` if the + /// the value does not exist in the trie. + fn query(&self, key: &Key) -> Option; + /// Create a proof that can be used to verify a key and its value are in the trie. + fn create_proof(&self, key: &Key) -> Result, DispatchError>; + /// Verify the existence of `key` and `value` in a given trie root and proof. + fn verify_proof( + root: &Hashing::Out, + proof: &[u8], + key: &Key, + value: &Value, + ) -> Result<(), DispatchError>; +} + +/// This trait is one strategy that can be used to benchmark a trie proof verification for the +/// runtime. This strategy assumes that the majority complexity of verifying a merkle proof comes +/// from computing hashes to recreate the merkle root. This trait converts the the proof, some +/// bytes, to the number of hashes we expect to execute to verify that proof. +pub trait ProofToHashes { + /// The Proof type we will use to determine the number of hashes. + type Proof: ?Sized; + /// This function returns the number of hashes we expect to calculate based on the + /// size of the proof. This is used for benchmarking, so for worst case scenario, we should + /// round up. + /// + /// The major complexity of doing a `verify_proof` is computing the hashes needed + /// to calculate the merkle root. For tries, it should be easy to predict the depth + /// of the trie (which is equivalent to the hashes), by looking at the length of the proof. + fn proof_to_hashes(proof: &Self::Proof) -> Result; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::BlakeTwo256; + + // A trie which simulates a trie of accounts (u32) and balances (u128). + type BalanceTrie2 = base2::BasicProvingTrie; + type BalanceTrie16 = base16::BasicProvingTrie; + + #[test] + fn basic_api_usage_base_2() { + let balance_trie = BalanceTrie2::generate_for((0..100u32).map(|i| (i, i.into()))).unwrap(); + let root = *balance_trie.root(); + assert_eq!(balance_trie.query(&69), Some(69)); + assert_eq!(balance_trie.query(&6969), None); + let proof = balance_trie.create_proof(&69u32).unwrap(); + assert_eq!(BalanceTrie2::verify_proof(&root, &proof, &69u32, &69u128), Ok(())); + } + + #[test] + fn basic_api_usage_base_16() { + let balance_trie = BalanceTrie16::generate_for((0..100u32).map(|i| (i, i.into()))).unwrap(); + let root = *balance_trie.root(); + assert_eq!(balance_trie.query(&69), Some(69)); + assert_eq!(balance_trie.query(&6969), None); + let proof = balance_trie.create_proof(&69u32).unwrap(); + assert_eq!(BalanceTrie16::verify_proof(&root, &proof, &69u32, &69u128), Ok(())); + } +} -- GitLab From 184af677481744b44f86f5881de79587ef854142 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:07:50 +0200 Subject: [PATCH 024/127] [ci] Run subsystem-benchmarks on large runners (#5860) cc @AndreiEres --- .github/workflows/subsystem-benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/subsystem-benchmarks.yml index 3c0eaf30e45..210714d847f 100644 --- a/.github/workflows/subsystem-benchmarks.yml +++ b/.github/workflows/subsystem-benchmarks.yml @@ -22,7 +22,7 @@ jobs: build: timeout-minutes: 80 needs: [preflight] - runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} + runs-on: ${{ needs.preflight.outputs.RUNNER }} container: image: ${{ needs.preflight.outputs.IMAGE }} strategy: -- GitLab From 9283dc1db4bbc199b0fe3fb7da81eee98013785e Mon Sep 17 00:00:00 2001 From: Egor_P Date: Mon, 30 Sep 2024 15:14:49 +0200 Subject: [PATCH 025/127] [Backport] Version bumps and prdocs reordering from stable2409 (#5849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR backports regular version bumps and prdocs reordering from the `stable2409` release branch to `master` --------- Co-authored-by: Morgan Adamiec Co-authored-by: Bastian Köcher Co-authored-by: Nazar Mokrynskyi --- .../parachains/runtimes/assets/asset-hub-rococo/src/lib.rs | 2 +- .../parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- .../runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs | 4 ++-- .../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 +- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/{ => 1.16.0}/pr_2923.prdoc | 0 prdoc/{ => 1.16.0}/pr_3049.prdoc | 0 prdoc/{ => 1.16.0}/pr_3786.prdoc | 0 prdoc/{ => 1.16.0}/pr_3996.prdoc | 0 prdoc/{ => 1.16.0}/pr_4129.prdoc | 0 prdoc/{ => 1.16.0}/pr_4424.prdoc | 0 prdoc/{ => 1.16.0}/pr_4460.prdoc | 0 prdoc/{ => 1.16.0}/pr_4487.prdoc | 0 prdoc/{ => 1.16.0}/pr_4488.prdoc | 0 prdoc/{ => 1.16.0}/pr_4527.prdoc | 0 prdoc/{ => 1.16.0}/pr_4564.prdoc | 0 prdoc/{ => 1.16.0}/pr_4586.prdoc | 0 prdoc/{ => 1.16.0}/pr_4613.prdoc | 0 prdoc/{ => 1.16.0}/pr_4640.prdoc | 0 prdoc/{ => 1.16.0}/pr_4665.prdoc | 0 prdoc/{ => 1.16.0}/pr_4706.prdoc | 0 prdoc/{ => 1.16.0}/pr_4739.prdoc | 0 prdoc/{ => 1.16.0}/pr_4751.prdoc | 0 prdoc/{ => 1.16.0}/pr_4792.prdoc | 0 prdoc/{ => 1.16.0}/pr_4822.prdoc | 0 prdoc/{ => 1.16.0}/pr_4845.prdoc | 0 prdoc/{ => 1.16.0}/pr_4928.prdoc | 0 prdoc/{ => 1.16.0}/pr_4930.prdoc | 0 prdoc/{ => 1.16.0}/pr_4936.prdoc | 0 prdoc/{ => 1.16.0}/pr_4938.prdoc | 0 prdoc/{ => 1.16.0}/pr_4949.prdoc | 0 prdoc/{ => 1.16.0}/pr_4956.prdoc | 0 prdoc/{ => 1.16.0}/pr_4959.prdoc | 0 prdoc/{ => 1.16.0}/pr_4962.prdoc | 0 prdoc/{ => 1.16.0}/pr_4963.prdoc | 0 prdoc/{ => 1.16.0}/pr_4967.prdoc | 0 prdoc/{ => 1.16.0}/pr_4970.prdoc | 0 prdoc/{ => 1.16.0}/pr_4973.prdoc | 0 prdoc/{ => 1.16.0}/pr_4976.prdoc | 0 prdoc/{ => 1.16.0}/pr_4993.prdoc | 0 prdoc/{ => 1.16.0}/pr_4998.prdoc | 0 prdoc/{ => 1.16.0}/pr_4999.prdoc | 0 prdoc/{ => 1.16.0}/pr_5029.prdoc | 0 prdoc/{ => 1.16.0}/pr_5036.prdoc | 0 prdoc/{ => 1.16.0}/pr_5055.prdoc | 0 prdoc/{ => 1.16.0}/pr_5065.prdoc | 0 prdoc/{ => 1.16.0}/pr_5067.prdoc | 0 prdoc/{ => 1.16.0}/pr_5074.prdoc | 0 prdoc/{ => 1.16.0}/pr_5078.prdoc | 0 prdoc/{ => 1.16.0}/pr_5082.prdoc | 0 prdoc/{ => 1.16.0}/pr_5113.prdoc | 0 prdoc/{ => 1.16.0}/pr_5114.prdoc | 0 prdoc/{ => 1.16.0}/pr_5124.prdoc | 0 prdoc/{ => 1.16.0}/pr_5127.prdoc | 0 prdoc/{ => 1.16.0}/pr_5129.prdoc | 0 prdoc/{ => 1.16.0}/pr_5130.prdoc | 0 prdoc/{ => 1.16.0}/pr_5131.prdoc | 0 prdoc/{ => 1.16.0}/pr_5132.prdoc | 0 prdoc/{ => 1.16.0}/pr_5142.prdoc | 0 prdoc/{ => 1.16.0}/pr_5155.prdoc | 0 prdoc/{ => 1.16.0}/pr_5173.prdoc | 0 prdoc/{ => 1.16.0}/pr_5174.prdoc | 0 prdoc/{ => 1.16.0}/pr_5188.prdoc | 0 prdoc/{ => 1.16.0}/pr_5195.prdoc | 0 prdoc/{ => 1.16.0}/pr_5196.prdoc | 0 prdoc/{ => 1.16.0}/pr_5197.prdoc | 0 prdoc/{ => 1.16.0}/pr_5204.prdoc | 0 prdoc/{ => 1.16.0}/pr_5205.prdoc | 0 prdoc/{ => 1.16.0}/pr_5214.prdoc | 0 prdoc/{ => 1.16.0}/pr_5240.prdoc | 0 prdoc/{ => 1.16.0}/pr_5250.prdoc | 0 prdoc/{ => 1.16.0}/pr_5252.prdoc | 0 prdoc/{ => 1.16.0}/pr_5257.prdoc | 0 prdoc/{ => 1.16.0}/pr_5262.prdoc | 0 prdoc/{ => 1.16.0}/pr_5269.prdoc | 0 prdoc/{ => 1.16.0}/pr_5270.prdoc | 0 prdoc/{ => 1.16.0}/pr_5284.prdoc | 0 prdoc/{ => 1.16.0}/pr_5288.prdoc | 0 prdoc/{ => 1.16.0}/pr_5293.prdoc | 0 prdoc/{ => 1.16.0}/pr_5316.prdoc | 0 prdoc/{ => 1.16.0}/pr_5326.prdoc | 0 prdoc/{ => 1.16.0}/pr_5327.prdoc | 0 prdoc/{ => 1.16.0}/pr_5339.prdoc | 0 prdoc/{ => 1.16.0}/pr_5344.prdoc | 0 prdoc/{ => 1.16.0}/pr_5348.prdoc | 0 prdoc/{ => 1.16.0}/pr_5352.prdoc | 0 prdoc/{ => 1.16.0}/pr_5354.prdoc | 0 prdoc/{ => 1.16.0}/pr_5356.prdoc | 0 prdoc/{ => 1.16.0}/pr_5359.prdoc | 0 prdoc/{ => 1.16.0}/pr_5360.prdoc | 0 prdoc/{ => 1.16.0}/pr_5364.prdoc | 0 prdoc/{ => 1.16.0}/pr_5369.prdoc | 0 prdoc/{ => 1.16.0}/pr_5376.prdoc | 0 prdoc/{ => 1.16.0}/pr_5380.prdoc | 0 prdoc/{ => 1.16.0}/pr_5384.prdoc | 0 prdoc/{ => 1.16.0}/pr_5392.prdoc | 0 prdoc/{ => 1.16.0}/pr_5393.prdoc | 0 prdoc/{ => 1.16.0}/pr_5396.prdoc | 0 prdoc/{ => 1.16.0}/pr_5407.prdoc | 0 prdoc/{ => 1.16.0}/pr_5410.prdoc | 0 prdoc/{ => 1.16.0}/pr_5411.prdoc | 0 prdoc/{ => 1.16.0}/pr_5424.prdoc | 0 prdoc/{ => 1.16.0}/pr_5430.prdoc | 0 prdoc/{ => 1.16.0}/pr_5431.prdoc | 0 prdoc/{ => 1.16.0}/pr_5436.prdoc | 0 prdoc/{ => 1.16.0}/pr_5439.prdoc | 0 prdoc/{ => 1.16.0}/pr_5442.prdoc | 0 prdoc/{ => 1.16.0}/pr_5443.prdoc | 0 prdoc/{ => 1.16.0}/pr_5450.prdoc | 0 prdoc/{ => 1.16.0}/pr_5465.prdoc | 0 prdoc/{ => 1.16.0}/pr_5466.prdoc | 0 prdoc/{ => 1.16.0}/pr_5467.prdoc | 0 prdoc/{ => 1.16.0}/pr_5509.prdoc | 0 prdoc/{ => 1.16.0}/pr_5513.prdoc | 0 prdoc/{ => 1.16.0}/pr_5527.prdoc | 0 prdoc/{ => 1.16.0}/pr_5538.prdoc | 0 prdoc/{ => 1.16.0}/pr_5546.prdoc | 0 prdoc/{ => 1.16.0}/pr_5563.prdoc | 0 prdoc/{ => 1.16.0}/pr_5580.prdoc | 0 prdoc/{ => 1.16.0}/pr_5581.prdoc | 0 prdoc/{ => 1.16.0}/pr_5594.prdoc | 0 prdoc/{ => 1.16.0}/pr_5632.prdoc | 0 prdoc/{ => 1.16.0}/pr_5644.prdoc | 0 prdoc/{ => 1.16.0}/pr_5649.prdoc | 0 prdoc/{ => 1.16.0}/pr_5655.prdoc | 0 prdoc/{ => 1.16.0}/pr_5660.prdoc | 0 prdoc/{ => 1.16.0}/pr_5671.prdoc | 0 prdoc/{ => 1.16.0}/pr_5678.prdoc | 0 prdoc/{ => 1.16.0}/pr_5688.prdoc | 0 prdoc/{ => 1.16.0}/pr_5695.prdoc | 0 prdoc/{ => 1.16.0}/pr_5712.prdoc | 0 prdoc/{ => 1.16.0}/pr_5713.prdoc | 0 prdoc/{ => 1.16.0}/pr_5747.prdoc | 0 142 files changed, 15 insertions(+), 15 deletions(-) rename prdoc/{ => 1.16.0}/pr_2923.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3049.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3786.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_3996.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4129.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4424.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4460.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4487.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4488.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4527.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4564.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4586.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4613.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4640.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4665.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4706.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4739.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4751.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4792.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4822.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4845.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4928.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4930.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4936.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4938.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4949.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4956.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4959.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4962.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4963.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4967.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4970.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4973.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4976.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4993.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4998.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_4999.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5029.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5036.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5055.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5065.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5067.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5074.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5078.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5082.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5113.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5114.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5124.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5127.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5129.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5130.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5131.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5132.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5142.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5155.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5173.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5174.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5188.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5195.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5196.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5197.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5204.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5205.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5214.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5240.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5250.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5252.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5257.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5262.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5269.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5270.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5284.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5288.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5293.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5316.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5326.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5327.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5339.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5344.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5348.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5352.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5354.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5356.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5359.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5360.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5364.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5369.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5376.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5380.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5384.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5392.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5393.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5396.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5407.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5410.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5411.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5424.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5430.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5431.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5436.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5439.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5442.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5443.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5450.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5465.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5466.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5467.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5509.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5513.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5527.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5538.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5546.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5563.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5580.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5581.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5594.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5632.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5644.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5649.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5655.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5660.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5671.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5678.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5688.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5695.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5712.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5713.prdoc (100%) rename prdoc/{ => 1.16.0}/pr_5747.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 430c5bd1856..8163a445220 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -123,7 +123,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, 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 8565cd30f43..75aea0eb2c2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -123,7 +123,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, 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 bf9bd58669b..13c1ab6b625 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 @@ -238,10 +238,10 @@ 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_016_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 5, + transaction_version: 6, system_version: 1, }; 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 87152d30977..03b69a21802 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 @@ -226,7 +226,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 5, + transaction_version: 6, system_version: 1, }; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 7174595f12a..a60d3ed66ac 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -126,7 +126,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 5f03b6ab7e2..853c8b48dc8 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -144,7 +144,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 7, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index be332f67c9e..74569af9ec7 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -149,7 +149,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index cc43c95f005..5e37faf6181 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -148,7 +148,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index 1b213785cf9..bc76f174b50 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -102,7 +102,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_015_000, + spec_version: 1_016_001, 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 16e023ad3dc..bfd31e2b484 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -137,7 +137,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 60b861678c5..8258ba0564f 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -136,7 +136,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 685a9fd337d..5b6bcdaa9d7 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -59,7 +59,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.15.1"; +pub const NODE_VERSION: &'static str = "1.16.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 5fd219c193c..e1826e4100b 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -168,7 +168,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 576e297df31..7324485256a 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -168,7 +168,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_015_000, + spec_version: 1_016_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, diff --git a/prdoc/pr_2923.prdoc b/prdoc/1.16.0/pr_2923.prdoc similarity index 100% rename from prdoc/pr_2923.prdoc rename to prdoc/1.16.0/pr_2923.prdoc diff --git a/prdoc/pr_3049.prdoc b/prdoc/1.16.0/pr_3049.prdoc similarity index 100% rename from prdoc/pr_3049.prdoc rename to prdoc/1.16.0/pr_3049.prdoc diff --git a/prdoc/pr_3786.prdoc b/prdoc/1.16.0/pr_3786.prdoc similarity index 100% rename from prdoc/pr_3786.prdoc rename to prdoc/1.16.0/pr_3786.prdoc diff --git a/prdoc/pr_3996.prdoc b/prdoc/1.16.0/pr_3996.prdoc similarity index 100% rename from prdoc/pr_3996.prdoc rename to prdoc/1.16.0/pr_3996.prdoc diff --git a/prdoc/pr_4129.prdoc b/prdoc/1.16.0/pr_4129.prdoc similarity index 100% rename from prdoc/pr_4129.prdoc rename to prdoc/1.16.0/pr_4129.prdoc diff --git a/prdoc/pr_4424.prdoc b/prdoc/1.16.0/pr_4424.prdoc similarity index 100% rename from prdoc/pr_4424.prdoc rename to prdoc/1.16.0/pr_4424.prdoc diff --git a/prdoc/pr_4460.prdoc b/prdoc/1.16.0/pr_4460.prdoc similarity index 100% rename from prdoc/pr_4460.prdoc rename to prdoc/1.16.0/pr_4460.prdoc diff --git a/prdoc/pr_4487.prdoc b/prdoc/1.16.0/pr_4487.prdoc similarity index 100% rename from prdoc/pr_4487.prdoc rename to prdoc/1.16.0/pr_4487.prdoc diff --git a/prdoc/pr_4488.prdoc b/prdoc/1.16.0/pr_4488.prdoc similarity index 100% rename from prdoc/pr_4488.prdoc rename to prdoc/1.16.0/pr_4488.prdoc diff --git a/prdoc/pr_4527.prdoc b/prdoc/1.16.0/pr_4527.prdoc similarity index 100% rename from prdoc/pr_4527.prdoc rename to prdoc/1.16.0/pr_4527.prdoc diff --git a/prdoc/pr_4564.prdoc b/prdoc/1.16.0/pr_4564.prdoc similarity index 100% rename from prdoc/pr_4564.prdoc rename to prdoc/1.16.0/pr_4564.prdoc diff --git a/prdoc/pr_4586.prdoc b/prdoc/1.16.0/pr_4586.prdoc similarity index 100% rename from prdoc/pr_4586.prdoc rename to prdoc/1.16.0/pr_4586.prdoc diff --git a/prdoc/pr_4613.prdoc b/prdoc/1.16.0/pr_4613.prdoc similarity index 100% rename from prdoc/pr_4613.prdoc rename to prdoc/1.16.0/pr_4613.prdoc diff --git a/prdoc/pr_4640.prdoc b/prdoc/1.16.0/pr_4640.prdoc similarity index 100% rename from prdoc/pr_4640.prdoc rename to prdoc/1.16.0/pr_4640.prdoc diff --git a/prdoc/pr_4665.prdoc b/prdoc/1.16.0/pr_4665.prdoc similarity index 100% rename from prdoc/pr_4665.prdoc rename to prdoc/1.16.0/pr_4665.prdoc diff --git a/prdoc/pr_4706.prdoc b/prdoc/1.16.0/pr_4706.prdoc similarity index 100% rename from prdoc/pr_4706.prdoc rename to prdoc/1.16.0/pr_4706.prdoc diff --git a/prdoc/pr_4739.prdoc b/prdoc/1.16.0/pr_4739.prdoc similarity index 100% rename from prdoc/pr_4739.prdoc rename to prdoc/1.16.0/pr_4739.prdoc diff --git a/prdoc/pr_4751.prdoc b/prdoc/1.16.0/pr_4751.prdoc similarity index 100% rename from prdoc/pr_4751.prdoc rename to prdoc/1.16.0/pr_4751.prdoc diff --git a/prdoc/pr_4792.prdoc b/prdoc/1.16.0/pr_4792.prdoc similarity index 100% rename from prdoc/pr_4792.prdoc rename to prdoc/1.16.0/pr_4792.prdoc diff --git a/prdoc/pr_4822.prdoc b/prdoc/1.16.0/pr_4822.prdoc similarity index 100% rename from prdoc/pr_4822.prdoc rename to prdoc/1.16.0/pr_4822.prdoc diff --git a/prdoc/pr_4845.prdoc b/prdoc/1.16.0/pr_4845.prdoc similarity index 100% rename from prdoc/pr_4845.prdoc rename to prdoc/1.16.0/pr_4845.prdoc diff --git a/prdoc/pr_4928.prdoc b/prdoc/1.16.0/pr_4928.prdoc similarity index 100% rename from prdoc/pr_4928.prdoc rename to prdoc/1.16.0/pr_4928.prdoc diff --git a/prdoc/pr_4930.prdoc b/prdoc/1.16.0/pr_4930.prdoc similarity index 100% rename from prdoc/pr_4930.prdoc rename to prdoc/1.16.0/pr_4930.prdoc diff --git a/prdoc/pr_4936.prdoc b/prdoc/1.16.0/pr_4936.prdoc similarity index 100% rename from prdoc/pr_4936.prdoc rename to prdoc/1.16.0/pr_4936.prdoc diff --git a/prdoc/pr_4938.prdoc b/prdoc/1.16.0/pr_4938.prdoc similarity index 100% rename from prdoc/pr_4938.prdoc rename to prdoc/1.16.0/pr_4938.prdoc diff --git a/prdoc/pr_4949.prdoc b/prdoc/1.16.0/pr_4949.prdoc similarity index 100% rename from prdoc/pr_4949.prdoc rename to prdoc/1.16.0/pr_4949.prdoc diff --git a/prdoc/pr_4956.prdoc b/prdoc/1.16.0/pr_4956.prdoc similarity index 100% rename from prdoc/pr_4956.prdoc rename to prdoc/1.16.0/pr_4956.prdoc diff --git a/prdoc/pr_4959.prdoc b/prdoc/1.16.0/pr_4959.prdoc similarity index 100% rename from prdoc/pr_4959.prdoc rename to prdoc/1.16.0/pr_4959.prdoc diff --git a/prdoc/pr_4962.prdoc b/prdoc/1.16.0/pr_4962.prdoc similarity index 100% rename from prdoc/pr_4962.prdoc rename to prdoc/1.16.0/pr_4962.prdoc diff --git a/prdoc/pr_4963.prdoc b/prdoc/1.16.0/pr_4963.prdoc similarity index 100% rename from prdoc/pr_4963.prdoc rename to prdoc/1.16.0/pr_4963.prdoc diff --git a/prdoc/pr_4967.prdoc b/prdoc/1.16.0/pr_4967.prdoc similarity index 100% rename from prdoc/pr_4967.prdoc rename to prdoc/1.16.0/pr_4967.prdoc diff --git a/prdoc/pr_4970.prdoc b/prdoc/1.16.0/pr_4970.prdoc similarity index 100% rename from prdoc/pr_4970.prdoc rename to prdoc/1.16.0/pr_4970.prdoc diff --git a/prdoc/pr_4973.prdoc b/prdoc/1.16.0/pr_4973.prdoc similarity index 100% rename from prdoc/pr_4973.prdoc rename to prdoc/1.16.0/pr_4973.prdoc diff --git a/prdoc/pr_4976.prdoc b/prdoc/1.16.0/pr_4976.prdoc similarity index 100% rename from prdoc/pr_4976.prdoc rename to prdoc/1.16.0/pr_4976.prdoc diff --git a/prdoc/pr_4993.prdoc b/prdoc/1.16.0/pr_4993.prdoc similarity index 100% rename from prdoc/pr_4993.prdoc rename to prdoc/1.16.0/pr_4993.prdoc diff --git a/prdoc/pr_4998.prdoc b/prdoc/1.16.0/pr_4998.prdoc similarity index 100% rename from prdoc/pr_4998.prdoc rename to prdoc/1.16.0/pr_4998.prdoc diff --git a/prdoc/pr_4999.prdoc b/prdoc/1.16.0/pr_4999.prdoc similarity index 100% rename from prdoc/pr_4999.prdoc rename to prdoc/1.16.0/pr_4999.prdoc diff --git a/prdoc/pr_5029.prdoc b/prdoc/1.16.0/pr_5029.prdoc similarity index 100% rename from prdoc/pr_5029.prdoc rename to prdoc/1.16.0/pr_5029.prdoc diff --git a/prdoc/pr_5036.prdoc b/prdoc/1.16.0/pr_5036.prdoc similarity index 100% rename from prdoc/pr_5036.prdoc rename to prdoc/1.16.0/pr_5036.prdoc diff --git a/prdoc/pr_5055.prdoc b/prdoc/1.16.0/pr_5055.prdoc similarity index 100% rename from prdoc/pr_5055.prdoc rename to prdoc/1.16.0/pr_5055.prdoc diff --git a/prdoc/pr_5065.prdoc b/prdoc/1.16.0/pr_5065.prdoc similarity index 100% rename from prdoc/pr_5065.prdoc rename to prdoc/1.16.0/pr_5065.prdoc diff --git a/prdoc/pr_5067.prdoc b/prdoc/1.16.0/pr_5067.prdoc similarity index 100% rename from prdoc/pr_5067.prdoc rename to prdoc/1.16.0/pr_5067.prdoc diff --git a/prdoc/pr_5074.prdoc b/prdoc/1.16.0/pr_5074.prdoc similarity index 100% rename from prdoc/pr_5074.prdoc rename to prdoc/1.16.0/pr_5074.prdoc diff --git a/prdoc/pr_5078.prdoc b/prdoc/1.16.0/pr_5078.prdoc similarity index 100% rename from prdoc/pr_5078.prdoc rename to prdoc/1.16.0/pr_5078.prdoc diff --git a/prdoc/pr_5082.prdoc b/prdoc/1.16.0/pr_5082.prdoc similarity index 100% rename from prdoc/pr_5082.prdoc rename to prdoc/1.16.0/pr_5082.prdoc diff --git a/prdoc/pr_5113.prdoc b/prdoc/1.16.0/pr_5113.prdoc similarity index 100% rename from prdoc/pr_5113.prdoc rename to prdoc/1.16.0/pr_5113.prdoc diff --git a/prdoc/pr_5114.prdoc b/prdoc/1.16.0/pr_5114.prdoc similarity index 100% rename from prdoc/pr_5114.prdoc rename to prdoc/1.16.0/pr_5114.prdoc diff --git a/prdoc/pr_5124.prdoc b/prdoc/1.16.0/pr_5124.prdoc similarity index 100% rename from prdoc/pr_5124.prdoc rename to prdoc/1.16.0/pr_5124.prdoc diff --git a/prdoc/pr_5127.prdoc b/prdoc/1.16.0/pr_5127.prdoc similarity index 100% rename from prdoc/pr_5127.prdoc rename to prdoc/1.16.0/pr_5127.prdoc diff --git a/prdoc/pr_5129.prdoc b/prdoc/1.16.0/pr_5129.prdoc similarity index 100% rename from prdoc/pr_5129.prdoc rename to prdoc/1.16.0/pr_5129.prdoc diff --git a/prdoc/pr_5130.prdoc b/prdoc/1.16.0/pr_5130.prdoc similarity index 100% rename from prdoc/pr_5130.prdoc rename to prdoc/1.16.0/pr_5130.prdoc diff --git a/prdoc/pr_5131.prdoc b/prdoc/1.16.0/pr_5131.prdoc similarity index 100% rename from prdoc/pr_5131.prdoc rename to prdoc/1.16.0/pr_5131.prdoc diff --git a/prdoc/pr_5132.prdoc b/prdoc/1.16.0/pr_5132.prdoc similarity index 100% rename from prdoc/pr_5132.prdoc rename to prdoc/1.16.0/pr_5132.prdoc diff --git a/prdoc/pr_5142.prdoc b/prdoc/1.16.0/pr_5142.prdoc similarity index 100% rename from prdoc/pr_5142.prdoc rename to prdoc/1.16.0/pr_5142.prdoc diff --git a/prdoc/pr_5155.prdoc b/prdoc/1.16.0/pr_5155.prdoc similarity index 100% rename from prdoc/pr_5155.prdoc rename to prdoc/1.16.0/pr_5155.prdoc diff --git a/prdoc/pr_5173.prdoc b/prdoc/1.16.0/pr_5173.prdoc similarity index 100% rename from prdoc/pr_5173.prdoc rename to prdoc/1.16.0/pr_5173.prdoc diff --git a/prdoc/pr_5174.prdoc b/prdoc/1.16.0/pr_5174.prdoc similarity index 100% rename from prdoc/pr_5174.prdoc rename to prdoc/1.16.0/pr_5174.prdoc diff --git a/prdoc/pr_5188.prdoc b/prdoc/1.16.0/pr_5188.prdoc similarity index 100% rename from prdoc/pr_5188.prdoc rename to prdoc/1.16.0/pr_5188.prdoc diff --git a/prdoc/pr_5195.prdoc b/prdoc/1.16.0/pr_5195.prdoc similarity index 100% rename from prdoc/pr_5195.prdoc rename to prdoc/1.16.0/pr_5195.prdoc diff --git a/prdoc/pr_5196.prdoc b/prdoc/1.16.0/pr_5196.prdoc similarity index 100% rename from prdoc/pr_5196.prdoc rename to prdoc/1.16.0/pr_5196.prdoc diff --git a/prdoc/pr_5197.prdoc b/prdoc/1.16.0/pr_5197.prdoc similarity index 100% rename from prdoc/pr_5197.prdoc rename to prdoc/1.16.0/pr_5197.prdoc diff --git a/prdoc/pr_5204.prdoc b/prdoc/1.16.0/pr_5204.prdoc similarity index 100% rename from prdoc/pr_5204.prdoc rename to prdoc/1.16.0/pr_5204.prdoc diff --git a/prdoc/pr_5205.prdoc b/prdoc/1.16.0/pr_5205.prdoc similarity index 100% rename from prdoc/pr_5205.prdoc rename to prdoc/1.16.0/pr_5205.prdoc diff --git a/prdoc/pr_5214.prdoc b/prdoc/1.16.0/pr_5214.prdoc similarity index 100% rename from prdoc/pr_5214.prdoc rename to prdoc/1.16.0/pr_5214.prdoc diff --git a/prdoc/pr_5240.prdoc b/prdoc/1.16.0/pr_5240.prdoc similarity index 100% rename from prdoc/pr_5240.prdoc rename to prdoc/1.16.0/pr_5240.prdoc diff --git a/prdoc/pr_5250.prdoc b/prdoc/1.16.0/pr_5250.prdoc similarity index 100% rename from prdoc/pr_5250.prdoc rename to prdoc/1.16.0/pr_5250.prdoc diff --git a/prdoc/pr_5252.prdoc b/prdoc/1.16.0/pr_5252.prdoc similarity index 100% rename from prdoc/pr_5252.prdoc rename to prdoc/1.16.0/pr_5252.prdoc diff --git a/prdoc/pr_5257.prdoc b/prdoc/1.16.0/pr_5257.prdoc similarity index 100% rename from prdoc/pr_5257.prdoc rename to prdoc/1.16.0/pr_5257.prdoc diff --git a/prdoc/pr_5262.prdoc b/prdoc/1.16.0/pr_5262.prdoc similarity index 100% rename from prdoc/pr_5262.prdoc rename to prdoc/1.16.0/pr_5262.prdoc diff --git a/prdoc/pr_5269.prdoc b/prdoc/1.16.0/pr_5269.prdoc similarity index 100% rename from prdoc/pr_5269.prdoc rename to prdoc/1.16.0/pr_5269.prdoc diff --git a/prdoc/pr_5270.prdoc b/prdoc/1.16.0/pr_5270.prdoc similarity index 100% rename from prdoc/pr_5270.prdoc rename to prdoc/1.16.0/pr_5270.prdoc diff --git a/prdoc/pr_5284.prdoc b/prdoc/1.16.0/pr_5284.prdoc similarity index 100% rename from prdoc/pr_5284.prdoc rename to prdoc/1.16.0/pr_5284.prdoc diff --git a/prdoc/pr_5288.prdoc b/prdoc/1.16.0/pr_5288.prdoc similarity index 100% rename from prdoc/pr_5288.prdoc rename to prdoc/1.16.0/pr_5288.prdoc diff --git a/prdoc/pr_5293.prdoc b/prdoc/1.16.0/pr_5293.prdoc similarity index 100% rename from prdoc/pr_5293.prdoc rename to prdoc/1.16.0/pr_5293.prdoc diff --git a/prdoc/pr_5316.prdoc b/prdoc/1.16.0/pr_5316.prdoc similarity index 100% rename from prdoc/pr_5316.prdoc rename to prdoc/1.16.0/pr_5316.prdoc diff --git a/prdoc/pr_5326.prdoc b/prdoc/1.16.0/pr_5326.prdoc similarity index 100% rename from prdoc/pr_5326.prdoc rename to prdoc/1.16.0/pr_5326.prdoc diff --git a/prdoc/pr_5327.prdoc b/prdoc/1.16.0/pr_5327.prdoc similarity index 100% rename from prdoc/pr_5327.prdoc rename to prdoc/1.16.0/pr_5327.prdoc diff --git a/prdoc/pr_5339.prdoc b/prdoc/1.16.0/pr_5339.prdoc similarity index 100% rename from prdoc/pr_5339.prdoc rename to prdoc/1.16.0/pr_5339.prdoc diff --git a/prdoc/pr_5344.prdoc b/prdoc/1.16.0/pr_5344.prdoc similarity index 100% rename from prdoc/pr_5344.prdoc rename to prdoc/1.16.0/pr_5344.prdoc diff --git a/prdoc/pr_5348.prdoc b/prdoc/1.16.0/pr_5348.prdoc similarity index 100% rename from prdoc/pr_5348.prdoc rename to prdoc/1.16.0/pr_5348.prdoc diff --git a/prdoc/pr_5352.prdoc b/prdoc/1.16.0/pr_5352.prdoc similarity index 100% rename from prdoc/pr_5352.prdoc rename to prdoc/1.16.0/pr_5352.prdoc diff --git a/prdoc/pr_5354.prdoc b/prdoc/1.16.0/pr_5354.prdoc similarity index 100% rename from prdoc/pr_5354.prdoc rename to prdoc/1.16.0/pr_5354.prdoc diff --git a/prdoc/pr_5356.prdoc b/prdoc/1.16.0/pr_5356.prdoc similarity index 100% rename from prdoc/pr_5356.prdoc rename to prdoc/1.16.0/pr_5356.prdoc diff --git a/prdoc/pr_5359.prdoc b/prdoc/1.16.0/pr_5359.prdoc similarity index 100% rename from prdoc/pr_5359.prdoc rename to prdoc/1.16.0/pr_5359.prdoc diff --git a/prdoc/pr_5360.prdoc b/prdoc/1.16.0/pr_5360.prdoc similarity index 100% rename from prdoc/pr_5360.prdoc rename to prdoc/1.16.0/pr_5360.prdoc diff --git a/prdoc/pr_5364.prdoc b/prdoc/1.16.0/pr_5364.prdoc similarity index 100% rename from prdoc/pr_5364.prdoc rename to prdoc/1.16.0/pr_5364.prdoc diff --git a/prdoc/pr_5369.prdoc b/prdoc/1.16.0/pr_5369.prdoc similarity index 100% rename from prdoc/pr_5369.prdoc rename to prdoc/1.16.0/pr_5369.prdoc diff --git a/prdoc/pr_5376.prdoc b/prdoc/1.16.0/pr_5376.prdoc similarity index 100% rename from prdoc/pr_5376.prdoc rename to prdoc/1.16.0/pr_5376.prdoc diff --git a/prdoc/pr_5380.prdoc b/prdoc/1.16.0/pr_5380.prdoc similarity index 100% rename from prdoc/pr_5380.prdoc rename to prdoc/1.16.0/pr_5380.prdoc diff --git a/prdoc/pr_5384.prdoc b/prdoc/1.16.0/pr_5384.prdoc similarity index 100% rename from prdoc/pr_5384.prdoc rename to prdoc/1.16.0/pr_5384.prdoc diff --git a/prdoc/pr_5392.prdoc b/prdoc/1.16.0/pr_5392.prdoc similarity index 100% rename from prdoc/pr_5392.prdoc rename to prdoc/1.16.0/pr_5392.prdoc diff --git a/prdoc/pr_5393.prdoc b/prdoc/1.16.0/pr_5393.prdoc similarity index 100% rename from prdoc/pr_5393.prdoc rename to prdoc/1.16.0/pr_5393.prdoc diff --git a/prdoc/pr_5396.prdoc b/prdoc/1.16.0/pr_5396.prdoc similarity index 100% rename from prdoc/pr_5396.prdoc rename to prdoc/1.16.0/pr_5396.prdoc diff --git a/prdoc/pr_5407.prdoc b/prdoc/1.16.0/pr_5407.prdoc similarity index 100% rename from prdoc/pr_5407.prdoc rename to prdoc/1.16.0/pr_5407.prdoc diff --git a/prdoc/pr_5410.prdoc b/prdoc/1.16.0/pr_5410.prdoc similarity index 100% rename from prdoc/pr_5410.prdoc rename to prdoc/1.16.0/pr_5410.prdoc diff --git a/prdoc/pr_5411.prdoc b/prdoc/1.16.0/pr_5411.prdoc similarity index 100% rename from prdoc/pr_5411.prdoc rename to prdoc/1.16.0/pr_5411.prdoc diff --git a/prdoc/pr_5424.prdoc b/prdoc/1.16.0/pr_5424.prdoc similarity index 100% rename from prdoc/pr_5424.prdoc rename to prdoc/1.16.0/pr_5424.prdoc diff --git a/prdoc/pr_5430.prdoc b/prdoc/1.16.0/pr_5430.prdoc similarity index 100% rename from prdoc/pr_5430.prdoc rename to prdoc/1.16.0/pr_5430.prdoc diff --git a/prdoc/pr_5431.prdoc b/prdoc/1.16.0/pr_5431.prdoc similarity index 100% rename from prdoc/pr_5431.prdoc rename to prdoc/1.16.0/pr_5431.prdoc diff --git a/prdoc/pr_5436.prdoc b/prdoc/1.16.0/pr_5436.prdoc similarity index 100% rename from prdoc/pr_5436.prdoc rename to prdoc/1.16.0/pr_5436.prdoc diff --git a/prdoc/pr_5439.prdoc b/prdoc/1.16.0/pr_5439.prdoc similarity index 100% rename from prdoc/pr_5439.prdoc rename to prdoc/1.16.0/pr_5439.prdoc diff --git a/prdoc/pr_5442.prdoc b/prdoc/1.16.0/pr_5442.prdoc similarity index 100% rename from prdoc/pr_5442.prdoc rename to prdoc/1.16.0/pr_5442.prdoc diff --git a/prdoc/pr_5443.prdoc b/prdoc/1.16.0/pr_5443.prdoc similarity index 100% rename from prdoc/pr_5443.prdoc rename to prdoc/1.16.0/pr_5443.prdoc diff --git a/prdoc/pr_5450.prdoc b/prdoc/1.16.0/pr_5450.prdoc similarity index 100% rename from prdoc/pr_5450.prdoc rename to prdoc/1.16.0/pr_5450.prdoc diff --git a/prdoc/pr_5465.prdoc b/prdoc/1.16.0/pr_5465.prdoc similarity index 100% rename from prdoc/pr_5465.prdoc rename to prdoc/1.16.0/pr_5465.prdoc diff --git a/prdoc/pr_5466.prdoc b/prdoc/1.16.0/pr_5466.prdoc similarity index 100% rename from prdoc/pr_5466.prdoc rename to prdoc/1.16.0/pr_5466.prdoc diff --git a/prdoc/pr_5467.prdoc b/prdoc/1.16.0/pr_5467.prdoc similarity index 100% rename from prdoc/pr_5467.prdoc rename to prdoc/1.16.0/pr_5467.prdoc diff --git a/prdoc/pr_5509.prdoc b/prdoc/1.16.0/pr_5509.prdoc similarity index 100% rename from prdoc/pr_5509.prdoc rename to prdoc/1.16.0/pr_5509.prdoc diff --git a/prdoc/pr_5513.prdoc b/prdoc/1.16.0/pr_5513.prdoc similarity index 100% rename from prdoc/pr_5513.prdoc rename to prdoc/1.16.0/pr_5513.prdoc diff --git a/prdoc/pr_5527.prdoc b/prdoc/1.16.0/pr_5527.prdoc similarity index 100% rename from prdoc/pr_5527.prdoc rename to prdoc/1.16.0/pr_5527.prdoc diff --git a/prdoc/pr_5538.prdoc b/prdoc/1.16.0/pr_5538.prdoc similarity index 100% rename from prdoc/pr_5538.prdoc rename to prdoc/1.16.0/pr_5538.prdoc diff --git a/prdoc/pr_5546.prdoc b/prdoc/1.16.0/pr_5546.prdoc similarity index 100% rename from prdoc/pr_5546.prdoc rename to prdoc/1.16.0/pr_5546.prdoc diff --git a/prdoc/pr_5563.prdoc b/prdoc/1.16.0/pr_5563.prdoc similarity index 100% rename from prdoc/pr_5563.prdoc rename to prdoc/1.16.0/pr_5563.prdoc diff --git a/prdoc/pr_5580.prdoc b/prdoc/1.16.0/pr_5580.prdoc similarity index 100% rename from prdoc/pr_5580.prdoc rename to prdoc/1.16.0/pr_5580.prdoc diff --git a/prdoc/pr_5581.prdoc b/prdoc/1.16.0/pr_5581.prdoc similarity index 100% rename from prdoc/pr_5581.prdoc rename to prdoc/1.16.0/pr_5581.prdoc diff --git a/prdoc/pr_5594.prdoc b/prdoc/1.16.0/pr_5594.prdoc similarity index 100% rename from prdoc/pr_5594.prdoc rename to prdoc/1.16.0/pr_5594.prdoc diff --git a/prdoc/pr_5632.prdoc b/prdoc/1.16.0/pr_5632.prdoc similarity index 100% rename from prdoc/pr_5632.prdoc rename to prdoc/1.16.0/pr_5632.prdoc diff --git a/prdoc/pr_5644.prdoc b/prdoc/1.16.0/pr_5644.prdoc similarity index 100% rename from prdoc/pr_5644.prdoc rename to prdoc/1.16.0/pr_5644.prdoc diff --git a/prdoc/pr_5649.prdoc b/prdoc/1.16.0/pr_5649.prdoc similarity index 100% rename from prdoc/pr_5649.prdoc rename to prdoc/1.16.0/pr_5649.prdoc diff --git a/prdoc/pr_5655.prdoc b/prdoc/1.16.0/pr_5655.prdoc similarity index 100% rename from prdoc/pr_5655.prdoc rename to prdoc/1.16.0/pr_5655.prdoc diff --git a/prdoc/pr_5660.prdoc b/prdoc/1.16.0/pr_5660.prdoc similarity index 100% rename from prdoc/pr_5660.prdoc rename to prdoc/1.16.0/pr_5660.prdoc diff --git a/prdoc/pr_5671.prdoc b/prdoc/1.16.0/pr_5671.prdoc similarity index 100% rename from prdoc/pr_5671.prdoc rename to prdoc/1.16.0/pr_5671.prdoc diff --git a/prdoc/pr_5678.prdoc b/prdoc/1.16.0/pr_5678.prdoc similarity index 100% rename from prdoc/pr_5678.prdoc rename to prdoc/1.16.0/pr_5678.prdoc diff --git a/prdoc/pr_5688.prdoc b/prdoc/1.16.0/pr_5688.prdoc similarity index 100% rename from prdoc/pr_5688.prdoc rename to prdoc/1.16.0/pr_5688.prdoc diff --git a/prdoc/pr_5695.prdoc b/prdoc/1.16.0/pr_5695.prdoc similarity index 100% rename from prdoc/pr_5695.prdoc rename to prdoc/1.16.0/pr_5695.prdoc diff --git a/prdoc/pr_5712.prdoc b/prdoc/1.16.0/pr_5712.prdoc similarity index 100% rename from prdoc/pr_5712.prdoc rename to prdoc/1.16.0/pr_5712.prdoc diff --git a/prdoc/pr_5713.prdoc b/prdoc/1.16.0/pr_5713.prdoc similarity index 100% rename from prdoc/pr_5713.prdoc rename to prdoc/1.16.0/pr_5713.prdoc diff --git a/prdoc/pr_5747.prdoc b/prdoc/1.16.0/pr_5747.prdoc similarity index 100% rename from prdoc/pr_5747.prdoc rename to prdoc/1.16.0/pr_5747.prdoc -- GitLab From a8d8596fd2dc36aa7c4e1bb63536c30fef2855ea Mon Sep 17 00:00:00 2001 From: Joseph Zhao <65984904+programskillforverification@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:15:11 +0800 Subject: [PATCH 026/127] Replace lazy_static with LazyLock (#5716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description close #5641 --------- Co-authored-by: Bastian Köcher --- Cargo.lock | 17 --- Cargo.toml | 1 - cumulus/pallets/parachain-system/Cargo.toml | 1 - cumulus/pallets/parachain-system/src/tests.rs | 12 +- cumulus/xcm/xcm-emulator/Cargo.toml | 1 - cumulus/xcm/xcm-emulator/src/lib.rs | 46 ++++---- polkadot/node/jaeger/Cargo.toml | 1 - polkadot/node/jaeger/src/lib.rs | 9 +- .../network/dispute-distribution/Cargo.toml | 1 - .../dispute-distribution/src/tests/mock.rs | 103 +++++++++--------- .../node/network/gossip-support/Cargo.toml | 1 - .../node/network/gossip-support/src/tests.rs | 49 ++++----- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/node/subsystem-util/Cargo.toml | 1 - .../node/zombienet-backchannel/Cargo.toml | 1 - .../node/zombienet-backchannel/src/lib.rs | 5 +- prdoc/pr_5716.prdoc | 37 +++++++ substrate/bin/node/bench/Cargo.toml | 1 - substrate/bin/node/bench/src/trie.rs | 13 ++- substrate/client/offchain/Cargo.toml | 6 +- substrate/client/offchain/src/api/http.rs | 8 +- substrate/client/tracing/Cargo.toml | 6 +- substrate/client/utils/Cargo.toml | 1 - substrate/client/utils/src/metrics.rs | 47 ++++---- .../primitives/consensus/beefy/Cargo.toml | 2 - .../consensus/beefy/src/test_utils.rs | 17 +-- substrate/primitives/core/Cargo.toml | 18 ++- substrate/primitives/core/fuzz/Cargo.toml | 1 - .../fuzz/fuzz_targets/fuzz_address_uri.rs | 9 +- substrate/primitives/core/src/address_uri.rs | 9 +- substrate/primitives/panic-handler/Cargo.toml | 1 - substrate/primitives/panic-handler/src/lib.rs | 12 +- substrate/primitives/trie/Cargo.toml | 2 - .../primitives/trie/src/cache/shared_cache.rs | 14 +-- .../utils/frame/benchmarking-cli/Cargo.toml | 1 - .../benchmarking-cli/src/machine/hardware.rs | 20 ++-- 36 files changed, 240 insertions(+), 236 deletions(-) create mode 100644 prdoc/pr_5716.prdoc diff --git a/Cargo.lock b/Cargo.lock index 97bdc935135..7eb88c85bc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4353,7 +4353,6 @@ dependencies = [ "futures", "hex-literal", "impl-trait-for-tuples", - "lazy_static", "log", "pallet-message-queue", "parity-scale-codec", @@ -6210,7 +6209,6 @@ dependencies = [ "gethostname", "handlebars", "itertools 0.11.0", - "lazy_static", "linked-hash-map", "log", "parity-scale-codec", @@ -10119,7 +10117,6 @@ dependencies = [ "kitchensink-runtime", "kvdb", "kvdb-rocksdb", - "lazy_static", "log", "node-primitives", "node-testing", @@ -14104,7 +14101,6 @@ dependencies = [ "futures", "futures-timer", "indexmap 2.2.3", - "lazy_static", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-node-network-protocol", @@ -14148,7 +14144,6 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "lazy_static", "parking_lot 0.12.3", "polkadot-node-network-protocol", "polkadot-node-subsystem", @@ -14696,7 +14691,6 @@ dependencies = [ name = "polkadot-node-jaeger" version = "7.0.0" dependencies = [ - "lazy_static", "log", "mick-jaeger", "parity-scale-codec", @@ -14863,7 +14857,6 @@ dependencies = [ "kvdb", "kvdb-memorydb", "kvdb-shared-tests", - "lazy_static", "log", "parity-db", "parity-scale-codec", @@ -19432,7 +19425,6 @@ dependencies = [ "futures-timer", "hyper 0.14.29", "hyper-rustls 0.24.2", - "lazy_static", "log", "num_cpus", "once_cell", @@ -19815,7 +19807,6 @@ dependencies = [ "console", "criterion", "is-terminal", - "lazy_static", "libc", "log", "parity-scale-codec", @@ -19904,7 +19895,6 @@ dependencies = [ "async-channel 1.9.0", "futures", "futures-timer", - "lazy_static", "log", "parking_lot 0.12.3", "prometheus", @@ -21752,7 +21742,6 @@ name = "sp-consensus-beefy" version = "13.0.0" dependencies = [ "array-bytes", - "lazy_static", "parity-scale-codec", "scale-info", "serde", @@ -21838,7 +21827,6 @@ dependencies = [ "impl-serde", "itertools 0.11.0", "k256", - "lazy_static", "libsecp256k1", "log", "merlin", @@ -22014,7 +22002,6 @@ dependencies = [ name = "sp-core-fuzz" version = "0.0.0" dependencies = [ - "lazy_static", "libfuzzer-sys", "regex", "sp-core 28.0.0", @@ -22494,7 +22481,6 @@ name = "sp-panic-handler" version = "13.0.0" dependencies = [ "backtrace", - "lazy_static", "regex", ] @@ -23086,7 +23072,6 @@ dependencies = [ "array-bytes", "criterion", "hash-db", - "lazy_static", "memory-db", "nohash-hasher", "parity-scale-codec", @@ -27020,7 +27005,6 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "lazy_static", "log", "pallet-balances", "pallet-message-queue", @@ -27270,7 +27254,6 @@ name = "zombienet-backchannel" version = "1.0.0" dependencies = [ "futures-util", - "lazy_static", "parity-scale-codec", "reqwest 0.11.20", "serde", diff --git a/Cargo.toml b/Cargo.toml index a30b5b57d36..155cd2cf5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -826,7 +826,6 @@ kvdb-memorydb = { version = "0.13.0" } kvdb-rocksdb = { version = "0.19.0" } kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } -lazy_static = { version = "1.5.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } libp2p = { version = "0.52.4" } diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 30a232f01b3..66429625d5b 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -49,7 +49,6 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } hex-literal = { workspace = true, default-features = true } -lazy_static = { workspace = true } trie-standardmap = { workspace = true } rand = { workspace = true, default-features = true } futures = { workspace = true } diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 548231966e4..23223627ebc 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -754,12 +754,8 @@ fn message_queue_chain() { #[test] #[cfg(not(feature = "runtime-benchmarks"))] fn receive_dmp() { - lazy_static::lazy_static! { - static ref MSG: InboundDownwardMessage = InboundDownwardMessage { - sent_at: 1, - msg: b"down".to_vec(), - }; - } + static MSG: std::sync::LazyLock = + std::sync::LazyLock::new(|| InboundDownwardMessage { sent_at: 1, msg: b"down".to_vec() }); BlockTests::new() .with_relay_sproof_builder(|_, relay_block_num, sproof| match relay_block_num { @@ -771,14 +767,14 @@ fn receive_dmp() { }) .with_inherent_data(|_, relay_block_num, data| match relay_block_num { 1 => { - data.downward_messages.push(MSG.clone()); + data.downward_messages.push((*MSG).clone()); }, _ => unreachable!(), }) .add(1, || { HANDLED_DMP_MESSAGES.with(|m| { let mut m = m.borrow_mut(); - assert_eq!(&*m, &[(MSG.msg.clone())]); + assert_eq!(&*m, &[MSG.msg.clone()]); m.clear(); }); }); diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index 6924f11292d..8598481fae7 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -13,7 +13,6 @@ workspace = true codec = { workspace = true, default-features = true } paste = { workspace = true, default-features = true } log = { workspace = true } -lazy_static = { workspace = true } impl-trait-for-tuples = { workspace = true } array-bytes = { workspace = true } diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index d393d453773..bb2945dc267 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -18,12 +18,16 @@ extern crate alloc; pub use array_bytes; pub use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -pub use lazy_static::lazy_static; pub use log; pub use paste; pub use std::{ - any::type_name, collections::HashMap, error::Error, fmt, marker::PhantomData, ops::Deref, - sync::Mutex, + any::type_name, + collections::HashMap, + error::Error, + fmt, + marker::PhantomData, + ops::Deref, + sync::{LazyLock, Mutex}, }; // Substrate @@ -443,10 +447,8 @@ macro_rules! __impl_test_ext_for_relay_chain { = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } - $crate::lazy_static! { - pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> - = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); - } + pub static $global_ext: $crate::LazyLock<$crate::Mutex<$crate::RefCell<$crate::HashMap>>> + = $crate::LazyLock::new(|| $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new()))); impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { @@ -478,10 +480,10 @@ macro_rules! __impl_test_ext_for_relay_chain { v.take() }); - // Get TestExternality from lazy_static + // Get TestExternality from LazyLock let global_ext_guard = $global_ext.lock().unwrap(); - // Replace TestExternality in lazy_static by TestExternality from thread_local + // Replace TestExternality in LazyLock by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } @@ -490,10 +492,10 @@ macro_rules! __impl_test_ext_for_relay_chain { let mut global_ext_unlocked = false; - // Keep the mutex unlocked until TesExternality from lazy_static + // Keep the mutex unlocked until TesExternality from LazyLock // has been updated while !global_ext_unlocked { - // Get TesExternality from lazy_static + // Get TesExternality from LazyLock let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { @@ -506,10 +508,10 @@ macro_rules! __impl_test_ext_for_relay_chain { } } - // Now that we know that lazy_static TestExt has been updated, we lock its mutex + // Now that we know that TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); - // and set TesExternality from lazy_static into TesExternality for local_thread + // and set TesExternality from LazyLock into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { @@ -744,10 +746,8 @@ macro_rules! __impl_test_ext_for_parachain { = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } - $crate::lazy_static! { - pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> - = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); - } + pub static $global_ext: $crate::LazyLock<$crate::Mutex<$crate::RefCell<$crate::HashMap>>> + = $crate::LazyLock::new(|| $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new()))); impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { @@ -777,10 +777,10 @@ macro_rules! __impl_test_ext_for_parachain { v.take() }); - // Get TestExternality from lazy_static + // Get TestExternality from LazyLock let global_ext_guard = $global_ext.lock().unwrap(); - // Replace TestExternality in lazy_static by TestExternality from thread_local + // Replace TestExternality in LazyLock by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } @@ -789,10 +789,10 @@ macro_rules! __impl_test_ext_for_parachain { let mut global_ext_unlocked = false; - // Keep the mutex unlocked until TesExternality from lazy_static + // Keep the mutex unlocked until TesExternality from LazyLock // has been updated while !global_ext_unlocked { - // Get TesExternality from lazy_static + // Get TesExternality from LazyLock let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { @@ -805,10 +805,10 @@ macro_rules! __impl_test_ext_for_parachain { } } - // Now that we know that lazy_static TestExt has been updated, we lock its mutex + // Now that we know that TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); - // and set TesExternality from lazy_static into TesExternality for local_thread + // and set TesExternality from LazyLock into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index 90a6c80e3d0..8615041bdaf 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -11,7 +11,6 @@ workspace = true [dependencies] mick-jaeger = { workspace = true } -lazy_static = { workspace = true } parking_lot = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } diff --git a/polkadot/node/jaeger/src/lib.rs b/polkadot/node/jaeger/src/lib.rs index 7de45860681..2ce5b8b6eb6 100644 --- a/polkadot/node/jaeger/src/lib.rs +++ b/polkadot/node/jaeger/src/lib.rs @@ -61,11 +61,12 @@ use self::spans::TraceIdentifier; use sp_core::traits::SpawnNamed; use parking_lot::RwLock; -use std::{result, sync::Arc}; +use std::{ + result, + sync::{Arc, LazyLock}, +}; -lazy_static::lazy_static! { - static ref INSTANCE: RwLock = RwLock::new(Jaeger::None); -} +static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(Jaeger::None)); /// Stateful convenience wrapper around [`mick_jaeger`]. pub enum Jaeger { diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index ccf1b5daad7..b4dcafe09eb 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -38,5 +38,4 @@ sp-tracing = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } futures-timer = { workspace = true } assert_matches = { workspace = true } -lazy_static = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/dispute-distribution/src/tests/mock.rs b/polkadot/node/network/dispute-distribution/src/tests/mock.rs index ccc050233e8..baa857e2eb6 100644 --- a/polkadot/node/network/dispute-distribution/src/tests/mock.rs +++ b/polkadot/node/network/dispute-distribution/src/tests/mock.rs @@ -19,12 +19,11 @@ use std::{ collections::{HashMap, HashSet}, - sync::Arc, + sync::{Arc, LazyLock}, time::Instant, }; use async_trait::async_trait; -use lazy_static::lazy_static; use polkadot_node_network_protocol::{authority_discovery::AuthorityDiscovery, PeerId}; use sc_keystore::LocalKeystore; @@ -60,64 +59,60 @@ pub const ALICE_INDEX: ValidatorIndex = ValidatorIndex(1); pub const BOB_INDEX: ValidatorIndex = ValidatorIndex(2); pub const CHARLIE_INDEX: ValidatorIndex = ValidatorIndex(3); -lazy_static! { - /// Mocked `AuthorityDiscovery` service. -pub static ref MOCK_AUTHORITY_DISCOVERY: MockAuthorityDiscovery = MockAuthorityDiscovery::new(); +pub static MOCK_AUTHORITY_DISCOVERY: LazyLock = + LazyLock::new(|| MockAuthorityDiscovery::new()); // Creating an innocent looking `SessionInfo` is really expensive in a debug build. Around // 700ms on my machine, We therefore cache those keys here: -pub static ref MOCK_VALIDATORS_DISCOVERY_KEYS: HashMap = - MOCK_VALIDATORS - .iter() - .chain(MOCK_AUTHORITIES_NEXT_SESSION.iter()) - .map(|v| (*v, v.public().into())) - .collect() -; -pub static ref FERDIE_DISCOVERY_KEY: AuthorityDiscoveryId = - MOCK_VALIDATORS_DISCOVERY_KEYS.get(&Sr25519Keyring::Ferdie).unwrap().clone(); - -pub static ref MOCK_SESSION_INFO: SessionInfo = - SessionInfo { - validators: MOCK_VALIDATORS.iter().take(4).map(|k| k.public().into()).collect(), - discovery_keys: MOCK_VALIDATORS +pub static MOCK_VALIDATORS_DISCOVERY_KEYS: LazyLock> = + LazyLock::new(|| { + MOCK_VALIDATORS .iter() - .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) - .collect(), - assignment_keys: vec![], - validator_groups: Default::default(), - n_cores: 0, - 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], - }; + .chain(MOCK_AUTHORITIES_NEXT_SESSION.iter()) + .map(|v| (*v, v.public().into())) + .collect() + }); +pub static FERDIE_DISCOVERY_KEY: LazyLock = + LazyLock::new(|| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&Sr25519Keyring::Ferdie).unwrap().clone()); + +pub static MOCK_SESSION_INFO: LazyLock = LazyLock::new(|| SessionInfo { + validators: MOCK_VALIDATORS.iter().take(4).map(|k| k.public().into()).collect(), + discovery_keys: MOCK_VALIDATORS + .iter() + .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) + .collect(), + assignment_keys: vec![], + validator_groups: Default::default(), + n_cores: 0, + 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], +}); /// `SessionInfo` for the second session. (No more validators, but two more authorities. -pub static ref MOCK_NEXT_SESSION_INFO: SessionInfo = - SessionInfo { - discovery_keys: - MOCK_AUTHORITIES_NEXT_SESSION - .iter() - .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) - .collect(), - validators: Default::default(), - assignment_keys: vec![], - validator_groups: Default::default(), - n_cores: 0, - 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], - }; -} +pub static MOCK_NEXT_SESSION_INFO: LazyLock = LazyLock::new(|| SessionInfo { + discovery_keys: MOCK_AUTHORITIES_NEXT_SESSION + .iter() + .map(|k| MOCK_VALIDATORS_DISCOVERY_KEYS.get(&k).unwrap().clone()) + .collect(), + validators: Default::default(), + assignment_keys: vec![], + validator_groups: Default::default(), + n_cores: 0, + 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], +}); pub fn make_candidate_receipt(relay_parent: Hash) -> CandidateReceipt { CandidateReceipt { diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index 83fdc7e2619..c8c19e5de07 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -39,5 +39,4 @@ polkadot-node-subsystem-test-helpers = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } parking_lot = { workspace = true, default-features = true } -lazy_static = { workspace = true } quickcheck = { workspace = true, default-features = true } diff --git a/polkadot/node/network/gossip-support/src/tests.rs b/polkadot/node/network/gossip-support/src/tests.rs index 09622254f52..399f29db67d 100644 --- a/polkadot/node/network/gossip-support/src/tests.rs +++ b/polkadot/node/network/gossip-support/src/tests.rs @@ -16,12 +16,11 @@ //! Unit tests for Gossip Support Subsystem. -use std::{collections::HashSet, time::Duration}; +use std::{collections::HashSet, sync::LazyLock, time::Duration}; use assert_matches::assert_matches; use async_trait::async_trait; use futures::{executor, future, Future}; -use lazy_static::lazy_static; use quickcheck::quickcheck; use rand::seq::SliceRandom as _; @@ -56,39 +55,29 @@ const AUTHORITY_KEYRINGS: &[Sr25519Keyring] = &[ Sr25519Keyring::Ferdie, ]; -lazy_static! { - static ref AUTHORITIES: Vec = - AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect(); +static AUTHORITIES: LazyLock> = + LazyLock::new(|| AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect()); - static ref AUTHORITIES_WITHOUT_US: Vec = { - let mut a = AUTHORITIES.clone(); - a.pop(); // remove FERDIE. - a - }; - - static ref PAST_PRESENT_FUTURE_AUTHORITIES: Vec = { - (0..50) - .map(|_| AuthorityDiscoveryPair::generate().0.public()) - .chain(AUTHORITIES.clone()) - .collect() - }; +static AUTHORITIES_WITHOUT_US: LazyLock> = LazyLock::new(|| { + let mut a = AUTHORITIES.clone(); + a.pop(); // remove FERDIE. + a +}); - // [2 6] - // [4 5] - // [1 3] - // [0 ] +static PAST_PRESENT_FUTURE_AUTHORITIES: LazyLock> = LazyLock::new(|| { + (0..50) + .map(|_| AuthorityDiscoveryPair::generate().0.public()) + .chain(AUTHORITIES.clone()) + .collect() +}); - static ref EXPECTED_SHUFFLING: Vec = vec![6, 4, 0, 5, 2, 3, 1]; +static EXPECTED_SHUFFLING: LazyLock> = LazyLock::new(|| vec![6, 4, 0, 5, 2, 3, 1]); - static ref ROW_NEIGHBORS: Vec = vec![ - ValidatorIndex::from(2), - ]; +static ROW_NEIGHBORS: LazyLock> = + LazyLock::new(|| vec![ValidatorIndex::from(2)]); - static ref COLUMN_NEIGHBORS: Vec = vec![ - ValidatorIndex::from(3), - ValidatorIndex::from(5), - ]; -} +static COLUMN_NEIGHBORS: LazyLock> = + LazyLock::new(|| vec![ValidatorIndex::from(3), ValidatorIndex::from(5)]); type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 5b6bcdaa9d7..99d3a3e515b 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -105,7 +105,7 @@ pub const MAX_FINALITY_LAG: u32 = 500; /// Type of a session window size. /// /// We are not using `NonZeroU32` here because `expect` and `unwrap` are not yet const, so global -/// constants of `SessionWindowSize` would require `lazy_static` in that case. +/// constants of `SessionWindowSize` would require `LazyLock` in that case. /// /// See: #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index a7157d1b5b7..3bd3892ba60 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -48,7 +48,6 @@ assert_matches = { workspace = true } futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -lazy_static = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } kvdb-shared-tests = { workspace = true } tempfile = { workspace = true } diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index a9bf1f5ef09..56c49a1ec30 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -16,7 +16,6 @@ tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = t url = { workspace = true } tokio-tungstenite = { workspace = true } futures-util = { workspace = true, default-features = true } -lazy_static = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } reqwest = { features = ["rustls-tls"], workspace = true } thiserror = { workspace = true } diff --git a/polkadot/node/zombienet-backchannel/src/lib.rs b/polkadot/node/zombienet-backchannel/src/lib.rs index 9068b03399c..080dcf1c2b7 100644 --- a/polkadot/node/zombienet-backchannel/src/lib.rs +++ b/polkadot/node/zombienet-backchannel/src/lib.rs @@ -21,7 +21,6 @@ use codec; use futures_util::{SinkExt, StreamExt}; -use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use std::{env, sync::Mutex}; use tokio::sync::broadcast; @@ -30,9 +29,7 @@ use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; mod errors; use errors::BackchannelError; -lazy_static! { - pub static ref ZOMBIENET_BACKCHANNEL: Mutex> = Mutex::new(None); -} +pub static ZOMBIENET_BACKCHANNEL: Mutex> = Mutex::new(None); #[derive(Debug)] pub struct ZombienetBackchannel { diff --git a/prdoc/pr_5716.prdoc b/prdoc/pr_5716.prdoc new file mode 100644 index 00000000000..a9866623372 --- /dev/null +++ b/prdoc/pr_5716.prdoc @@ -0,0 +1,37 @@ +# 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: Replace `lazy_static` with `LazyLock` + +doc: + - audience: Node Dev + description: | + Replace all lazy_static usages with LazyLock from the Rust standard library. This will bring us less dependencies. + +crates: + - name: sp-core + bump: patch + - name: sp-panic-handler + bump: patch + - name: sp-trie + bump: patch + - name: sc-utils + bump: major + - name: cumulus-pallet-parachain-system + bump: patch + - name: sp-consensus-beefy + bump: patch + - name: polkadot-node-primitives + bump: patch + - name: polkadot-node-jaeger + bump: patch + - name: frame-benchmarking-cli + bump: major + - name: sc-offchain + bump: patch + - name: polkadot-dispute-distribution + bump: patch + - name: polkadot-gossip-support + bump: patch + - name: xcm-emulator + bump: patch diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 88ea908abc2..8c6556da682 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -40,7 +40,6 @@ hash-db = { workspace = true, default-features = true } tempfile = { workspace = true } fs_extra = { workspace = true } rand = { features = ["small_rng"], workspace = true, default-features = true } -lazy_static = { workspace = true } parity-db = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } diff --git a/substrate/bin/node/bench/src/trie.rs b/substrate/bin/node/bench/src/trie.rs index 09ab405c03b..402a186767e 100644 --- a/substrate/bin/node/bench/src/trie.rs +++ b/substrate/bin/node/bench/src/trie.rs @@ -20,11 +20,14 @@ use hash_db::Prefix; use kvdb::KeyValueDB; -use lazy_static::lazy_static; use rand::Rng; use sp_state_machine::Backend as _; use sp_trie::{trie_types::TrieDBMutBuilderV1, TrieMut as _}; -use std::{borrow::Cow, collections::HashMap, sync::Arc}; +use std::{ + borrow::Cow, + collections::HashMap, + sync::{Arc, LazyLock}, +}; use node_primitives::Hash; @@ -57,10 +60,8 @@ pub enum DatabaseSize { Huge, } -lazy_static! { - static ref KUSAMA_STATE_DISTRIBUTION: SizePool = - SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION); -} +static KUSAMA_STATE_DISTRIBUTION: LazyLock = + LazyLock::new(|| SizePool::from_histogram(crate::state_sizes::KUSAMA_STATE_DISTRIBUTION)); impl DatabaseSize { /// Should be multiple of SAMPLE_SIZE! diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 4b5b04cca62..bbbe7018d10 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -22,7 +22,10 @@ codec = { features = ["derive"], workspace = true, default-features = true } fnv = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -hyperv14 = { features = ["http2", "stream"], workspace = true, default-features = true } +hyperv14 = { features = [ + "http2", + "stream", +], workspace = true, default-features = true } hyper-rustls = { features = ["http2"], workspace = true } num_cpus = { workspace = true } once_cell = { workspace = true } @@ -46,7 +49,6 @@ log = { workspace = true, default-features = true } [dev-dependencies] async-trait = { workspace = true } -lazy_static = { workspace = true } tokio = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-db = { default-features = true, workspace = true } diff --git a/substrate/client/offchain/src/api/http.rs b/substrate/client/offchain/src/api/http.rs index fda5728b0d0..73407b1359d 100644 --- a/substrate/client/offchain/src/api/http.rs +++ b/substrate/client/offchain/src/api/http.rs @@ -763,14 +763,12 @@ mod tests { use crate::api::timestamp; use core::convert::Infallible; use futures::{future, StreamExt}; - use lazy_static::lazy_static; use sp_core::offchain::{Duration, Externalities, HttpError, HttpRequestId, HttpRequestStatus}; + use std::sync::LazyLock; - // Using lazy_static to avoid spawning lots of different SharedClients, + // Using LazyLock to avoid spawning lots of different SharedClients, // as spawning a SharedClient is CPU-intensive and opens lots of fds. - lazy_static! { - static ref SHARED_CLIENT: SharedClient = SharedClient::new(); - } + static SHARED_CLIENT: LazyLock = LazyLock::new(|| SharedClient::new()); // Returns an `HttpApi` whose worker is ran in the background, and a `SocketAddr` to an HTTP // server that runs in the background as well. diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 09571610a3a..b8f5e40caf8 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -20,7 +20,6 @@ console = { workspace = true } is-terminal = { workspace = true } chrono = { workspace = true } codec = { workspace = true, default-features = true } -lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } @@ -29,7 +28,10 @@ serde = { workspace = true, default-features = true } thiserror = { workspace = true } tracing = { workspace = true, default-features = true } tracing-log = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "parking_lot"] } +tracing-subscriber = { workspace = true, features = [ + "env-filter", + "parking_lot", +] } sc-client-api = { workspace = true, default-features = true } sc-tracing-proc-macro = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } diff --git a/substrate/client/utils/Cargo.toml b/substrate/client/utils/Cargo.toml index 6c3a2228952..485261058d5 100644 --- a/substrate/client/utils/Cargo.toml +++ b/substrate/client/utils/Cargo.toml @@ -16,7 +16,6 @@ workspace = true async-channel = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -lazy_static = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } prometheus = { workspace = true } diff --git a/substrate/client/utils/src/metrics.rs b/substrate/client/utils/src/metrics.rs index 308e90cb253..9b6e1e47039 100644 --- a/substrate/client/utils/src/metrics.rs +++ b/substrate/client/utils/src/metrics.rs @@ -18,42 +18,49 @@ //! Metering primitives and globals -use lazy_static::lazy_static; use prometheus::{ core::{AtomicU64, GenericCounter, GenericGauge}, Error as PrometheusError, Registry, }; +use std::sync::LazyLock; use prometheus::{ core::{GenericCounterVec, GenericGaugeVec}, Opts, }; -lazy_static! { - pub static ref TOKIO_THREADS_TOTAL: GenericCounter = - GenericCounter::new("substrate_tokio_threads_total", "Total number of threads created") - .expect("Creating of statics doesn't fail. qed"); - pub static ref TOKIO_THREADS_ALIVE: GenericGauge = - GenericGauge::new("substrate_tokio_threads_alive", "Number of threads alive right now") - .expect("Creating of statics doesn't fail. qed"); -} +pub static TOKIO_THREADS_TOTAL: LazyLock> = LazyLock::new(|| { + GenericCounter::new("substrate_tokio_threads_total", "Total number of threads created") + .expect("Creating of statics doesn't fail. qed") +}); -lazy_static! { - pub static ref UNBOUNDED_CHANNELS_COUNTER: GenericCounterVec = GenericCounterVec::new( - Opts::new( - "substrate_unbounded_channel_len", - "Items sent/received/dropped on each mpsc::unbounded instance" - ), - &["entity", "action"], // name of channel, send|received|dropped - ).expect("Creating of statics doesn't fail. qed"); - pub static ref UNBOUNDED_CHANNELS_SIZE: GenericGaugeVec = GenericGaugeVec::new( +pub static TOKIO_THREADS_ALIVE: LazyLock> = LazyLock::new(|| { + GenericGauge::new("substrate_tokio_threads_alive", "Number of threads alive right now") + .expect("Creating of statics doesn't fail. qed") +}); + +pub static UNBOUNDED_CHANNELS_COUNTER: LazyLock> = + LazyLock::new(|| { + GenericCounterVec::new( + Opts::new( + "substrate_unbounded_channel_len", + "Items sent/received/dropped on each mpsc::unbounded instance", + ), + &["entity", "action"], // name of channel, send|received|dropped + ) + .expect("Creating of statics doesn't fail. qed") + }); + +pub static UNBOUNDED_CHANNELS_SIZE: LazyLock> = LazyLock::new(|| { + GenericGaugeVec::new( Opts::new( "substrate_unbounded_channel_size", "Size (number of messages to be processed) of each mpsc::unbounded instance", ), &["entity"], // name of channel - ).expect("Creating of statics doesn't fail. qed"); -} + ) + .expect("Creating of statics doesn't fail. qed") +}); pub static SENT_LABEL: &'static str = "send"; pub static RECEIVED_LABEL: &'static str = "received"; diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 57ddab9a70c..13d80683c85 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -28,7 +28,6 @@ sp-runtime = { workspace = true } sp-keystore = { workspace = true } sp-weights = { workspace = true } strum = { features = ["derive"], workspace = true } -lazy_static = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } @@ -38,7 +37,6 @@ w3f-bls = { features = ["std"], workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "dep:lazy_static", "scale-info/std", "serde/std", "sp-api/std", diff --git a/substrate/primitives/consensus/beefy/src/test_utils.rs b/substrate/primitives/consensus/beefy/src/test_utils.rs index bd335ede489..4460bcefd45 100644 --- a/substrate/primitives/consensus/beefy/src/test_utils.rs +++ b/substrate/primitives/consensus/beefy/src/test_utils.rs @@ -26,7 +26,7 @@ use sp_core::{ecdsa, Pair}; use sp_runtime::traits::{BlockNumber, Hash, Header as HeaderT}; use codec::Encode; -use std::{collections::HashMap, marker::PhantomData}; +use std::{collections::HashMap, marker::PhantomData, sync::LazyLock}; use strum::IntoEnumIterator; /// Set of test accounts using [`crate::ecdsa_crypto`] types. @@ -111,12 +111,15 @@ where } } -lazy_static::lazy_static! { - 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(); -} +static PRIVATE_KEYS: LazyLock, ecdsa_crypto::Pair>> = + LazyLock::new(|| Keyring::iter().map(|i| (i.clone(), i.pair())).collect()); +static PUBLIC_KEYS: LazyLock, ecdsa_crypto::Public>> = + LazyLock::new(|| { + 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 { diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 51cbfa3bdfb..f6bc17bccac 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -26,10 +26,14 @@ impl-serde = { optional = true, workspace = true } hash-db = { workspace = true } hash256-std-hasher = { workspace = true } bs58 = { optional = true, workspace = true } -rand = { features = ["small_rng"], optional = true, workspace = true, default-features = true } +rand = { features = [ + "small_rng", +], optional = true, workspace = true, default-features = true } substrate-bip39 = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 -bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = ["alloc"] } +bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = [ + "alloc", +] } zeroize = { workspace = true } secrecy = { features = ["alloc"], workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } @@ -58,17 +62,21 @@ sp-runtime-interface = { workspace = true } # k256 crate, better portability, intended to be used in substrate-runtimes (no-std) k256 = { features = ["alloc", "ecdsa"], workspace = true } # secp256k1 crate, better performance, intended to be used on host side (std) -secp256k1 = { features = ["alloc", "recovery"], optional = true, workspace = true } +secp256k1 = { features = [ + "alloc", + "recovery", +], optional = true, workspace = true } # bls crypto w3f-bls = { optional = true, workspace = true } # bandersnatch crypto -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = ["substrate-curves"], optional = true } +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", default-features = false, features = [ + "substrate-curves", +], optional = true } [dev-dependencies] criterion = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -lazy_static = { workspace = true } regex = { workspace = true } [[bench]] diff --git a/substrate/primitives/core/fuzz/Cargo.toml b/substrate/primitives/core/fuzz/Cargo.toml index 46dfe8d483b..b6ef395adf9 100644 --- a/substrate/primitives/core/fuzz/Cargo.toml +++ b/substrate/primitives/core/fuzz/Cargo.toml @@ -11,7 +11,6 @@ workspace = true cargo-fuzz = true [dependencies] -lazy_static = { workspace = true } libfuzzer-sys = { workspace = true } regex = { workspace = true } diff --git a/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs b/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs index e2d9e2fc8b0..ac84faf2d89 100644 --- a/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs +++ b/substrate/primitives/core/fuzz/fuzz_targets/fuzz_address_uri.rs @@ -24,11 +24,12 @@ extern crate sp_core; use libfuzzer_sys::fuzz_target; use regex::Regex; use sp_core::crypto::AddressUri; +use std::sync::LazyLock; -lazy_static::lazy_static! { - static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") - .expect("constructed from known-good static value; qed"); -} +static SECRET_PHRASE_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") + .expect("constructed from known-good static value; qed") +}); fuzz_target!(|input: &str| { let regex_result = SECRET_PHRASE_REGEX.captures(input); diff --git a/substrate/primitives/core/src/address_uri.rs b/substrate/primitives/core/src/address_uri.rs index bbe31b7553b..4877250cf3a 100644 --- a/substrate/primitives/core/src/address_uri.rs +++ b/substrate/primitives/core/src/address_uri.rs @@ -196,11 +196,12 @@ impl<'a> AddressUri<'a> { mod tests { use super::*; use regex::Regex; + use std::sync::LazyLock; - lazy_static::lazy_static! { - static ref SECRET_PHRASE_REGEX: Regex = Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") - .expect("constructed from known-good static value; qed"); - } + static SECRET_PHRASE_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"^(?P[a-zA-Z0-9 ]+)?(?P(//?[^/]+)*)(///(?P.*))?$") + .expect("constructed from known-good static value; qed") + }); fn check_with_regex(input: &str) { let regex_result = SECRET_PHRASE_REGEX.captures(input); diff --git a/substrate/primitives/panic-handler/Cargo.toml b/substrate/primitives/panic-handler/Cargo.toml index 395e788eb24..012fe08f7cd 100644 --- a/substrate/primitives/panic-handler/Cargo.toml +++ b/substrate/primitives/panic-handler/Cargo.toml @@ -18,5 +18,4 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] backtrace = { workspace = true } -lazy_static = { workspace = true } regex = { workspace = true } diff --git a/substrate/primitives/panic-handler/src/lib.rs b/substrate/primitives/panic-handler/src/lib.rs index e2a9bfa195a..c4a7eb8dc67 100644 --- a/substrate/primitives/panic-handler/src/lib.rs +++ b/substrate/primitives/panic-handler/src/lib.rs @@ -31,6 +31,7 @@ use std::{ io::{self, Write}, marker::PhantomData, panic::{self, PanicInfo}, + sync::LazyLock, thread, }; @@ -128,8 +129,9 @@ impl Drop for AbortGuard { // NOTE: When making any changes here make sure to also change this function in `sc-tracing`. fn strip_control_codes(input: &str) -> std::borrow::Cow { - lazy_static::lazy_static! { - static ref RE: Regex = Regex::new(r#"(?x) + static RE: LazyLock = LazyLock::new(|| { + Regex::new( + r#"(?x) \x1b\[[^m]+m| # VT100 escape codes [ \x00-\x09\x0B-\x1F # ASCII control codes / Unicode C0 control codes, except \n @@ -138,8 +140,10 @@ fn strip_control_codes(input: &str) -> std::borrow::Cow { \u{202A}-\u{202E} # Unicode left-to-right / right-to-left control characters \u{2066}-\u{2069} # Same as above ] - "#).expect("regex parsing doesn't fail; qed"); - } + "#, + ) + .expect("regex parsing doesn't fail; qed") + }); RE.replace_all(input, "") } diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index a28f29b0158..7f27bb09729 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -24,7 +24,6 @@ harness = false ahash = { optional = true, workspace = true } codec = { workspace = true } hash-db = { workspace = true } -lazy_static = { optional = true, workspace = true } memory-db = { workspace = true } nohash-hasher = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } @@ -51,7 +50,6 @@ std = [ "ahash", "codec/std", "hash-db/std", - "lazy_static", "memory-db/std", "nohash-hasher", "parking_lot", diff --git a/substrate/primitives/trie/src/cache/shared_cache.rs b/substrate/primitives/trie/src/cache/shared_cache.rs index e3ba94a2af7..7f6da80fe95 100644 --- a/substrate/primitives/trie/src/cache/shared_cache.rs +++ b/substrate/primitives/trie/src/cache/shared_cache.rs @@ -25,17 +25,15 @@ use schnellru::LruMap; use std::{ collections::{hash_map::Entry as SetEntry, HashMap}, hash::{BuildHasher, Hasher as _}, - sync::Arc, + sync::{Arc, LazyLock}, }; use trie_db::{node::NodeOwned, CachedValue}; -lazy_static::lazy_static! { - static ref RANDOM_STATE: ahash::RandomState = { - use rand::Rng; - let mut rng = rand::thread_rng(); - ahash::RandomState::generate_with(rng.gen(), rng.gen(), rng.gen(), rng.gen()) - }; -} +static RANDOM_STATE: LazyLock = LazyLock::new(|| { + use rand::Rng; + let mut rng = rand::thread_rng(); + ahash::RandomState::generate_with(rng.gen(), rng.gen(), rng.gen(), rng.gen()) +}); pub struct SharedNodeCacheLimiter { /// The maximum size (in bytes) the cache can hold inline. diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 4e88e3360e3..ee5522f5bc0 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -24,7 +24,6 @@ comfy-table = { workspace = true } handlebars = { workspace = true } Inflector = { workspace = true } itertools = { workspace = true } -lazy_static = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } rand = { features = ["small_rng"], workspace = true, default-features = true } diff --git a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs index ee1d490b854..f542eb60520 100644 --- a/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs +++ b/substrate/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -17,19 +17,17 @@ //! Contains types to define hardware requirements. -use lazy_static::lazy_static; use sc_sysinfo::Requirements; +use std::sync::LazyLock; -lazy_static! { - /// The hardware requirements as measured on reference hardware. - /// - /// These values are provided by Parity, however it is possible - /// to use your own requirements if you are running a custom chain. - pub static ref SUBSTRATE_REFERENCE_HARDWARE: Requirements = { - let raw = include_bytes!("reference_hardware.json").as_slice(); - serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") - }; -} +/// The hardware requirements as measured on reference hardware. +/// +/// These values are provided by Parity, however it is possible +/// to use your own requirements if you are running a custom chain. +pub static SUBSTRATE_REFERENCE_HARDWARE: LazyLock = LazyLock::new(|| { + let raw = include_bytes!("reference_hardware.json").as_slice(); + serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") +}); #[cfg(test)] mod tests { -- GitLab From 8279d1046cca51a317dec15df5a9b29240545163 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:19:56 +0200 Subject: [PATCH 027/127] Bump syn from 2.0.77 to 2.0.79 in the known_good_semver group (#5864) Bumps the known_good_semver group with 1 update: [syn](https://github.com/dtolnay/syn). Updates `syn` from 2.0.77 to 2.0.79

Release notes

Sourced from syn's releases.

2.0.79

  • Fix infinite loop on parsing chained ranges (#1741)
  • Fix panic in parsing use items containing absolute paths (#1742)

2.0.78

  • Fix infinite loop on chained comparison (#1739)
Commits
  • 732e6e3 Release 2.0.79
  • af63396 Merge pull request #1742 from dtolnay/usecrateroot
  • 31e8632 Fix construction of UseGroup containing crate roots
  • 037861a Merge pull request #1741 from dtolnay/binoploop
  • 8df4dd0 Force cursor to advance in parse_expr calls
  • 09d020f Release 2.0.78
  • 7eaebfb Merge pull request #1739 from dtolnay/chainedcomparison
  • b3d2886 Fix infinite loop on chained comparison
  • 3f3d0c5 Add regression test for issue 1738
  • 346efae Touch up PR 1737
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=syn&package-manager=cargo&previous-version=2.0.77&new-version=2.0.79)](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 | 188 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 95 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eb88c85bc8..a572c37a406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,7 +168,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "syn-solidity", "tiny-keccak", ] @@ -295,7 +295,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -753,7 +753,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "synstructure 0.13.1", ] @@ -776,7 +776,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1380,7 +1380,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1397,7 +1397,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -1612,7 +1612,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -3031,7 +3031,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4388,7 +4388,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4940,7 +4940,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4980,7 +4980,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scratch", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -4997,7 +4997,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5045,7 +5045,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5067,7 +5067,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5184,7 +5184,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5195,7 +5195,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5206,7 +5206,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5314,7 +5314,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5375,7 +5375,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "regex", - "syn 2.0.77", + "syn 2.0.79", "termcolor", "toml 0.8.12", "walkdir", @@ -5606,7 +5606,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5626,7 +5626,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5637,7 +5637,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5852,7 +5852,7 @@ dependencies = [ "prettyplease", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -5924,7 +5924,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6267,7 +6267,7 @@ dependencies = [ "quote 1.0.37", "scale-info", "sp-arithmetic 23.0.0", - "syn 2.0.77", + "syn 2.0.79", "trybuild", ] @@ -6481,7 +6481,7 @@ dependencies = [ "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "static_assertions", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6492,7 +6492,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6501,7 +6501,7 @@ version = "11.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -6757,7 +6757,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -8215,7 +8215,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -8947,7 +8947,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9364,7 +9364,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9378,7 +9378,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9389,7 +9389,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9400,7 +9400,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -9746,7 +9746,7 @@ dependencies = [ "cfg-if", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -10350,7 +10350,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -10526,7 +10526,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -11306,7 +11306,7 @@ version = "18.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -12410,7 +12410,7 @@ version = "0.1.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -12655,7 +12655,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-runtime 31.0.1", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -13762,7 +13762,7 @@ dependencies = [ "pest_meta", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -13803,7 +13803,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16281,7 +16281,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16293,7 +16293,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16305,7 +16305,7 @@ dependencies = [ "polkavm-common 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16315,7 +16315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16325,7 +16325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16335,7 +16335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf952e05bc5ce7d81293bae18cb44c271c78615b201d75e983cdcc40d5c6ef1" dependencies = [ "polkavm-derive-impl 0.11.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16552,7 +16552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2 1.0.86", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16643,7 +16643,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16654,7 +16654,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16735,7 +16735,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16817,7 +16817,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.4", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -16838,7 +16838,7 @@ dependencies = [ "prost 0.13.2", "prost-types 0.13.2", "regex", - "syn 2.0.77", + "syn 2.0.79", "tempfile", ] @@ -16865,7 +16865,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -16878,7 +16878,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -17347,7 +17347,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -17946,7 +17946,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.0", - "syn 2.0.77", + "syn 2.0.79", "unicode-ident", ] @@ -18474,7 +18474,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -19835,7 +19835,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -20014,7 +20014,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "scale-info", - "syn 2.0.77", + "syn 2.0.79", "thiserror", ] @@ -20364,7 +20364,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -20466,7 +20466,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -21470,7 +21470,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -21485,7 +21485,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22095,7 +22095,7 @@ version = "0.1.0" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22106,7 +22106,7 @@ checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22124,7 +22124,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22133,7 +22133,7 @@ version = "14.0.0" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22144,7 +22144,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22705,7 +22705,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22717,7 +22717,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -22731,7 +22731,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23206,7 +23206,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "sp-version 29.0.0", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23218,7 +23218,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23683,7 +23683,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -23696,7 +23696,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24185,7 +24185,7 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.77", + "syn 2.0.79", "thiserror", "tokio", ] @@ -24248,7 +24248,7 @@ dependencies = [ "quote 1.0.37", "scale-typegen", "subxt-codegen", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24401,9 +24401,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", @@ -24419,7 +24419,7 @@ dependencies = [ "paste", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24448,7 +24448,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24578,7 +24578,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24753,7 +24753,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -24929,7 +24929,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25205,7 +25205,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25247,7 +25247,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -25887,7 +25887,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -25921,7 +25921,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -27055,7 +27055,7 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm", - "syn 2.0.77", + "syn 2.0.79", "trybuild", ] @@ -27226,7 +27226,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] @@ -27246,7 +27246,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 155cd2cf5ba..e2c6d6c8ded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1304,7 +1304,7 @@ substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } subxt = { version = "0.37", default-features = false } subxt-signer = { version = "0.37" } -syn = { version = "2.0.77" } +syn = { version = "2.0.79" } sysinfo = { version = "0.30" } tar = { version = "0.4" } tempfile = { version = "3.8.1" } -- GitLab From 1617852a2f1caec796f2b218aecae2facaeacad6 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Tue, 1 Oct 2024 14:39:01 +0200 Subject: [PATCH 028/127] Remove ValidateFromChainState (#5707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR removes the `CandidateValidationMessage::ValidateFromChainState`, which was previously used by backing, but is no longer relevant since initial async backing implementation https://github.com/paritytech/polkadot/pull/5557. Fixes https://github.com/paritytech/polkadot-sdk/issues/5643 ## Integration This change should not affect downstream projects since `ValidateFromChainState` was already unused. ## Review Notes - Removed all occurrences of `ValidateFromChainState`. - Moved utility functions, previously used in candidate validation tests and malus, exclusively to candidate validation tests as they are no longer used in malus. - Deleted the `polkadot_parachain_candidate_validation_validate_from_chain_state` metric from Prometheus. - Removed `Spawner` from `ReplaceValidationResult` in malus’ interceptors. - `fake_validation_error` was only used for `ValidateFromChainState` handling, while other cases directly used `InvalidCandidate::InvalidOutputs`. It has been replaced with `fake_validation_error`, with a fallback to `InvalidCandidate::InvalidOutputs`. - Updated overseer’s minimal example to replace `ValidateFromChainState` with `ValidateFromExhaustive`. --- .../node/core/candidate-validation/src/lib.rs | 195 +----------------- .../core/candidate-validation/src/metrics.rs | 15 -- .../core/candidate-validation/src/tests.rs | 55 ++++- .../src/variants/back_garbage_candidate.rs | 2 - polkadot/node/malus/src/variants/common.rs | 155 +------------- .../src/variants/dispute_valid_candidates.rs | 2 - .../src/variants/suggest_garbage_candidate.rs | 1 - .../node/overseer/examples/minimal-example.rs | 10 +- polkadot/node/overseer/src/tests.rs | 12 +- polkadot/node/subsystem-types/src/messages.rs | 22 -- .../src/types/overseer-protocol.md | 16 -- prdoc/pr_5707.prdoc | 19 ++ 12 files changed, 97 insertions(+), 407 deletions(-) create mode 100644 prdoc/pr_5707.prdoc diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index a9732e93441..50505d73391 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -46,8 +46,8 @@ use polkadot_primitives::{ DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT, }, AuthorityDiscoveryId, CandidateCommitments, CandidateDescriptor, CandidateEvent, - CandidateReceipt, ExecutorParams, Hash, OccupiedCoreAssumption, PersistedValidationData, - PvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, + CandidateReceipt, ExecutorParams, Hash, PersistedValidationData, PvfExecKind, PvfPrepKind, + SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, }; use sp_application_crypto::{AppCrypto, ByteArray}; use sp_keystore::KeystorePtr; @@ -83,8 +83,7 @@ const PVF_APPROVAL_EXECUTION_RETRY_DELAY: Duration = Duration::from_secs(3); const PVF_APPROVAL_EXECUTION_RETRY_DELAY: Duration = Duration::from_millis(200); // The task queue size is chosen to be somewhat bigger than the PVF host incoming queue size -// to allow exhaustive validation messages to fall through in case the tasks are clogged with -// `ValidateFromChainState` messages awaiting data from the runtime +// to allow exhaustive validation messages to fall through in case the tasks are clogged const TASK_LIMIT: usize = 30; /// Configuration for the candidate validation subsystem @@ -155,30 +154,6 @@ where S: SubsystemSender, { match msg { - CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - .. - } => async move { - let _timer = metrics.time_validate_from_chain_state(); - let res = validate_from_chain_state( - &mut sender, - validation_host, - candidate_receipt, - pov, - executor_params, - exec_kind, - &metrics, - ) - .await; - - metrics.on_validation_event(&res); - let _ = response_sender.send(res); - } - .boxed(), CandidateValidationMessage::ValidateFromExhaustive { validation_data, validation_code, @@ -657,170 +632,6 @@ where } } -#[derive(Debug)] -enum AssumptionCheckOutcome { - Matches(PersistedValidationData, ValidationCode), - DoesNotMatch, - BadRequest, -} - -async fn check_assumption_validation_data( - sender: &mut Sender, - descriptor: &CandidateDescriptor, - assumption: OccupiedCoreAssumption, -) -> AssumptionCheckOutcome -where - Sender: SubsystemSender, -{ - let validation_data = { - let (tx, rx) = oneshot::channel(); - let d = runtime_api_request( - sender, - descriptor.relay_parent, - RuntimeApiRequest::PersistedValidationData(descriptor.para_id, assumption, tx), - rx, - ) - .await; - - match d { - Ok(None) | Err(RuntimeRequestFailed) => return AssumptionCheckOutcome::BadRequest, - Ok(Some(d)) => d, - } - }; - - let persisted_validation_data_hash = validation_data.hash(); - - if descriptor.persisted_validation_data_hash == persisted_validation_data_hash { - let (code_tx, code_rx) = oneshot::channel(); - let validation_code = runtime_api_request( - sender, - descriptor.relay_parent, - RuntimeApiRequest::ValidationCode(descriptor.para_id, assumption, code_tx), - code_rx, - ) - .await; - - match validation_code { - Ok(None) | Err(RuntimeRequestFailed) => AssumptionCheckOutcome::BadRequest, - Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v), - } - } else { - AssumptionCheckOutcome::DoesNotMatch - } -} - -async fn find_assumed_validation_data( - sender: &mut Sender, - descriptor: &CandidateDescriptor, -) -> AssumptionCheckOutcome -where - Sender: SubsystemSender, -{ - // The candidate descriptor has a `persisted_validation_data_hash` which corresponds to - // one of up to two possible values that we can derive from the state of the - // relay-parent. We can fetch these values by getting the persisted validation data - // based on the different `OccupiedCoreAssumption`s. - - const ASSUMPTIONS: &[OccupiedCoreAssumption] = &[ - OccupiedCoreAssumption::Included, - OccupiedCoreAssumption::TimedOut, - // `TimedOut` and `Free` both don't perform any speculation and therefore should be the - // same for our purposes here. In other words, if `TimedOut` matched then the `Free` must - // be matched as well. - ]; - - // Consider running these checks in parallel to reduce validation latency. - for assumption in ASSUMPTIONS { - let outcome = check_assumption_validation_data(sender, descriptor, *assumption).await; - - match outcome { - AssumptionCheckOutcome::Matches(_, _) => return outcome, - AssumptionCheckOutcome::BadRequest => return outcome, - AssumptionCheckOutcome::DoesNotMatch => continue, - } - } - - AssumptionCheckOutcome::DoesNotMatch -} - -/// Returns validation data for a given candidate. -pub async fn find_validation_data( - sender: &mut Sender, - descriptor: &CandidateDescriptor, -) -> Result, ValidationFailed> -where - Sender: SubsystemSender, -{ - match find_assumed_validation_data(sender, &descriptor).await { - AssumptionCheckOutcome::Matches(validation_data, validation_code) => - Ok(Some((validation_data, validation_code))), - AssumptionCheckOutcome::DoesNotMatch => { - // If neither the assumption of the occupied core having the para included or the - // assumption of the occupied core timing out are valid, then the - // persisted_validation_data_hash in the descriptor is not based on the relay parent and - // is thus invalid. - Ok(None) - }, - AssumptionCheckOutcome::BadRequest => - Err(ValidationFailed("Assumption Check: Bad request".into())), - } -} - -async fn validate_from_chain_state( - sender: &mut Sender, - validation_host: ValidationHost, - candidate_receipt: CandidateReceipt, - pov: Arc, - executor_params: ExecutorParams, - exec_kind: PvfExecKind, - metrics: &Metrics, -) -> Result -where - Sender: SubsystemSender, -{ - let mut new_sender = sender.clone(); - let (validation_data, validation_code) = - match find_validation_data(&mut new_sender, &candidate_receipt.descriptor).await? { - Some((validation_data, validation_code)) => (validation_data, validation_code), - None => return Ok(ValidationResult::Invalid(InvalidCandidate::BadParent)), - }; - - let validation_result = validate_candidate_exhaustive( - validation_host, - validation_data, - validation_code, - candidate_receipt.clone(), - pov, - executor_params, - exec_kind, - metrics, - ) - .await; - - if let Ok(ValidationResult::Valid(ref outputs, _)) = validation_result { - let (tx, rx) = oneshot::channel(); - match runtime_api_request( - sender, - candidate_receipt.descriptor.relay_parent, - RuntimeApiRequest::CheckValidationOutputs( - candidate_receipt.descriptor.para_id, - outputs.clone(), - tx, - ), - rx, - ) - .await - { - Ok(true) => {}, - Ok(false) => return Ok(ValidationResult::Invalid(InvalidCandidate::InvalidOutputs)), - Err(RuntimeRequestFailed) => - return Err(ValidationFailed("Check Validation Outputs: Bad request".into())), - } - } - - validation_result -} - async fn validate_candidate_exhaustive( mut validation_backend: impl ValidationBackend + Send, persisted_validation_data: PersistedValidationData, diff --git a/polkadot/node/core/candidate-validation/src/metrics.rs b/polkadot/node/core/candidate-validation/src/metrics.rs index 1459907aa59..76ccd56555f 100644 --- a/polkadot/node/core/candidate-validation/src/metrics.rs +++ b/polkadot/node/core/candidate-validation/src/metrics.rs @@ -20,7 +20,6 @@ use polkadot_node_metrics::metrics::{self, prometheus}; #[derive(Clone)] pub(crate) struct MetricsInner { pub(crate) validation_requests: prometheus::CounterVec, - pub(crate) validate_from_chain_state: prometheus::Histogram, pub(crate) validate_from_exhaustive: prometheus::Histogram, pub(crate) validate_candidate_exhaustive: prometheus::Histogram, } @@ -46,13 +45,6 @@ impl Metrics { } } - /// Provide a timer for `validate_from_chain_state` which observes on drop. - pub fn time_validate_from_chain_state( - &self, - ) -> Option { - self.0.as_ref().map(|metrics| metrics.validate_from_chain_state.start_timer()) - } - /// Provide a timer for `validate_from_exhaustive` which observes on drop. pub fn time_validate_from_exhaustive( &self, @@ -83,13 +75,6 @@ impl metrics::Metrics for Metrics { )?, registry, )?, - validate_from_chain_state: prometheus::register( - prometheus::Histogram::with_opts(prometheus::HistogramOpts::new( - "polkadot_parachain_candidate_validation_validate_from_chain_state", - "Time spent within `candidate_validation::validate_from_chain_state`", - ))?, - registry, - )?, validate_from_exhaustive: prometheus::register( prometheus::Histogram::with_opts(prometheus::HistogramOpts::new( "polkadot_parachain_candidate_validation_validate_from_exhaustive", diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 0dcd84bab6c..17ca67889f6 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -25,7 +25,8 @@ use polkadot_node_subsystem::messages::AllMessages; use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ - CoreIndex, GroupIndex, HeadData, Id as ParaId, SessionInfo, UpwardMessage, ValidatorId, + CoreIndex, GroupIndex, HeadData, Id as ParaId, OccupiedCoreAssumption, SessionInfo, + UpwardMessage, ValidatorId, }; use polkadot_primitives_test_helpers::{ dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor, @@ -34,6 +35,58 @@ use sp_core::{sr25519::Public, testing::TaskExecutor}; use sp_keyring::Sr25519Keyring; use sp_keystore::{testing::MemoryKeystore, Keystore}; +#[derive(Debug)] +enum AssumptionCheckOutcome { + Matches(PersistedValidationData, ValidationCode), + DoesNotMatch, + BadRequest, +} + +async fn check_assumption_validation_data( + sender: &mut Sender, + descriptor: &CandidateDescriptor, + assumption: OccupiedCoreAssumption, +) -> AssumptionCheckOutcome +where + Sender: SubsystemSender, +{ + let validation_data = { + let (tx, rx) = oneshot::channel(); + let d = runtime_api_request( + sender, + descriptor.relay_parent, + RuntimeApiRequest::PersistedValidationData(descriptor.para_id, assumption, tx), + rx, + ) + .await; + + match d { + Ok(None) | Err(RuntimeRequestFailed) => return AssumptionCheckOutcome::BadRequest, + Ok(Some(d)) => d, + } + }; + + let persisted_validation_data_hash = validation_data.hash(); + + if descriptor.persisted_validation_data_hash == persisted_validation_data_hash { + let (code_tx, code_rx) = oneshot::channel(); + let validation_code = runtime_api_request( + sender, + descriptor.relay_parent, + RuntimeApiRequest::ValidationCode(descriptor.para_id, assumption, code_tx), + code_rx, + ) + .await; + + match validation_code { + Ok(None) | Err(RuntimeRequestFailed) => AssumptionCheckOutcome::BadRequest, + Ok(Some(v)) => AssumptionCheckOutcome::Matches(validation_data, v), + } + } else { + AssumptionCheckOutcome::DoesNotMatch + } +} + #[test] fn correctly_checks_included_assumption() { let validation_data: PersistedValidationData = Default::default(); diff --git a/polkadot/node/malus/src/variants/back_garbage_candidate.rs b/polkadot/node/malus/src/variants/back_garbage_candidate.rs index b939a2151e2..d6f1353a46a 100644 --- a/polkadot/node/malus/src/variants/back_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/back_garbage_candidate.rs @@ -67,12 +67,10 @@ impl OverseerGen for BackGarbageCandidates { RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { - let spawner = args.spawner.clone(); let validation_filter = ReplaceValidationResult::new( FakeCandidateValidation::BackingAndApprovalValid, FakeCandidateValidationError::InvalidOutputs, f64::from(self.percentage), - SpawnGlue(spawner), ); validator_overseer_builder( diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs index eb6988f8181..50a5af63db4 100644 --- a/polkadot/node/malus/src/variants/common.rs +++ b/polkadot/node/malus/src/variants/common.rs @@ -21,7 +21,6 @@ use crate::{ shared::{MALICIOUS_POV, MALUS}, }; -use polkadot_node_core_candidate_validation::find_validation_data; use polkadot_node_primitives::{InvalidCandidate, ValidationResult}; use polkadot_primitives::{ @@ -149,59 +148,21 @@ impl Into for FakeCandidateValidationError { #[derive(Clone, Debug)] /// An interceptor which fakes validation result with a preconfigured result. /// Replaces `CandidateValidationSubsystem`. -pub struct ReplaceValidationResult { +pub struct ReplaceValidationResult { fake_validation: FakeCandidateValidation, fake_validation_error: FakeCandidateValidationError, distribution: Bernoulli, - spawner: Spawner, } -impl ReplaceValidationResult -where - Spawner: overseer::gen::Spawner, -{ +impl ReplaceValidationResult { pub fn new( fake_validation: FakeCandidateValidation, fake_validation_error: FakeCandidateValidationError, percentage: f64, - spawner: Spawner, ) -> Self { let distribution = Bernoulli::new(percentage / 100.0) .expect("Invalid probability! Percentage must be in range [0..=100]."); - Self { fake_validation, fake_validation_error, distribution, spawner } - } - - /// Creates and sends the validation response for a given candidate. Queries the runtime to - /// obtain the validation data for the given candidate. - pub fn send_validation_response( - &self, - candidate_descriptor: CandidateDescriptor, - subsystem_sender: Sender, - response_sender: oneshot::Sender>, - ) where - Sender: overseer::CandidateValidationSenderTrait + Clone + Send + 'static, - { - let _candidate_descriptor = candidate_descriptor.clone(); - let mut subsystem_sender = subsystem_sender.clone(); - let (sender, receiver) = std::sync::mpsc::channel(); - self.spawner.spawn_blocking( - "malus-get-validation-data", - Some("malus"), - Box::pin(async move { - match find_validation_data(&mut subsystem_sender, &_candidate_descriptor).await { - Ok(Some((validation_data, validation_code))) => { - sender - .send((validation_data, validation_code)) - .expect("channel is still open"); - }, - _ => { - panic!("Unable to fetch validation data"); - }, - } - }), - ); - let (validation_data, _) = receiver.recv().unwrap(); - create_validation_response(validation_data, candidate_descriptor, response_sender); + Self { fake_validation, fake_validation_error, distribution } } } @@ -251,10 +212,9 @@ fn create_validation_response( response_sender.send(result).unwrap(); } -impl MessageInterceptor for ReplaceValidationResult +impl MessageInterceptor for ReplaceValidationResult where Sender: overseer::CandidateValidationSenderTrait + Clone + Send + 'static, - Spawner: overseer::gen::Spawner + Clone + 'static, { type Message = CandidateValidationMessage; @@ -262,7 +222,7 @@ where // configuration fail them. fn intercept_incoming( &self, - subsystem_sender: &mut Sender, + _subsystem_sender: &mut Sender, msg: FromOrchestra, ) -> Option> { match msg { @@ -343,7 +303,7 @@ where match behave_maliciously { true => { let validation_result = - ValidationResult::Invalid(InvalidCandidate::InvalidOutputs); + ValidationResult::Invalid(self.fake_validation_error.into()); gum::info!( target: MALUS, @@ -390,109 +350,6 @@ where }), } }, - // Behaviour related to the backing subsystem - FromOrchestra::Communication { - msg: - CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - .. - }, - } => { - match self.fake_validation { - x if x.misbehaves_valid() && x.should_misbehave(exec_kind) => { - // Behave normally if the `PoV` is not known to be malicious. - if pov.block_data.0.as_slice() != MALICIOUS_POV { - return Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - }, - }) - } - // If the `PoV` is malicious, back the candidate with some probability `p`, - // where 'p' defaults to 100% for suggest-garbage-candidate variant. - let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); - match behave_maliciously { - true => { - gum::info!( - target: MALUS, - ?behave_maliciously, - "😈 Backing candidate with malicious PoV.", - ); - - self.send_validation_response( - candidate_receipt.descriptor, - subsystem_sender.clone(), - response_sender, - ); - None - }, - // If the `PoV` is malicious, we behave normally with some probability - // `(1-p)` - false => Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - }, - }), - } - }, - x if x.misbehaves_invalid() && x.should_misbehave(exec_kind) => { - // Maliciously set the validation result to invalid for a valid candidate - // with probability `p` - let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); - match behave_maliciously { - true => { - let validation_result = - ValidationResult::Invalid(self.fake_validation_error.into()); - gum::info!( - target: MALUS, - para_id = ?candidate_receipt.descriptor.para_id, - "😈 Maliciously sending invalid validation result: {:?}.", - &validation_result, - ); - // We're not even checking the candidate, this makes us appear - // faster than honest validators. - response_sender.send(Ok(validation_result)).unwrap(); - None - }, - // With some probability `(1-p)` we behave normally - false => { - gum::info!(target: MALUS, "😈 'Decided' to not act maliciously.",); - - Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - }, - }) - }, - } - }, - _ => Some(FromOrchestra::Communication { - msg: CandidateValidationMessage::ValidateFromChainState { - candidate_receipt, - pov, - executor_params, - exec_kind, - response_sender, - }, - }), - } - }, msg => Some(msg), } } diff --git a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs index a50fdce16e4..5422167545c 100644 --- a/polkadot/node/malus/src/variants/dispute_valid_candidates.rs +++ b/polkadot/node/malus/src/variants/dispute_valid_candidates.rs @@ -84,12 +84,10 @@ impl OverseerGen for DisputeValidCandidates { RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static, Spawner: 'static + SpawnNamed + Clone + Unpin, { - let spawner = args.spawner.clone(); let validation_filter = ReplaceValidationResult::new( self.fake_validation, self.fake_validation_error, f64::from(self.percentage), - SpawnGlue(spawner.clone()), ); validator_overseer_builder( diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs index 6921352cdfc..ab2d380fbaf 100644 --- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs @@ -315,7 +315,6 @@ impl OverseerGen for SuggestGarbageCandidates { FakeCandidateValidation::BackingAndApprovalValid, FakeCandidateValidationError::InvalidOutputs, fake_valid_probability, - SpawnGlue(args.spawner.clone()), ); validator_overseer_builder( diff --git a/polkadot/node/overseer/examples/minimal-example.rs b/polkadot/node/overseer/examples/minimal-example.rs index 86a1801a5f2..c2cb1817312 100644 --- a/polkadot/node/overseer/examples/minimal-example.rs +++ b/polkadot/node/overseer/examples/minimal-example.rs @@ -31,8 +31,10 @@ use polkadot_overseer::{ gen::{FromOrchestra, SpawnedSubsystem}, HeadSupportsParachains, SubsystemError, }; -use polkadot_primitives::{CandidateReceipt, Hash, PvfExecKind}; -use polkadot_primitives_test_helpers::{dummy_candidate_descriptor, dummy_hash}; +use polkadot_primitives::{CandidateReceipt, Hash, PersistedValidationData, PvfExecKind}; +use polkadot_primitives_test_helpers::{ + dummy_candidate_descriptor, dummy_hash, dummy_validation_code, +}; struct AlwaysSupportsParachains; @@ -73,7 +75,9 @@ impl Subsystem1 { commitments_hash: Hash::zero(), }; - let msg = CandidateValidationMessage::ValidateFromChainState { + let msg = CandidateValidationMessage::ValidateFromExhaustive { + validation_data: PersistedValidationData { ..Default::default() }, + validation_code: dummy_validation_code(), candidate_receipt, pov: PoV { block_data: BlockData(Vec::new()) }.into(), executor_params: Default::default(), diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index cb0add03e2e..47c26186b51 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -29,10 +29,10 @@ use polkadot_node_subsystem_types::messages::{ }; use polkadot_primitives::{ CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind, - PvfExecKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, + PersistedValidationData, PvfExecKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, }; use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor, dummy_candidate_receipt, dummy_hash, + dummy_candidate_descriptor, dummy_candidate_receipt, dummy_hash, dummy_validation_code, }; use crate::{ @@ -104,7 +104,9 @@ where }; let (tx, _) = oneshot::channel(); - ctx.send_message(CandidateValidationMessage::ValidateFromChainState { + ctx.send_message(CandidateValidationMessage::ValidateFromExhaustive { + validation_data: PersistedValidationData { ..Default::default() }, + validation_code: dummy_validation_code(), candidate_receipt, pov: PoV { block_data: BlockData(Vec::new()) }.into(), executor_params: Default::default(), @@ -802,7 +804,9 @@ fn test_candidate_validation_msg() -> CandidateValidationMessage { commitments_hash: Hash::zero(), }; - CandidateValidationMessage::ValidateFromChainState { + CandidateValidationMessage::ValidateFromExhaustive { + validation_data: PersistedValidationData { ..Default::default() }, + validation_code: dummy_validation_code(), candidate_receipt, pov, executor_params: Default::default(), diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index fafc700e739..a520e85f52a 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -142,28 +142,6 @@ pub enum PreCheckOutcome { /// or `Ok(ValidationResult::Invalid)`. #[derive(Debug)] pub enum CandidateValidationMessage { - /// Validate a candidate with provided parameters using relay-chain state. - /// - /// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode` - /// from the runtime API of the chain, based on the `relay_parent` - /// of the `CandidateReceipt`. - /// - /// This will also perform checking of validation outputs against the acceptance criteria. - /// - /// If there is no state available which can provide this data or the core for - /// the para is not free at the relay-parent, an error is returned. - ValidateFromChainState { - /// The candidate receipt - candidate_receipt: CandidateReceipt, - /// The proof-of-validity - pov: Arc, - /// Session's executor parameters - executor_params: ExecutorParams, - /// Execution kind, used for timeouts and retries (backing/approvals) - exec_kind: PvfExecKind, - /// The sending side of the response channel - response_sender: oneshot::Sender>, - }, /// Validate a candidate with provided, exhaustive parameters for validation. /// /// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full diff --git a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md index 317f339ddd4..6e24d969dde 100644 --- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -901,22 +901,6 @@ const APPROVAL_EXECUTION_TIMEOUT: Duration = 6 seconds; /// or `Ok(ValidationResult::Invalid)`. #[derive(Debug)] pub enum CandidateValidationMessage { - /// Validate a candidate with provided parameters using relay-chain state. - /// - /// This will implicitly attempt to gather the `PersistedValidationData` and `ValidationCode` - /// from the runtime API of the chain, based on the `relay_parent` - /// of the `CandidateDescriptor`. - /// - /// This will also perform checking of validation outputs against the acceptance criteria. - /// - /// If there is no state available which can provide this data or the core for - /// the para is not free at the relay-parent, an error is returned. - ValidateFromChainState( - CandidateDescriptor, - Arc, - Duration, // Execution timeout. - oneshot::Sender>, - ), /// Validate a candidate with provided, exhaustive parameters for validation. /// /// Explicitly provide the `PersistedValidationData` and `ValidationCode` so this can do full diff --git a/prdoc/pr_5707.prdoc b/prdoc/pr_5707.prdoc new file mode 100644 index 00000000000..11136b3c362 --- /dev/null +++ b/prdoc/pr_5707.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: Remove ValidateFromChainState + +doc: + - audience: Node Dev + description: | + Removed the `CandidateValidationMessage::ValidateFromChainState`, which was previously used by backing, but is no longer relevant since initial async backing implementation + +crates: + - name: polkadot-node-subsystem-types + bump: major + - name: polkadot-node-core-candidate-validation + bump: major + - name: polkadot + bump: patch + - name: polkadot-overseer + bump: patch -- GitLab From 3de2a92598474828b0c170856d76cfe7b8f7b45d Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 1 Oct 2024 19:38:36 +0300 Subject: [PATCH 029/127] Beefy equivocation: check all the MMR roots (#5857) Normally, the BEEFY protocol only accepts a single MMR Root entry in a commitment's payload. But to be extra careful, when validating equivocation reports, let's check all the MMR roots, if there are more. --------- Co-authored-by: Adrian Catangiu --- prdoc/pr_5857.prdoc | 19 +++++++++++ substrate/frame/beefy-mmr/src/lib.rs | 32 ++++++++++++++----- substrate/frame/beefy-mmr/src/tests.rs | 24 ++++++++++++-- .../primitives/consensus/beefy/src/payload.rs | 18 +++++++++++ 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 prdoc/pr_5857.prdoc diff --git a/prdoc/pr_5857.prdoc b/prdoc/pr_5857.prdoc new file mode 100644 index 00000000000..00ee0a8cc70 --- /dev/null +++ b/prdoc/pr_5857.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: "Beefy equivocation: check all the MMR roots" + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + This PR adjusts the logic for `report_fork_voting` exposed by `pallet-beefy`. + Normally, the BEEFY protocol only accepts a single MMR Root entry in a commitment's payload. But, in order to + be extra careful, now, when validating equivocation reports, we check all the MMR roots, if there are more. + +crates: + - name: sp-consensus-beefy + bump: patch + - name: pallet-beefy-mmr + bump: patch diff --git a/substrate/frame/beefy-mmr/src/lib.rs b/substrate/frame/beefy-mmr/src/lib.rs index 73119c3faa9..ef99bc1e9cf 100644 --- a/substrate/frame/beefy-mmr/src/lib.rs +++ b/substrate/frame/beefy-mmr/src/lib.rs @@ -258,17 +258,33 @@ where }, }; - let commitment_root = - match commitment.payload.get_decoded::>(&known_payloads::MMR_ROOT_ID) { - Some(commitment_root) => commitment_root, + let mut found_commitment_root = false; + let commitment_roots = commitment + .payload + .get_all_decoded::>(&known_payloads::MMR_ROOT_ID); + for maybe_commitment_root in commitment_roots { + match maybe_commitment_root { + Some(commitment_root) => { + found_commitment_root = true; + if canonical_prev_root != commitment_root { + // If the commitment contains an MMR root, that is not equal to + // `canonical_prev_root`, the commitment is invalid + return true; + } + }, None => { - // If the commitment doesn't contain any MMR root, while the proof is valid, - // the commitment is invalid - return true + // If the commitment contains an MMR root, that can't be decoded, it is invalid. + return true; }, - }; + } + } + if !found_commitment_root { + // If the commitment doesn't contain any MMR root, while the proof is valid, + // the commitment is invalid + return true; + } - canonical_prev_root != commitment_root + false } } diff --git a/substrate/frame/beefy-mmr/src/tests.rs b/substrate/frame/beefy-mmr/src/tests.rs index b126a01012b..297fb28647a 100644 --- a/substrate/frame/beefy-mmr/src/tests.rs +++ b/substrate/frame/beefy-mmr/src/tests.rs @@ -278,8 +278,28 @@ fn is_non_canonical_should_work_correctly() { &Commitment { payload: Payload::from_single_entry( known_payloads::MMR_ROOT_ID, - H256::repeat_byte(0).encode(), - ), + prev_roots[250 - 1].encode() + ) + .push_raw(known_payloads::MMR_ROOT_ID, H256::repeat_byte(0).encode(),), + block_number: 250, + validator_set_id: 0, + }, + valid_proof.clone(), + Mmr::mmr_root(), + ), + true + ); + + // If the `commitment.payload` contains an MMR root that can't be decoded, + // it's non-canonical. + assert_eq!( + BeefyMmr::is_non_canonical( + &Commitment { + payload: Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + prev_roots[250 - 1].encode() + ) + .push_raw(known_payloads::MMR_ROOT_ID, vec![],), block_number: 250, validator_set_id: 0, }, diff --git a/substrate/primitives/consensus/beefy/src/payload.rs b/substrate/primitives/consensus/beefy/src/payload.rs index d22255c384b..9e792670fef 100644 --- a/substrate/primitives/consensus/beefy/src/payload.rs +++ b/substrate/primitives/consensus/beefy/src/payload.rs @@ -56,6 +56,16 @@ impl Payload { Some(&self.0[index].1) } + /// Returns all the raw payloads under given `id`. + pub fn get_all_raw<'a>( + &'a self, + id: &'a BeefyPayloadId, + ) -> impl Iterator> + 'a { + self.0 + .iter() + .filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) }) + } + /// Returns a decoded payload value under given `id`. /// /// In case the value is not there, or it cannot be decoded `None` is returned. @@ -63,6 +73,14 @@ impl Payload { self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok()) } + /// Returns all decoded payload values under given `id`. + pub fn get_all_decoded<'a, T: Decode>( + &'a self, + id: &'a BeefyPayloadId, + ) -> impl Iterator> + 'a { + self.get_all_raw(id).map(|raw| T::decode(&mut &raw[..]).ok()) + } + /// Push a `Vec` with a given id into the payload vec. /// This method will internally sort the payload vec after every push. /// -- GitLab From c50630518ef3e3908d3c66f8f24095f9302ce444 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Wed, 2 Oct 2024 10:37:51 +0300 Subject: [PATCH 030/127] fix prospective parachains test to use shuffled candidate list (#5880) Fixes https://github.com/paritytech/polkadot-sdk/issues/5617 --- .../src/fragment_chain/tests.rs | 2 +- prdoc/pr_5880.prdoc | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5880.prdoc diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs index 9886d19e522..3332cbeb03c 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -1433,7 +1433,7 @@ fn test_find_ancestor_path_and_find_backable_chain() { // Now back all candidates. Back them in a random order. The result should always be the same. let mut candidates_shuffled = candidates.clone(); candidates_shuffled.shuffle(&mut thread_rng()); - for candidate in candidates.iter() { + for candidate in candidates_shuffled.iter() { chain.candidate_backed(candidate); storage.mark_backed(candidate); } diff --git a/prdoc/pr_5880.prdoc b/prdoc/pr_5880.prdoc new file mode 100644 index 00000000000..b246bff11f8 --- /dev/null +++ b/prdoc/pr_5880.prdoc @@ -0,0 +1,11 @@ +title: Fix prospective parachains test to use shuffled candidate list + +doc: + - audience: Node Dev + description: | + Fix prospective parachains test to use shuffled candidate list. + Resolves https://github.com/paritytech/polkadot-sdk/issues/5617. + +crates: + - name: polkadot-node-core-prospective-parachains + bump: none -- GitLab From 383180ac818f71e7a05647b54973e00712ea3f52 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:18:15 -0300 Subject: [PATCH 031/127] `zombienet`, retry only on 'runner_system_failure' (#5882) Only retry jobs on `runner_system_failure`. Thx! --- .gitlab/pipeline/zombienet.yml | 2 +- .gitlab/pipeline/zombienet/cumulus.yml | 4 +++- .gitlab/pipeline/zombienet/parachain-template.yml | 4 +++- .gitlab/pipeline/zombienet/polkadot.yml | 4 +++- .gitlab/pipeline/zombienet/substrate.yml | 4 +++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index e43b5a54b04..f793271d8f4 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.110" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.111" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" diff --git a/.gitlab/pipeline/zombienet/cumulus.yml b/.gitlab/pipeline/zombienet/cumulus.yml index 6e2b53fae61..fc88e1ff145 100644 --- a/.gitlab/pipeline/zombienet/cumulus.yml +++ b/.gitlab/pipeline/zombienet/cumulus.yml @@ -46,7 +46,9 @@ paths: - ./zombienet-logs allow_failure: true - retry: 2 + retry: + max: 1 + when: runner_system_failure tags: - zombienet-polkadot-integration-test diff --git a/.gitlab/pipeline/zombienet/parachain-template.yml b/.gitlab/pipeline/zombienet/parachain-template.yml index 6ed63182ec5..896ba7913be 100644 --- a/.gitlab/pipeline/zombienet/parachain-template.yml +++ b/.gitlab/pipeline/zombienet/parachain-template.yml @@ -28,7 +28,9 @@ after_script: - mkdir -p ./zombienet-logs - cp /tmp/zombie*/*/*.log ./zombienet-logs/ - retry: 2 + retry: + max: 1 + when: runner_system_failure timeout: 20m tags: - linux-docker diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 9a3bed24cfa..0da95f224cf 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -72,7 +72,9 @@ after_script: - mkdir -p ./zombienet-logs - cp /tmp/zombie*/logs/* ./zombienet-logs/ - retry: 2 + retry: + max: 1 + when: runner_system_failure tags: - zombienet-polkadot-integration-test diff --git a/.gitlab/pipeline/zombienet/substrate.yml b/.gitlab/pipeline/zombienet/substrate.yml index 2013ffd571c..d4ffa34f339 100644 --- a/.gitlab/pipeline/zombienet/substrate.yml +++ b/.gitlab/pipeline/zombienet/substrate.yml @@ -38,7 +38,9 @@ after_script: - mkdir -p ./zombienet-logs - cp /tmp/zombie*/logs/* ./zombienet-logs/ - retry: 2 + retry: + max: 1 + when: runner_system_failure tags: - zombienet-polkadot-integration-test -- GitLab From 087ea0352ec2a3a2f0dc5254fe0455ba429bc25f Mon Sep 17 00:00:00 2001 From: s0me0ne-unkn0wn <48632512+s0me0ne-unkn0wn@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:38:15 +0200 Subject: [PATCH 032/127] Do not enforce PoV size hard limit in config (#5887) Quoting @bkchr (from [here](https://github.com/paritytech/polkadot-sdk/issues/5334#issuecomment-2383815078)): > That the hardcoded limit is used there again, is IMO not correct. The `max_pov_size` should be controlled by the `HostConfiguration` and not limited by some "random" constant. This PR aims to change the hard limit to a not-so-random constant, allowing more room for maneuvering in the future. --- .../runtime/parachains/src/configuration.rs | 10 +++++++--- .../parachains/src/configuration/tests.rs | 2 +- prdoc/pr_5887.prdoc | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_5887.prdoc diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 30fe95883e7..36888247580 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -29,7 +29,7 @@ use polkadot_parachain_primitives::primitives::{ use polkadot_primitives::{ ApprovalVotingParams, AsyncBackingParams, Balance, ExecutorParamError, ExecutorParams, NodeFeatures, SessionIndex, LEGACY_MIN_BACKING_VOTES, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, - MAX_POV_SIZE, ON_DEMAND_MAX_QUEUE_MAX_SIZE, + ON_DEMAND_MAX_QUEUE_MAX_SIZE, }; use sp_runtime::{traits::Zero, Perbill, Percent}; @@ -46,6 +46,10 @@ use polkadot_primitives::SchedulerParams; const LOG_TARGET: &str = "runtime::configuration"; +// This value is derived from network layer limits. See `sc_network::MAX_RESPONSE_SIZE` and +// `polkadot_node_network_protocol::POV_RESPONSE_SIZE`. +const POV_SIZE_HARD_LIMIT: u32 = 16 * 1024 * 1024; + /// All configuration of the runtime with respect to paras. #[derive( Clone, @@ -310,7 +314,7 @@ pub enum InconsistentError { MaxCodeSizeExceedHardLimit { max_code_size: u32 }, /// `max_head_data_size` exceeds the hard limit of `MAX_HEAD_DATA_SIZE`. MaxHeadDataSizeExceedHardLimit { max_head_data_size: u32 }, - /// `max_pov_size` exceeds the hard limit of `MAX_POV_SIZE`. + /// `max_pov_size` exceeds the hard limit of `POV_SIZE_HARD_LIMIT`. MaxPovSizeExceedHardLimit { max_pov_size: u32 }, /// `minimum_validation_upgrade_delay` is less than `paras_availability_period`. MinimumValidationUpgradeDelayLessThanChainAvailabilityPeriod { @@ -377,7 +381,7 @@ where }) } - if self.max_pov_size > MAX_POV_SIZE { + if self.max_pov_size > POV_SIZE_HARD_LIMIT { return Err(MaxPovSizeExceedHardLimit { max_pov_size: self.max_pov_size }) } diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index dad8b6458e1..0d20399e471 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -210,7 +210,7 @@ fn invariants() { ); assert_err!( - Configuration::set_max_pov_size(RuntimeOrigin::root(), MAX_POV_SIZE + 1), + Configuration::set_max_pov_size(RuntimeOrigin::root(), POV_SIZE_HARD_LIMIT + 1), Error::::InvalidNewValue ); diff --git a/prdoc/pr_5887.prdoc b/prdoc/pr_5887.prdoc new file mode 100644 index 00000000000..3ee6ac05a11 --- /dev/null +++ b/prdoc/pr_5887.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: "Set reasonable hard limit for PoV size config value" + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + Sets the hard limit of the `max_pov_size` host configuration parameter to correspond to the + actual network-related limit rather than to a random constant. + +crates: + - name: polkadot-runtime-parachains + bump: patch + -- GitLab From 722b028d35cf5e29576184f1dfee613a2d41697c Mon Sep 17 00:00:00 2001 From: Andrii Date: Wed, 2 Oct 2024 15:00:01 +0200 Subject: [PATCH 033/127] Added foreign locations to local accounts converter to all the parachains (#5765) Added `HashedDescription>` foreign locations to local accounts converter to all the parachains. --------- Co-authored-by: command-bot <> Co-authored-by: Branislav Kontur Co-authored-by: Adrian Catangiu --- .../assets/asset-hub-rococo/tests/tests.rs | 111 +++++++++++++ .../asset-hub-westend/src/xcm_config.rs | 7 +- .../assets/asset-hub-westend/tests/tests.rs | 111 +++++++++++++ .../bridge-hub-rococo/src/xcm_config.rs | 13 +- .../bridge-hub-rococo/tests/tests.rs | 157 +++++++++++++++--- .../bridge-hub-westend/src/xcm_config.rs | 13 +- .../bridge-hub-westend/tests/tests.rs | 111 +++++++++++++ .../collectives-westend/src/xcm_config.rs | 16 +- .../collectives-westend/tests/tests.rs | 134 +++++++++++++++ .../contracts/contracts-rococo/src/lib.rs | 2 +- .../contracts-rococo/src/xcm_config.rs | 15 +- .../contracts/contracts-rococo/tests/tests.rs | 134 +++++++++++++++ .../coretime-rococo/src/xcm_config.rs | 13 +- .../coretime/coretime-rococo/tests/tests.rs | 134 +++++++++++++++ .../coretime-westend/src/xcm_config.rs | 13 +- .../coretime/coretime-westend/tests/tests.rs | 134 +++++++++++++++ .../people/people-rococo/src/xcm_config.rs | 15 +- .../people/people-rococo/tests/tests.rs | 134 +++++++++++++++ .../people/people-westend/src/xcm_config.rs | 15 +- .../people/people-westend/tests/tests.rs | 134 +++++++++++++++ .../runtimes/testing/penpal/src/xcm_config.rs | 16 +- polkadot/runtime/rococo/src/tests.rs | 78 ++++++++- polkadot/runtime/westend/src/tests.rs | 79 ++++++++- prdoc/pr_5765.prdoc | 42 +++++ 24 files changed, 1548 insertions(+), 83 deletions(-) create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs create mode 100644 cumulus/parachains/runtimes/contracts/contracts-rococo/tests/tests.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs create mode 100644 cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs create mode 100644 cumulus/parachains/runtimes/people/people-westend/tests/tests.rs create mode 100644 prdoc/pr_5765.prdoc 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 6b0cf87a6f7..6e10f916899 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -48,12 +48,14 @@ use frame_support::{ }; use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; +use sp_core::crypto::Ss58Codec; use sp_runtime::traits::MaybeEquivalence; use std::convert::Into; use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::WithLatestLocationConverter; use xcm_executor::traits::{JustTry, WeightTrader}; +use xcm_runtime_apis::conversions::LocationToAccountHelper; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -1355,3 +1357,112 @@ fn change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() { }, ) } + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 cc96bd59cb4..614ef81a4fa 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 @@ -48,7 +48,7 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, MatchedConvertedConcreteId, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, @@ -93,9 +93,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, - // Foreign chain account alias into local accounts according to a hash of their standard - // description. - HashedDescription>, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, // Different global consensus parachain sovereign account. // (Used for over-bridge transfers and reserve processing) GlobalConsensusParachainConvertsFor, 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 ad3c450eb37..ff84bdea69f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -49,12 +49,14 @@ use frame_support::{ }; use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; +use sp_core::crypto::Ss58Codec; use sp_runtime::traits::MaybeEquivalence; 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::WithLatestLocationConverter; use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; +use xcm_runtime_apis::conversions::LocationToAccountHelper; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -1329,3 +1331,112 @@ fn reserve_transfer_native_asset_to_non_teleport_para_works() { WeightLimit::Unlimited, ); } + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 e9b15024be8..2fb186703a8 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 @@ -44,11 +44,12 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HandleFee, HashedDescription, + IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{ traits::{FeeManager, FeeReason, FeeReason::Export}, @@ -78,6 +79,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. 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 002e31174cb..20bd5d12913 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 @@ -32,7 +32,7 @@ use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8} use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_core::ChannelId; use sp_consensus_aura::SlotDuration; -use sp_core::H160; +use sp_core::{crypto::Ss58Codec, H160}; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, @@ -40,6 +40,7 @@ use sp_runtime::{ }; use testnet_parachains_constants::rococo::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; parameter_types! { pub CheckingAccount: AccountId = PolkadotXcm::check_account(); @@ -120,26 +121,6 @@ bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID ); -#[test] -fn change_required_stake_by_governance_works() { - bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< - Runtime, - bridge_common_config::RequiredStakeForStakeAndSlash, - Balance, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::System(call).encode()), - || { - ( - bridge_common_config::RequiredStakeForStakeAndSlash::key().to_vec(), - bridge_common_config::RequiredStakeForStakeAndSlash::get(), - ) - }, - |old_value| old_value.checked_mul(2).unwrap(), - ) -} - mod bridge_hub_westend_tests { use super::*; use bp_messages::LegacyLaneId; @@ -529,8 +510,8 @@ mod bridge_hub_bulletin_tests { rococo_runtime_constants::system_parachain::PEOPLE_ID; parameter_types! { - pub SiblingPeopleParachainLocation: Location = Location::new(1, [Parachain(SIBLING_PEOPLE_PARACHAIN_ID)]); - pub BridgedBulletinLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); + pub SiblingPeopleParachainLocation: Location = Location::new(1, [Parachain(SIBLING_PEOPLE_PARACHAIN_ID)]); + pub BridgedBulletinLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); } // Runtime from tests PoV @@ -715,3 +696,133 @@ mod bridge_hub_bulletin_tests { ) } } + +#[test] +fn change_required_stake_by_governance_works() { + bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridge_common_config::RequiredStakeForStakeAndSlash, + Balance, + >( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridge_common_config::RequiredStakeForStakeAndSlash::key().to_vec(), + bridge_common_config::RequiredStakeForStakeAndSlash::get(), + ) + }, + |old_value| old_value.checked_mul(2).unwrap(), + ) +} + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic + // change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(Alice).into() }], + ), + expected_account_id_str: "5EueAXd4h8u75nSbFdDJbC29cmi4Uo1YJssqEL9idvindxFL", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(Alice).into() }, + ], + ), + expected_account_id_str: "5Dmbuiq48fU4iW58FKYqoGbbfxFHjbAeGLMtjFg6NNCw3ssr", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 491caa38dc5..ae31ca4cedf 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 @@ -43,11 +43,12 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HandleFee, HashedDescription, + IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{ traits::{FeeManager, FeeReason, FeeReason::Export}, @@ -76,6 +77,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. 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 d168d1b7a13..c5a9b8c53a9 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 @@ -39,6 +39,7 @@ use codec::{Decode, Encode}; use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; use parachains_common::{AccountId, AuraId, Balance}; use sp_consensus_aura::SlotDuration; +use sp_core::crypto::Ss58Codec; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, @@ -46,6 +47,7 @@ use sp_runtime::{ }; use testnet_parachains_constants::westend::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; // Random para id of sibling chain used in tests. pub const SIBLING_PARACHAIN_ID: u32 = 2053; @@ -410,3 +412,112 @@ pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() { ), ) } + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(Alice).into() }], + ), + expected_account_id_str: "5EueAXd4h8u75nSbFdDJbC29cmi4Uo1YJssqEL9idvindxFL", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(Alice).into() }, + ], + ), + expected_account_id_str: "5Dmbuiq48fU4iW58FKYqoGbbfxFHjbAeGLMtjFg6NNCw3ssr", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 08b1d192b0b..f8e03303c32 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -37,13 +37,13 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungibleAdapter, IsConcrete, LocatableAssetId, - OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -81,6 +81,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain.#[allow(deprecated)] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs new file mode 100644 index 00000000000..7add10559d8 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use collectives_westend_runtime::xcm_config::LocationToAccountId; +use parachains_common::AccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 853c8b48dc8..50316a9a1d0 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -27,7 +27,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod contracts; mod weights; -mod xcm_config; +pub mod xcm_config; extern crate alloc; 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 6a41cf75d35..39fdd30a049 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -40,12 +40,13 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -75,6 +76,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/tests/tests.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/tests/tests.rs new file mode 100644 index 00000000000..02c4b7b3963 --- /dev/null +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use contracts_rococo_runtime::xcm_config::LocationToAccountId; +use parachains_common::AccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 f56a3c42de0..2eae13de2fd 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -41,11 +41,12 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, IsConcrete, NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -74,6 +75,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs new file mode 100644 index 00000000000..2cabce567b6 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use coretime_rococo_runtime::xcm_config::LocationToAccountId; +use parachains_common::AccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 da8aa1c18bd..1205be95c93 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -41,11 +41,12 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, - FungibleAdapter, IsConcrete, NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -74,6 +75,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs new file mode 100644 index 00000000000..e391d71a9ab --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use coretime_westend_runtime::xcm_config::LocationToAccountId; +use parachains_common::AccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 96ab3eafa78..a2e20e2778b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -38,12 +38,13 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeTerminus, EnsureXcmOrigin, - FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -93,6 +94,8 @@ pub type LocationToAccountId = ( AccountId32Aliases, // Here/local root location to `AccountId`. HashedDescription, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. diff --git a/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs new file mode 100644 index 00000000000..3627d9c40ec --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use parachains_common::AccountId; +use people_rococo_runtime::xcm_config::LocationToAccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} 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 f35e920d7cb..bec5b923d8a 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -38,12 +38,13 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeTerminus, EnsureXcmOrigin, - FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, + DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -93,6 +94,8 @@ pub type LocationToAccountId = ( AccountId32Aliases, // Here/local root location to `AccountId`. HashedDescription, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting the native currency on this chain. diff --git a/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs new file mode 100644 index 00000000000..fa9331952b4 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs @@ -0,0 +1,134 @@ +// 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 . + +#![cfg(test)] + +use parachains_common::AccountId; +use people_westend_runtime::xcm_config::LocationToAccountId; +use sp_core::crypto::Ss58Codec; +use xcm::latest::prelude::*; +use xcm_runtime_apis::conversions::LocationToAccountHelper; + +const ALICE: [u8; 32] = [1u8; 32]; + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Parent", + location: Location::new(1, Here), + expected_account_id_str: "5Dt6dpkWPwLaH4BBCKJwjiWrFVAGyYk3tLUabvyn4v7KtESG", + }, + TestCase { + description: "DescribeTerminus Sibling", + location: Location::new(1, [Parachain(1111)]), + expected_account_id_str: "5Eg2fnssmmJnF3z1iZ1NouAuzciDaaDQH7qURAy3w15jULDk", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Parent", + location: Location::new(1, [PalletInstance(50)]), + expected_account_id_str: "5CnwemvaAXkWFVwibiCvf2EjqwiqBi29S5cLLydZLEaEw6jZ", + }, + TestCase { + description: "DescribePalletTerminal Sibling", + location: Location::new(1, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5GFBgPjpEQPdaxEnFirUoa51u5erVx84twYxJVuBRAT2UP2g", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Parent", + location: Location::new( + 1, + [Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }], + ), + expected_account_id_str: "5DN5SGsuUG7PAqFL47J9meViwdnk9AdeSWKFkcHC45hEzVz4", + }, + TestCase { + description: "DescribeAccountId32Terminal Sibling", + location: Location::new( + 1, + [ + Parachain(1111), + Junction::AccountId32 { network: None, id: AccountId::from(ALICE).into() }, + ], + ), + expected_account_id_str: "5DGRXLYwWGce7wvm14vX1Ms4Vf118FSWQbJkyQigY2pfm6bg", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Parent", + location: Location::new(1, [AccountKey20 { network: None, key: [0u8; 20] }]), + expected_account_id_str: "5F5Ec11567pa919wJkX6VHtv2ZXS5W698YCW35EdEbrg14cg", + }, + TestCase { + description: "DescribeAccountKey20Terminal Sibling", + location: Location::new( + 1, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5CB2FbUds2qvcJNhDiTbRZwiS3trAy6ydFGMSVutmYijpPAg", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]), + expected_account_id_str: "5CUjnE2vgcUCuhxPwFoQ5r7p1DkhujgvMNDHaF2bLqRp4D5F", + }, + TestCase { + description: "DescribeTreasuryVoiceTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5G6TDwaVgbWmhqRUKjBhRRnH4ry9L9cjRymUEmiRsLbSE4gB", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Parent", + location: Location::new(1, [Plurality { id: BodyId::Unit, part: BodyPart::Voice }]), + expected_account_id_str: "5EBRMTBkDisEXsaN283SRbzx9Xf2PXwUxxFCJohSGo4jYe6B", + }, + TestCase { + description: "DescribeBodyTerminal Sibling", + location: Location::new( + 1, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DBoExvojy8tYnHgLL97phNH975CyT45PWTZEeGoBZfAyRMH", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 99aadb33b84..b72d6d232a1 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -49,13 +49,13 @@ use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SingleAssetExchangeAdapter, SovereignSignedViaLocation, StartsWith, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, + ConvertedConcreteId, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FixedWeightBounds, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, + LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SingleAssetExchangeAdapter, + SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -90,6 +90,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// Means for transacting assets on this chain. diff --git a/polkadot/runtime/rococo/src/tests.rs b/polkadot/runtime/rococo/src/tests.rs index 464a8c4f545..01eaad87e34 100644 --- a/polkadot/runtime/rococo/src/tests.rs +++ b/polkadot/runtime/rococo/src/tests.rs @@ -19,8 +19,11 @@ use crate::*; use std::collections::HashSet; +use crate::xcm_config::LocationConverter; use frame_support::traits::WhitelistedStorageKeys; -use sp_core::hexdisplay::HexDisplay; +use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; +use sp_keyring::AccountKeyring::Alice; +use xcm_runtime_apis::conversions::LocationToAccountHelper; #[test] fn check_whitelist() { @@ -61,3 +64,76 @@ mod encoding_tests { assert_eq!(RuntimeHoldReason::Nis(pallet_nis::HoldReason::NftReceipt).encode(), [38, 0]); } } + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Child", + location: Location::new(0, [Parachain(1111)]), + expected_account_id_str: "5Ec4AhP4h37t7TFsAZ4HhFq6k92usAAJDUC3ADSZ4H4Acru3", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Child", + location: Location::new(0, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5FjEBrKn3STAFsZpQF4jzwxUYHNGnNgzdZqSQfTzeJ82XKp6", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Child", + location: Location::new( + 0, + [Parachain(1111), AccountId32 { network: None, id: AccountId::from(Alice).into() }], + ), + expected_account_id_str: "5EEMro9RRDpne4jn9TuD7cTB6Amv1raVZ3xspSkqb2BF3FJH", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Child", + location: Location::new( + 0, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5HohjXdjs6afcYcgHHSstkrtGfxgfGKsnZ1jtewBpFiGu4DL", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Child", + location: Location::new( + 0, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5GenE4vJgHvwYVcD6b4nBvH5HNY4pzpVHWoqwFpNMFT7a2oX", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Child", + location: Location::new( + 0, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DPgGBFTTYm1dGbtB1VWHJ3T3ScvdrskGGx6vSJZNP1WNStV", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index dc8103ab52c..eef95006bce 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -18,9 +18,11 @@ use std::collections::HashSet; -use crate::*; +use crate::{xcm_config::LocationConverter, *}; use frame_support::traits::WhitelistedStorageKeys; -use sp_core::hexdisplay::HexDisplay; +use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; +use sp_keyring::AccountKeyring::Alice; +use xcm_runtime_apis::conversions::LocationToAccountHelper; #[test] fn remove_keys_weight_is_sensible() { @@ -236,3 +238,76 @@ mod remote_tests { }); } } + +#[test] +fn location_conversion_works() { + // the purpose of hardcoded values is to catch an unintended location conversion logic change. + struct TestCase { + description: &'static str, + location: Location, + expected_account_id_str: &'static str, + } + + let test_cases = vec![ + // DescribeTerminus + TestCase { + description: "DescribeTerminus Child", + location: Location::new(0, [Parachain(1111)]), + expected_account_id_str: "5Ec4AhP4h37t7TFsAZ4HhFq6k92usAAJDUC3ADSZ4H4Acru3", + }, + // DescribePalletTerminal + TestCase { + description: "DescribePalletTerminal Child", + location: Location::new(0, [Parachain(1111), PalletInstance(50)]), + expected_account_id_str: "5FjEBrKn3STAFsZpQF4jzwxUYHNGnNgzdZqSQfTzeJ82XKp6", + }, + // DescribeAccountId32Terminal + TestCase { + description: "DescribeAccountId32Terminal Child", + location: Location::new( + 0, + [Parachain(1111), AccountId32 { network: None, id: AccountId::from(Alice).into() }], + ), + expected_account_id_str: "5EEMro9RRDpne4jn9TuD7cTB6Amv1raVZ3xspSkqb2BF3FJH", + }, + // DescribeAccountKey20Terminal + TestCase { + description: "DescribeAccountKey20Terminal Child", + location: Location::new( + 0, + [Parachain(1111), AccountKey20 { network: None, key: [0u8; 20] }], + ), + expected_account_id_str: "5HohjXdjs6afcYcgHHSstkrtGfxgfGKsnZ1jtewBpFiGu4DL", + }, + // DescribeTreasuryVoiceTerminal + TestCase { + description: "DescribeTreasuryVoiceTerminal Child", + location: Location::new( + 0, + [Parachain(1111), Plurality { id: BodyId::Treasury, part: BodyPart::Voice }], + ), + expected_account_id_str: "5GenE4vJgHvwYVcD6b4nBvH5HNY4pzpVHWoqwFpNMFT7a2oX", + }, + // DescribeBodyTerminal + TestCase { + description: "DescribeBodyTerminal Child", + location: Location::new( + 0, + [Parachain(1111), Plurality { id: BodyId::Unit, part: BodyPart::Voice }], + ), + expected_account_id_str: "5DPgGBFTTYm1dGbtB1VWHJ3T3ScvdrskGGx6vSJZNP1WNStV", + }, + ]; + + for tc in test_cases { + let expected = + AccountId::from_string(tc.expected_account_id_str).expect("Invalid AccountId string"); + + let got = LocationToAccountHelper::::convert_location( + tc.location.into(), + ) + .unwrap(); + + assert_eq!(got, expected, "{}", tc.description); + } +} diff --git a/prdoc/pr_5765.prdoc b/prdoc/pr_5765.prdoc new file mode 100644 index 00000000000..e8ecca8ba0f --- /dev/null +++ b/prdoc/pr_5765.prdoc @@ -0,0 +1,42 @@ +# 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: "Added foreign locations to local accounts converter to all the parachains." + +doc: + - audience: Runtime Dev + description: | + Added foreign locations to local accounts converter to all the parachains. + i.e. added HashedDescription> to LocationToAccountId + + - audience: Runtime User + description: | + Now any user account can have a sovereign account on another chain controlled by the original account. + +crates: + - name: asset-hub-westend-runtime + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + - name: collectives-westend-runtime + bump: patch + - name: contracts-rococo-runtime + bump: patch + - name: coretime-rococo-runtime + bump: patch + - name: coretime-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: patch + - name: people-westend-runtime + bump: patch + - name: penpal-runtime + bump: patch + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: asset-hub-rococo-runtime + bump: patch -- GitLab From 3cf83ca8e2d4a2828a8eeb0836556a092c0b7f59 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 2 Oct 2024 16:13:59 +0300 Subject: [PATCH 034/127] `polkadot-parachain`: add manual seal support (#5586) Resolves https://github.com/paritytech/polkadot-sdk/issues/5026 This PR adds support for starting a dev node with manual seal consensus. This can be done by using the `--dev-block-time` argument . For example: ``` polkadot-parachain --chain asset-hub-rococo-dev --dev-block-time 5000 --tmp ``` --- Cargo.lock | 46 +-- .../polkadot-parachain-lib/Cargo.toml | 2 + .../polkadot-parachain-lib/src/cli.rs | 8 + .../polkadot-parachain-lib/src/command.rs | 73 ++--- .../src/common/command.rs | 4 +- .../polkadot-parachain-lib/src/common/mod.rs | 6 +- .../polkadot-parachain-lib/src/common/spec.rs | 295 +++++++++--------- .../polkadot-parachain-lib/src/lib.rs | 2 +- .../src/{service.rs => nodes/aura.rs} | 137 ++------ .../src/nodes/manual_seal.rs | 233 ++++++++++++++ .../polkadot-parachain-lib/src/nodes/mod.rs | 58 ++++ .../polkadot-parachain-lib/src/nodes/shell.rs | 152 +++++++++ .../src/guides/enable_elastic_scaling_mvp.rs | 4 +- 13 files changed, 687 insertions(+), 333 deletions(-) rename cumulus/polkadot-parachain/polkadot-parachain-lib/src/{service.rs => nodes/aura.rs} (77%) create mode 100644 cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs create mode 100644 cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs create mode 100644 cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs diff --git a/Cargo.lock b/Cargo.lock index a572c37a406..db33c59f803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1179,7 +1179,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.0.0", + "futures-lite 2.3.0", ] [[package]] @@ -1227,7 +1227,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.0.0", + "futures-lite 2.3.0", "parking", "polling 3.4.0", "rustix 0.38.21", @@ -1276,7 +1276,7 @@ checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ "async-io 2.3.3", "blocking", - "futures-lite 2.0.0", + "futures-lite 2.3.0", ] [[package]] @@ -1311,7 +1311,7 @@ dependencies = [ "blocking", "cfg-if", "event-listener 5.2.0", - "futures-lite 2.0.0", + "futures-lite 2.3.0", "rustix 0.38.21", "tracing", ] @@ -2618,9 +2618,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2-sys" @@ -6736,17 +6736,15 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand 2.1.0", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", ] [[package]] @@ -10505,9 +10503,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -10537,9 +10535,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -13341,9 +13339,9 @@ checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -14975,6 +14973,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "futures", + "futures-timer", "jsonrpsee 0.24.3", "log", "nix 0.28.0", @@ -14991,6 +14990,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", + "sc-consensus-manual-seal", "sc-executor 0.32.0", "sc-network", "sc-rpc", @@ -16808,7 +16808,7 @@ checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.11.0", "log", "multimap", "once_cell", @@ -16829,7 +16829,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.11.0", "log", "multimap", "once_cell", @@ -16862,7 +16862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.79", @@ -16875,7 +16875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.79", @@ -20746,7 +20746,7 @@ dependencies = [ "async-net 2.0.0", "async-process 2.3.0", "blocking", - "futures-lite 2.0.0", + "futures-lite 2.3.0", ] [[package]] @@ -20832,7 +20832,7 @@ dependencies = [ "either", "event-listener 4.0.3", "fnv", - "futures-lite 2.0.0", + "futures-lite 2.3.0", "futures-util", "hashbrown 0.14.5", "hex", @@ -20918,7 +20918,7 @@ dependencies = [ "event-listener 4.0.3", "fnv", "futures-channel", - "futures-lite 2.0.0", + "futures-lite 2.3.0", "futures-util", "hashbrown 0.14.5", "hex", diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml index 066cbfae53a..5e4c9fcf1a3 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml @@ -61,6 +61,7 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-feature sp-inherents = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } @@ -83,6 +84,7 @@ cumulus-client-service = { workspace = true, default-features = true } cumulus-primitives-aura = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } +futures-timer = "3.0.3" [dev-dependencies] assert_cmd = { workspace = true } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs index 349dc01d8a4..6ca328912bb 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs @@ -119,6 +119,14 @@ pub struct Cli { #[command(flatten)] pub run: cumulus_client_cli::RunCmd, + /// Start a dev node that produces a block each `dev_block_time` ms. + /// + /// This is a dev option, and it won't result in starting or connecting to a parachain network. + /// The resulting node will work on its own, running the wasm blob and artificially producing + /// a block each `dev_block_time` ms, as if it was part of a parachain. + #[arg(long)] + pub dev_block_time: Option, + /// EXPERIMENTAL: Use slot-based collator which can handle elastic scaling. /// /// Use with care, this flag is unstable and subject to change. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index 63562f192ae..ff85bdf9401 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -22,13 +22,12 @@ use crate::{ AuraConsensusId, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, RuntimeResolver, }, - spec::DynNodeSpec, types::Block, NodeBlock, NodeExtraArgs, }, fake_runtime_api, + nodes::{shell::ShellNode, DynNodeSpecExt}, runtime::BlockNumber, - service::ShellNode, }; #[cfg(feature = "runtime-benchmarks")] use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; @@ -52,17 +51,17 @@ pub struct RunConfig { pub fn new_aura_node_spec( aura_id: AuraConsensusId, extra_args: &NodeExtraArgs, -) -> Box +) -> Box where Block: NodeBlock, { match aura_id { - AuraConsensusId::Sr25519 => crate::service::new_aura_node_spec::< + AuraConsensusId::Sr25519 => crate::nodes::aura::new_aura_node_spec::< Block, fake_runtime_api::aura_sr25519::RuntimeApi, sp_consensus_aura::sr25519::AuthorityId, >(extra_args), - AuraConsensusId::Ed25519 => crate::service::new_aura_node_spec::< + AuraConsensusId::Ed25519 => crate::nodes::aura::new_aura_node_spec::< Block, fake_runtime_api::aura_ed25519::RuntimeApi, sp_consensus_aura::ed25519::AuthorityId, @@ -74,7 +73,7 @@ fn new_node_spec( config: &sc_service::Configuration, runtime_resolver: &Box, extra_args: &NodeExtraArgs, -) -> std::result::Result, sc_cli::Error> { +) -> std::result::Result, sc_cli::Error> { let runtime = runtime_resolver.runtime(config.chain_spec.as_ref())?; Ok(match runtime { @@ -214,6 +213,20 @@ pub fn run(cmd_config: RunConfig) -> Result<() let collator_options = cli.run.collator_options(); runner.run_node_until_exit(|config| async move { + let node_spec = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + let para_id = ParaId::from( + Extensions::try_get(&*config.chain_spec) + .map(|e| e.para_id) + .ok_or("Could not find parachain extension in chain-spec.")?, + ); + + if let Some(dev_block_time) = cli.dev_block_time { + return node_spec + .start_manual_seal_node(config, para_id, dev_block_time) + .map_err(Into::into) + } + // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the // asset-hub chain spec, then rename the base path to the new chain ID. In the case // that both file paths exist, the node will exit, as the user must decide (by @@ -263,15 +276,9 @@ pub fn run(cmd_config: RunConfig) -> Result<() })) .flatten(); - let para_id = Extensions::try_get(&*config.chain_spec) - .map(|e| e.para_id) - .ok_or("Could not find parachain extension in chain-spec.")?; - - let id = ParaId::from(para_id); - let parachain_account = AccountIdConversion::::into_account_truncating( - &id, + ¶_id, ); let tokio_handle = config.tokio_handle.clone(); @@ -279,38 +286,22 @@ pub fn run(cmd_config: RunConfig) -> Result<() SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle) .map_err(|err| format!("Relay chain argument error: {}", err))?; - info!("🪪 Parachain id: {:?}", id); + info!("🪪 Parachain id: {:?}", para_id); info!("🧾 Parachain Account: {}", parachain_account); info!("✍️ Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); - start_node( - config, - &cmd_config.runtime_resolver, - polkadot_config, - collator_options, - id, - cli.node_extra_args(), - hwbench, - ) - .await + node_spec + .start_node( + config, + polkadot_config, + collator_options, + para_id, + hwbench, + cli.node_extra_args(), + ) + .await + .map_err(Into::into) }) }, } } - -#[sc_tracing::logging::prefix_logs_with("Parachain")] -async fn start_node( - config: sc_service::Configuration, - runtime_resolver: &Box, - polkadot_config: sc_service::Configuration, - collator_options: cumulus_client_cli::CollatorOptions, - id: ParaId, - extra_args: NodeExtraArgs, - hwbench: Option, -) -> Result { - let node_spec = new_node_spec(&config, runtime_resolver, &extra_args)?; - node_spec - .start_node(config, polkadot_config, collator_options, id, hwbench, extra_args) - .await - .map_err(Into::into) -} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs index e2826826d40..a60fc9232d9 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::common::spec::NodeSpec; +use crate::common::spec::BaseNodeSpec; use cumulus_client_cli::ExportGenesisHeadCommand; use frame_benchmarking_cli::BlockCmd; #[cfg(any(feature = "runtime-benchmarks"))] @@ -81,7 +81,7 @@ pub trait NodeCommandRunner { impl NodeCommandRunner for T where - T: NodeSpec, + T: BaseNodeSpec, { fn prepare_check_block_cmd( self: Box, diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs index c6dce521e4a..37660a5347a 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs @@ -28,6 +28,7 @@ pub mod types; use cumulus_primitives_core::{CollectCollationInfo, GetCoreSelectorApi}; use sc_client_db::DbHash; +use serde::de::DeserializeOwned; use sp_api::{ApiExt, CallApiAt, ConstructRuntimeApi, Metadata}; use sp_block_builder::BlockBuilder; use sp_runtime::{ @@ -39,8 +40,7 @@ use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::{fmt::Debug, path::PathBuf, str::FromStr}; pub trait NodeBlock: - BlockT - + for<'de> serde::Deserialize<'de> + BlockT + DeserializeOwned { type BoundedFromStrErr: Debug; type BoundedNumber: FromStr + BlockNumber; @@ -49,7 +49,7 @@ pub trait NodeBlock: impl NodeBlock for T where - T: BlockT + for<'de> serde::Deserialize<'de>, + T: BlockT + DeserializeOwned, ::Header: Unpin, as FromStr>::Err: Debug, { diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs index 0c0230296eb..dca28b3c28f 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs @@ -39,6 +39,7 @@ use sc_network::{config::FullNetworkConfiguration, NetworkBackend, NetworkBlock} use sc_service::{Configuration, ImportQueue, PartialComponents, TaskManager}; use sc_sysinfo::HwBench; use sc_telemetry::{TelemetryHandle, TelemetryWorker}; +use sc_tracing::tracing::Instrument; use sc_transaction_pool::FullPool; use sp_keystore::KeystorePtr; use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; @@ -91,7 +92,7 @@ fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { } } -pub(crate) trait NodeSpec { +pub(crate) trait BaseNodeSpec { type Block: NodeBlock; type RuntimeApi: ConstructNodeRuntimeApi< @@ -101,16 +102,6 @@ pub(crate) trait NodeSpec { type BuildImportQueue: BuildImportQueue; - type BuildRpcExtensions: BuildRpcExtensions< - ParachainClient, - ParachainBackend, - FullPool>, - >; - - type StartConsensus: StartConsensus; - - const SYBIL_RESISTANCE: CollatorSybilResistance; - /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to @@ -187,6 +178,18 @@ pub(crate) trait NodeSpec { other: (block_import, telemetry, telemetry_worker_handle), }) } +} + +pub(crate) trait NodeSpec: BaseNodeSpec { + type BuildRpcExtensions: BuildRpcExtensions< + ParachainClient, + ParachainBackend, + FullPool>, + >; + + type StartConsensus: StartConsensus; + + const SYBIL_RESISTANCE: CollatorSybilResistance; /// Start a node with the given parachain spec. /// @@ -202,147 +205,153 @@ pub(crate) trait NodeSpec { where Net: NetworkBackend, { - Box::pin(async move { - let parachain_config = prepare_node_config(parachain_config); - - let params = Self::new_partial(¶chain_config)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; - - let client = params.client.clone(); - let backend = params.backend.clone(); - - let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( - polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), - ) - .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::<_, _, Net>::new( - ¶chain_config.network, - prometheus_registry.clone(), - ); - - let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, + Box::pin( + async move { + let parachain_config = prepare_node_config(parachain_config); + + let params = Self::new_partial(¶chain_config)?; + let (block_import, mut telemetry, telemetry_worker_handle) = params.other; + + let client = params.client.clone(); + let backend = params.backend.clone(); + + let mut task_manager = params.task_manager; + let (relay_chain_interface, collator_key) = build_relay_chain_interface( + polkadot_config, + ¶chain_config, + telemetry_worker_handle, + &mut task_manager, + collator_options.clone(), + hwbench.clone(), + ) + .await + .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; + + let validator = parachain_config.role.is_authority(); + let prometheus_registry = parachain_config.prometheus_registry().cloned(); + let transaction_pool = params.transaction_pool.clone(); + let import_queue_service = params.import_queue.service(); + let net_config = FullNetworkConfiguration::<_, _, Net>::new( + ¶chain_config.network, + prometheus_registry.clone(), + ); + + let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = + build_network(BuildNetworkParams { + parachain_config: ¶chain_config, + net_config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + para_id, + spawn_handle: task_manager.spawn_handle(), + relay_chain_interface: relay_chain_interface.clone(), + import_queue: params.import_queue, + sybil_resistance_level: Self::SYBIL_RESISTANCE, + }) + .await?; + + let rpc_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + Self::BuildRpcExtensions::build_rpc_extensions( + client.clone(), + backend_for_rpc.clone(), + transaction_pool.clone(), + ) + }) + }; + + sc_service::spawn_tasks(sc_service::SpawnTasksParams { + rpc_builder, client: client.clone(), transaction_pool: transaction_pool.clone(), + task_manager: &mut task_manager, + config: parachain_config, + keystore: params.keystore_container.keystore(), + backend: backend.clone(), + network: network.clone(), + sync_service: sync_service.clone(), + system_rpc_tx, + tx_handler_controller, + telemetry: telemetry.as_mut(), + })?; + + if let Some(hwbench) = hwbench { + sc_sysinfo::print_hwbench(&hwbench); + if validator { + warn_if_slow_hardware(&hwbench); + } + + if let Some(ref mut telemetry) = telemetry { + let telemetry_handle = telemetry.handle(); + task_manager.spawn_handle().spawn( + "telemetry_hwbench", + None, + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + ); + } + } + + let announce_block = { + let sync_service = sync_service.clone(); + Arc::new(move |hash, data| sync_service.announce_block(hash, data)) + }; + + let relay_chain_slot_duration = Duration::from_secs(6); + + let overseer_handle = relay_chain_interface + .overseer_handle() + .map_err(|e| sc_service::Error::Application(Box::new(e)))?; + + start_relay_chain_tasks(StartRelayChainTasksParams { + client: client.clone(), + announce_block: announce_block.clone(), para_id, - spawn_handle: task_manager.spawn_handle(), relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level: Self::SYBIL_RESISTANCE, - }) - .await?; - - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - let backend_for_rpc = backend.clone(); - - Box::new(move |_| { - Self::BuildRpcExtensions::build_rpc_extensions( - client.clone(), - backend_for_rpc.clone(), - transaction_pool.clone(), - ) - }) - }; - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); + task_manager: &mut task_manager, + da_recovery_profile: if validator { + DARecoveryProfile::Collator + } else { + DARecoveryProfile::FullNode + }, + import_queue: import_queue_service, + relay_chain_slot_duration, + recovery_handle: Box::new(overseer_handle.clone()), + sync_service, + })?; + if validator { - warn_if_slow_hardware(&hwbench); + Self::StartConsensus::start_consensus( + client.clone(), + block_import, + prometheus_registry.as_ref(), + telemetry.as_ref().map(|t| t.handle()), + &task_manager, + relay_chain_interface.clone(), + transaction_pool, + params.keystore_container.keystore(), + relay_chain_slot_duration, + para_id, + collator_key.expect("Command line arguments do not allow this. qed"), + overseer_handle, + announce_block, + backend.clone(), + node_extra_args, + )?; } - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), - ); - } - } + start_network.start_network(); - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), - para_id, - relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service, - })?; - - if validator { - Self::StartConsensus::start_consensus( - client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - backend.clone(), - node_extra_args, - )?; + Ok(task_manager) } - - start_network.start_network(); - - Ok(task_manager) - }) + .instrument(sc_tracing::tracing::info_span!( + sc_tracing::logging::PREFIX_LOG_SPAN, + name = "Parachain", + )), + ) } } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs index 6aa2f656a48..a293ab225c6 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs @@ -45,7 +45,7 @@ mod cli; mod command; mod common; mod fake_runtime_api; -mod service; +mod nodes; pub use cli::CliConfig; pub use command::{run, RunConfig}; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs similarity index 77% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs rename to cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs index b1c714784f4..cf1ee91cbab 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs @@ -17,15 +17,15 @@ use crate::{ common::{ aura::{AuraIdT, AuraRuntimeApi}, - rpc::{BuildEmptyRpcExtensions, BuildParachainRpcExtensions}, - spec::{BuildImportQueue, DynNodeSpec, NodeSpec, StartConsensus}, + rpc::BuildParachainRpcExtensions, + spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, types::{ - AccountId, Balance, Block, Hash, Nonce, ParachainBackend, ParachainBlockImport, + AccountId, Balance, Hash, Nonce, ParachainBackend, ParachainBlockImport, ParachainClient, }, ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, }, - fake_runtime_api::aura_sr25519::RuntimeApi as FakeRuntimeApi, + nodes::DynNodeSpecExt, }; use cumulus_client_collator::service::{ CollatorService, ServiceInterface as CollatorServiceInterface, @@ -38,7 +38,6 @@ use cumulus_client_consensus_aura::collators::slot_based::{ use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; #[allow(deprecated)] -use cumulus_client_service::old_consensus; use cumulus_client_service::CollatorSybilResistance; use cumulus_primitives_core::{relay_chain::ValidationCode, ParaId}; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; @@ -63,40 +62,6 @@ use sp_runtime::{ }; use std::{marker::PhantomData, sync::Arc, time::Duration}; -/// Build the import queue for the shell runtime. -pub(crate) struct BuildShellImportQueue; - -impl BuildImportQueue, FakeRuntimeApi> for BuildShellImportQueue { - fn build_import_queue( - client: Arc, FakeRuntimeApi>>, - block_import: ParachainBlockImport, FakeRuntimeApi>, - config: &Configuration, - _telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result>> { - cumulus_client_consensus_relay_chain::import_queue( - client, - block_import, - |_, _| async { Ok(()) }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - ) - .map_err(Into::into) - } -} - -pub(crate) struct ShellNode; - -impl NodeSpec for ShellNode { - type Block = Block; - type RuntimeApi = FakeRuntimeApi; - type BuildImportQueue = BuildShellImportQueue; - type BuildRpcExtensions = BuildEmptyRpcExtensions, Self::RuntimeApi>; - type StartConsensus = StartRelayChainConsensus; - - const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; -} - struct Verifier { client: Arc, aura_verifier: Box>, @@ -205,7 +170,7 @@ impl Default } } -impl NodeSpec +impl BaseNodeSpec for AuraNode where Block: NodeBlock, @@ -214,11 +179,23 @@ where + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, AuraId: AuraIdT + Sync, - StartConsensus: self::StartConsensus + 'static, { type Block = Block; type RuntimeApi = RuntimeApi; type BuildImportQueue = BuildRelayToAuraImportQueue; +} + +impl NodeSpec + for AuraNode +where + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + substrate_frame_rpc_system::AccountNonceApi, + AuraId: AuraIdT + Sync, + StartConsensus: self::StartConsensus + 'static, +{ type BuildRpcExtensions = BuildParachainRpcExtensions; type StartConsensus = StartConsensus; const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; @@ -226,7 +203,7 @@ where pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, -) -> Box +) -> Box where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, @@ -252,82 +229,6 @@ where } } -/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain -/// decides what is backed and included. -pub(crate) struct StartRelayChainConsensus; - -impl StartConsensus, FakeRuntimeApi> for StartRelayChainConsensus { - fn start_consensus( - client: Arc, FakeRuntimeApi>>, - block_import: ParachainBlockImport, FakeRuntimeApi>, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc, ParachainClient, FakeRuntimeApi>>>, - _keystore: KeystorePtr, - _relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - _backend: Arc>>, - _node_extra_args: NodeExtraArgs, - ) -> Result<(), Error> { - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), - client.clone(), - transaction_pool, - prometheus_registry, - telemetry, - ); - - let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( - cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { - para_id, - proposer_factory, - block_import, - relay_chain_interface: relay_chain_interface.clone(), - create_inherent_data_providers: move |_, (relay_parent, validation_data)| { - let relay_chain_interface = relay_chain_interface.clone(); - async move { - let parachain_inherent = - cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( - relay_parent, - &relay_chain_interface, - &validation_data, - para_id, - ).await; - let parachain_inherent = parachain_inherent.ok_or_else(|| { - Box::::from( - "Failed to create parachain inherent", - ) - })?; - Ok(parachain_inherent) - } - }, - }, - ); - - let spawner = task_manager.spawn_handle(); - - // Required for free-for-all consensus - #[allow(deprecated)] - old_consensus::start_collator_sync(old_consensus::StartCollatorParams { - para_id, - block_status: client.clone(), - announce_block, - overseer_handle, - spawner, - key: collator_key, - parachain_consensus: free_for_all, - runtime_api: client.clone(), - }); - - Ok(()) - } -} - /// Start consensus using the lookahead aura collator. pub(crate) struct StartSlotBasedAuraConsensus( PhantomData<(Block, RuntimeApi, AuraId)>, diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs new file mode 100644 index 00000000000..d00d7adf27e --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs @@ -0,0 +1,233 @@ +// 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 . + +use crate::common::{ + rpc::BuildRpcExtensions as BuildRpcExtensionsT, + spec::{BaseNodeSpec, BuildImportQueue, NodeSpec as NodeSpecT}, + types::{Hash, ParachainBlockImport, ParachainClient}, +}; +use codec::Encode; +use cumulus_client_parachain_inherent::{MockValidationDataInherentDataProvider, MockXcmConfig}; +use cumulus_primitives_core::ParaId; +use sc_consensus::{DefaultImportQueue, LongestChain}; +use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; +use sc_network::NetworkBackend; +use sc_service::{build_polkadot_syncing_strategy, Configuration, PartialComponents, TaskManager}; +use sc_telemetry::TelemetryHandle; +use sp_runtime::traits::Header; +use sp_timestamp::Timestamp; +use std::{marker::PhantomData, sync::Arc}; + +pub struct ManualSealNode(PhantomData); + +impl BuildImportQueue + for ManualSealNode +{ + fn build_import_queue( + client: Arc>, + _block_import: ParachainBlockImport, + config: &Configuration, + _telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result> { + Ok(sc_consensus_manual_seal::import_queue( + Box::new(client.clone()), + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + )) + } +} + +impl BaseNodeSpec for ManualSealNode { + type Block = NodeSpec::Block; + type RuntimeApi = NodeSpec::RuntimeApi; + type BuildImportQueue = Self; +} + +impl ManualSealNode { + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn start_node( + &self, + mut config: Configuration, + para_id: ParaId, + block_time: u64, + ) -> sc_service::error::Result + where + Net: NetworkBackend, + { + let PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain: _, + transaction_pool, + other: (_, mut telemetry, _), + } = Self::new_partial(&config)?; + let select_chain = LongestChain::new(backend.clone()); + + // Since this is a dev node, prevent it from connecting to peers. + config.network.default_peers_set.in_peers = 0; + config.network.default_peers_set.out_peers = 0; + let mut net_config = sc_network::config::FullNetworkConfiguration::<_, _, Net>::new( + &config.network, + config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone()), + ); + let metrics = Net::register_notification_metrics( + config.prometheus_config.as_ref().map(|cfg| &cfg.registry), + ); + + let syncing_strategy = build_polkadot_syncing_strategy( + config.protocol_id(), + config.chain_spec.fork_id(), + &mut net_config, + None, + client.clone(), + &task_manager.spawn_handle(), + config.prometheus_config.as_ref().map(|config| &config.registry), + )?; + + let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: &config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue, + net_config, + block_announce_validator_builder: None, + syncing_strategy, + block_relay: None, + metrics, + })?; + + let proposer = sc_basic_authorship::ProposerFactory::new( + task_manager.spawn_handle(), + client.clone(), + transaction_pool.clone(), + None, + None, + ); + + let (manual_seal_sink, manual_seal_stream) = futures::channel::mpsc::channel(1024); + let mut manual_seal_sink_clone = manual_seal_sink.clone(); + task_manager + .spawn_essential_handle() + .spawn("block_authoring", None, async move { + loop { + futures_timer::Delay::new(std::time::Duration::from_millis(block_time)).await; + manual_seal_sink_clone + .try_send(sc_consensus_manual_seal::EngineCommand::SealNewBlock { + create_empty: true, + finalize: true, + parent_hash: None, + sender: None, + }) + .unwrap(); + } + }); + + let client_for_cidp = client.clone(); + let params = sc_consensus_manual_seal::ManualSealParams { + block_import: client.clone(), + env: proposer, + client: client.clone(), + pool: transaction_pool.clone(), + select_chain, + commands_stream: Box::pin(manual_seal_stream), + consensus_data_provider: None, + create_inherent_data_providers: move |block: Hash, ()| { + let current_para_head = client_for_cidp + .header(block) + .expect("Header lookup should succeed") + .expect("Header passed in as parent should be present in backend."); + let current_para_block_head = + Some(polkadot_primitives::HeadData(current_para_head.encode())); + let client_for_xcm = client_for_cidp.clone(); + async move { + use sp_runtime::traits::UniqueSaturatedInto; + + let mocked_parachain = MockValidationDataInherentDataProvider { + // When using manual seal we start from block 0, and it's very unlikely to + // reach a block number > u32::MAX. + current_para_block: UniqueSaturatedInto::::unique_saturated_into( + *current_para_head.number(), + ), + para_id, + current_para_block_head, + relay_offset: 1000, + relay_blocks_per_para_block: 1, + para_blocks_per_relay_epoch: 10, + relay_randomness_config: (), + xcm_config: MockXcmConfig::new(&*client_for_xcm, block, Default::default()), + raw_downward_messages: vec![], + raw_horizontal_messages: vec![], + additional_key_values: None, + }; + Ok(( + sp_timestamp::InherentDataProvider::new(Timestamp::new(0)), + mocked_parachain, + )) + } + }, + }; + let authorship_future = sc_consensus_manual_seal::run_manual_seal(params); + task_manager.spawn_essential_handle().spawn_blocking( + "manual-seal", + None, + authorship_future, + ); + let rpc_extensions_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + let mut module = NodeSpec::BuildRpcExtensions::build_rpc_extensions( + client.clone(), + backend_for_rpc.clone(), + transaction_pool.clone(), + )?; + module + .merge(ManualSeal::new(manual_seal_sink.clone()).into_rpc()) + .map_err(|e| sc_service::Error::Application(e.into()))?; + Ok(module) + }) + }; + + let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + network, + client: client.clone(), + keystore: keystore_container.keystore(), + task_manager: &mut task_manager, + transaction_pool: transaction_pool.clone(), + rpc_builder: rpc_extensions_builder, + backend, + system_rpc_tx, + tx_handler_controller, + sync_service, + config, + telemetry: telemetry.as_mut(), + })?; + + start_network.start_network(); + Ok(task_manager) + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs new file mode 100644 index 00000000000..36f54fa3d05 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs @@ -0,0 +1,58 @@ +// 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 . + +pub mod aura; +mod manual_seal; +pub mod shell; + +use crate::common::spec::{DynNodeSpec, NodeSpec as NodeSpecT}; +use cumulus_primitives_core::ParaId; +use manual_seal::ManualSealNode; +use sc_service::{Configuration, TaskManager}; + +/// Trait that extends the `DynNodeSpec` trait with manual seal related logic. +/// +/// We need it in order to be able to access both the `DynNodeSpec` and the manual seal logic +/// through dynamic dispatch. +pub trait DynNodeSpecExt: DynNodeSpec { + fn start_manual_seal_node( + &self, + config: Configuration, + para_id: ParaId, + block_time: u64, + ) -> sc_service::error::Result; +} + +impl DynNodeSpecExt for T +where + T: NodeSpecT + DynNodeSpec, +{ + #[sc_tracing::logging::prefix_logs_with("Parachain")] + fn start_manual_seal_node( + &self, + config: Configuration, + para_id: ParaId, + block_time: u64, + ) -> sc_service::error::Result { + let node = ManualSealNode::::new(); + match config.network.network_backend { + sc_network::config::NetworkBackendType::Libp2p => + node.start_node::>(config, para_id, block_time), + sc_network::config::NetworkBackendType::Litep2p => + node.start_node::(config, para_id, block_time), + } + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs new file mode 100644 index 00000000000..5f9c671d710 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs @@ -0,0 +1,152 @@ +// 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 . + +use crate::{ + common::{ + rpc::BuildEmptyRpcExtensions, + spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, + types::{Block, Hash, ParachainBackend, ParachainBlockImport, ParachainClient}, + NodeExtraArgs, + }, + fake_runtime_api::aura_sr25519::RuntimeApi as FakeRuntimeApi, +}; +#[docify::export(slot_based_colator_import)] +#[allow(deprecated)] +use cumulus_client_service::old_consensus; +use cumulus_client_service::CollatorSybilResistance; +use cumulus_primitives_core::ParaId; +use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; +use polkadot_primitives::CollatorPair; +use prometheus_endpoint::Registry; +use sc_consensus::DefaultImportQueue; +use sc_service::{Configuration, Error, TaskManager}; +use sc_telemetry::TelemetryHandle; +use sc_transaction_pool::FullPool; +use sp_keystore::KeystorePtr; +use std::{sync::Arc, time::Duration}; + +/// Build the import queue for the shell runtime. +pub(crate) struct BuildShellImportQueue; + +impl BuildImportQueue, FakeRuntimeApi> for BuildShellImportQueue { + fn build_import_queue( + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, + config: &Configuration, + _telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result>> { + cumulus_client_consensus_relay_chain::import_queue( + client, + block_import, + |_, _| async { Ok(()) }, + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + ) + .map_err(Into::into) + } +} + +/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain +/// decides what is backed and included. +pub(crate) struct StartRelayChainConsensus; + +impl StartConsensus, FakeRuntimeApi> for StartRelayChainConsensus { + fn start_consensus( + client: Arc, FakeRuntimeApi>>, + block_import: ParachainBlockImport, FakeRuntimeApi>, + prometheus_registry: Option<&Registry>, + telemetry: Option, + task_manager: &TaskManager, + relay_chain_interface: Arc, + transaction_pool: Arc, ParachainClient, FakeRuntimeApi>>>, + _keystore: KeystorePtr, + _relay_chain_slot_duration: Duration, + para_id: ParaId, + collator_key: CollatorPair, + overseer_handle: OverseerHandle, + announce_block: Arc>) + Send + Sync>, + _backend: Arc>>, + _node_extra_args: NodeExtraArgs, + ) -> Result<(), Error> { + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry, + ); + + let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( + cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { + para_id, + proposer_factory, + block_import, + relay_chain_interface: relay_chain_interface.clone(), + create_inherent_data_providers: move |_, (relay_parent, validation_data)| { + let relay_chain_interface = relay_chain_interface.clone(); + async move { + let parachain_inherent = + cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( + relay_parent, + &relay_chain_interface, + &validation_data, + para_id, + ).await; + let parachain_inherent = parachain_inherent.ok_or_else(|| { + Box::::from( + "Failed to create parachain inherent", + ) + })?; + Ok(parachain_inherent) + } + }, + }, + ); + + let spawner = task_manager.spawn_handle(); + + // Required for free-for-all consensus + #[allow(deprecated)] + old_consensus::start_collator_sync(old_consensus::StartCollatorParams { + para_id, + block_status: client.clone(), + announce_block, + overseer_handle, + spawner, + key: collator_key, + parachain_consensus: free_for_all, + runtime_api: client.clone(), + }); + + Ok(()) + } +} + +pub(crate) struct ShellNode; + +impl BaseNodeSpec for ShellNode { + type Block = Block; + type RuntimeApi = FakeRuntimeApi; + type BuildImportQueue = BuildShellImportQueue; +} + +impl NodeSpec for ShellNode { + type BuildRpcExtensions = BuildEmptyRpcExtensions, Self::RuntimeApi>; + type StartConsensus = StartRelayChainConsensus; + + const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; +} diff --git a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs index 38ef18b88e0..14a0b610796 100644 --- a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs +++ b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs @@ -85,7 +85,7 @@ //! This phase consists of plugging in the new slot-based collator. //! //! 1. In `node/src/service.rs` import the slot based collator instead of the lookahead collator. -#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs", slot_based_colator_import)] +#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs", slot_based_colator_import)] //! //! 2. In `start_consensus()` //! - Remove the `overseer_handle` param (also remove the @@ -94,7 +94,7 @@ //! `slot_drift` field with a value of `Duration::from_secs(1)`. //! - Replace the single future returned by `aura::run` with the two futures returned by it and //! spawn them as separate tasks: -#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/service.rs", launch_slot_based_collator)] +#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs", launch_slot_based_collator)] //! //! 3. In `start_parachain_node()` remove the `overseer_handle` param passed to `start_consensus`. //! -- GitLab From 8614dc0e055d06de4a3774ac1da0a422b33f34e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 2 Oct 2024 17:37:21 +0200 Subject: [PATCH 035/127] Always amend to the same commit for gh-pages (#5909) --- .gitlab/pipeline/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index 5ad9ae9bfb3..c0ce058f17f 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -54,7 +54,7 @@ publish-rustdoc: # This causes GitLab to exit immediately and marks this job failed. # We don't want to mark the entire job failed if there's nothing to # publish though, hence the `|| true`. - - git commit -m "___Updated docs for ${CI_COMMIT_REF_NAME}___" || + - git commit --amend -m "___Updated docs" || echo "___Nothing to commit___" - git push origin gh-pages --force # artificial sleep to publish gh-pages -- GitLab From 00f7104c1468464db945940a785340c66b76a0c7 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:05:06 -0300 Subject: [PATCH 036/127] bump zombienet version `v1.3.112` (#5916) Bump `zombienet` version, including fixes (`ci`) and the latest version of `pjs` embedded. Thx! --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index f793271d8f4..b7f2397d20b 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.111" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.112" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" -- GitLab From 3313163485c4d4946ea97477407107796469f1c4 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 3 Oct 2024 20:04:27 +0200 Subject: [PATCH 037/127] rpc v2: backpressure chainHead_v1_storage (#5741) Close https://github.com/paritytech/polkadot-sdk/issues/5589 This PR makes it possible for `rpc_v2::Storage::query_iter_paginated` to be "backpressured" which is achieved by having a channel where the result is sent back and when this channel is "full" we pause the iteration. The chainHead_follow has an internal channel which doesn't represent the actual connection and that is set to a very small number (16). Recall that the JSON-RPC server has a dedicate buffer for each connection by default of 64. #### Notes - Because `archive_storage` also depends on `rpc_v2::Storage::query_iter_paginated` I had to tweak the method to support limits as well. The reason is that archive_storage won't get backpressured properly because it's not an subscription. (it would much easier if it would be a subscription in rpc v2 spec because nothing against querying huge amount storage keys) - `query_iter_paginated` doesn't necessarily return the storage "in order" such as - `query_iter_paginated(vec![("key1", hash), ("key2", value)], ...)` could return them in arbitrary order because it's wrapped in FuturesUnordered but I could change that if we want to process it inorder (it's slower) - there is technically no limit on the number of storage queries in each `chainHead_v1_storage call` rather than the rpc max message limit which 10MB and only allowed to max 16 calls `chainHead_v1_x` concurrently (this should be fine) #### Benchmarks using subxt on localhost - Iterate over 10 accounts on westend-dev -> ~2-3x faster - Fetch 1024 storage values (i.e, not descedant values) -> ~50x faster - Fetch 1024 descendant values -> ~500x faster The reason for this is because as Josep explained in the issue is that one is only allowed query five storage items per call and clients has make lots of calls to drive it forward.. --------- Co-authored-by: command-bot <> Co-authored-by: James Wilson --- Cargo.lock | 28 +-- Cargo.toml | 2 +- prdoc/pr_5741.prdoc | 25 ++ substrate/client/rpc-servers/src/lib.rs | 5 +- substrate/client/rpc-spec-v2/Cargo.toml | 3 +- .../client/rpc-spec-v2/src/archive/archive.rs | 1 + .../rpc-spec-v2/src/chain_head/chain_head.rs | 127 ++++++---- .../src/chain_head/chain_head_storage.rs | 224 ++++++------------ .../client/rpc-spec-v2/src/chain_head/mod.rs | 7 + .../src/chain_head/subscription/inner.rs | 192 +++++---------- .../src/chain_head/subscription/mod.rs | 2 +- .../rpc-spec-v2/src/chain_head/tests.rs | 183 ++++---------- .../client/rpc-spec-v2/src/common/storage.rs | 55 ++++- 13 files changed, 369 insertions(+), 485 deletions(-) create mode 100644 prdoc/pr_5741.prdoc diff --git a/Cargo.lock b/Cargo.lock index db33c59f803..46c8b607e8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7149,9 +7149,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -7753,7 +7753,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -7811,7 +7811,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi 0.3.9", "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -9622,13 +9622,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -10410,7 +10411,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi 0.3.9", "libc", ] @@ -24894,21 +24895,20 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -24923,9 +24923,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", diff --git a/Cargo.toml b/Cargo.toml index e2c6d6c8ded..70eda144b5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1322,7 +1322,7 @@ tikv-jemalloc-ctl = { version = "0.5.0" } tikv-jemallocator = { version = "0.5.0" } time = { version = "0.3" } tiny-keccak = { version = "2.0.2" } -tokio = { version = "1.37.0", default-features = false } +tokio = { version = "1.40.0", default-features = false } tokio-retry = { version = "0.3.0" } tokio-stream = { version = "0.1.14" } tokio-test = { version = "0.4.2" } diff --git a/prdoc/pr_5741.prdoc b/prdoc/pr_5741.prdoc new file mode 100644 index 00000000000..5eafbc90ee8 --- /dev/null +++ b/prdoc/pr_5741.prdoc @@ -0,0 +1,25 @@ +# 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: make RPC endpoint `chainHead_v1_storage` faster + +doc: + - audience: Node Operator + description: | + The RPC endpoint `chainHead_v1_storage` now relies solely on backpressure to + determine how quickly to serve back values instead of handing back a fixed number + of entries and then expecting the client to ask for more. This should improve the + throughput for bigger storage queries significantly. + + Benchmarks using subxt on localhost: + - Iterate over 10 accounts on westend-dev -> ~2-3x faster + - Fetch 1024 storage values (i.e, not descedant values) -> ~50x faster + - Fetch 1024 descendant values -> ~500x faster + +crates: + - name: sc-rpc-spec-v2 + bump: major + - name: sc-rpc-server + bump: patch + - name: sc-service + bump: major diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 756e2a08c6d..31e4042d81f 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -255,8 +255,9 @@ where ), }; - let rpc_middleware = - RpcServiceBuilder::new().option_layer(middleware_layer.clone()); + let rpc_middleware = RpcServiceBuilder::new() + .rpc_logger(1024) + .option_layer(middleware_layer.clone()); let mut svc = service_builder .set_rpc_middleware(rpc_middleware) .build(methods, stop_handle); diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index ae21895de38..58dd8b830be 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -28,7 +28,6 @@ sp-rpc = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } codec = { workspace = true, default-features = true } thiserror = { workspace = true } @@ -56,6 +55,8 @@ sp-externalities = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-service = { features = ["test-helpers"], workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true, features = ["test-helpers"] } assert_matches = { workspace = true } pretty_assertions = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 82c6b2cacc2..dd6c566a76e 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -275,6 +275,7 @@ where self.storage_max_descendant_responses, self.storage_max_queried_items, ); + Ok(storage_client.handle_query(hash, items, child_trie)) } } 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 1bc5cecb205..a88e7f2a0b3 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 @@ -27,14 +27,15 @@ use crate::{ api::ChainHeadApiServer, chain_head_follow::ChainHeadFollower, error::Error as ChainHeadRpcError, - event::{FollowEvent, MethodResponse, OperationError}, - subscription::{SubscriptionManagement, SubscriptionManagementError}, + event::{FollowEvent, MethodResponse, OperationError, OperationId, OperationStorageItems}, + subscription::{StopHandle, SubscriptionManagement, SubscriptionManagementError}, + FollowEventSendError, FollowEventSender, }, - common::events::StorageQuery, + common::{events::StorageQuery, storage::QueryResult}, hex_string, SubscriptionTaskExecutor, }; use codec::Encode; -use futures::{channel::oneshot, future::FutureExt}; +use futures::{channel::oneshot, future::FutureExt, SinkExt}; use jsonrpsee::{ core::async_trait, server::ResponsePayload, types::SubscriptionId, ConnectionId, Extensions, MethodResponseFuture, PendingSubscriptionSink, @@ -51,9 +52,16 @@ use sp_core::{traits::CallContext, Bytes}; use sp_rpc::list::ListOrValue; use sp_runtime::traits::Block as BlockT; use std::{marker::PhantomData, sync::Arc, time::Duration}; +use tokio::sync::mpsc; pub(crate) const LOG_TARGET: &str = "rpc-spec-v2"; +/// The buffer capacity for each storage query. +/// +/// This is small because the underlying JSON-RPC server has +/// its down buffer capacity per connection as well. +const STORAGE_QUERY_BUF: usize = 16; + /// The configuration of [`ChainHead`]. pub struct ChainHeadConfig { /// The maximum number of pinned blocks across all subscriptions. @@ -65,9 +73,6 @@ pub struct ChainHeadConfig { /// Stop all subscriptions if the distance between the leaves and the current finalized /// block is larger than this value. pub max_lagging_distance: usize, - /// The maximum number of items reported by the `chainHead_storage` before - /// pagination is required. - pub operation_max_storage_items: usize, /// The maximum number of `chainHead_follow` subscriptions per connection. pub max_follow_subscriptions_per_connection: usize, } @@ -87,10 +92,6 @@ const MAX_PINNED_DURATION: Duration = Duration::from_secs(60); /// Note: The lower limit imposed by the spec is 16. const MAX_ONGOING_OPERATIONS: usize = 16; -/// The maximum number of items the `chainHead_storage` can return -/// before paginations is required. -const MAX_STORAGE_ITER_ITEMS: usize = 5; - /// Stop all subscriptions if the distance between the leaves and the current finalized /// block is larger than this value. const MAX_LAGGING_DISTANCE: usize = 128; @@ -105,7 +106,6 @@ impl Default for ChainHeadConfig { subscription_max_pinned_duration: MAX_PINNED_DURATION, subscription_max_ongoing_operations: MAX_ONGOING_OPERATIONS, max_lagging_distance: MAX_LAGGING_DISTANCE, - operation_max_storage_items: MAX_STORAGE_ITER_ITEMS, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, } } @@ -121,9 +121,6 @@ pub struct ChainHead, Block: BlockT, Client> { executor: SubscriptionTaskExecutor, /// Keep track of the pinned blocks for each subscription. subscriptions: SubscriptionManagement, - /// The maximum number of items reported by the `chainHead_storage` before - /// pagination is required. - operation_max_storage_items: usize, /// Stop all subscriptions if the distance between the leaves and the current finalized /// block is larger than this value. max_lagging_distance: usize, @@ -150,7 +147,6 @@ impl, Block: BlockT, Client> ChainHead { config.max_follow_subscriptions_per_connection, backend, ), - operation_max_storage_items: config.operation_max_storage_items, max_lagging_distance: config.max_lagging_distance, _phantom: PhantomData, } @@ -314,7 +310,7 @@ where }), }; - let (rp, rp_fut) = method_started_response(operation_id, None); + let (rp, rp_fut) = method_started_response(operation_id); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -322,7 +318,7 @@ where return; } - let _ = block_guard.response_sender().unbounded_send(event); + let _ = block_guard.response_sender().send(event).await; }; executor.spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed()); @@ -426,20 +422,10 @@ where Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; - let mut storage_client = ChainHeadStorage::::new( - self.client.clone(), - self.operation_max_storage_items, - ); - let operation = block_guard.operation(); - let operation_id = operation.operation_id(); + let mut storage_client = ChainHeadStorage::::new(self.client.clone()); - // The number of operations we are allowed to execute. - let num_operations = operation.num_reserved(); - let discarded = items.len().saturating_sub(num_operations); - let mut items = items; - items.truncate(num_operations); + let (rp, rp_fut) = method_started_response(block_guard.operation().operation_id()); - let (rp, rp_fut) = method_started_response(operation_id, Some(discarded)); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -447,10 +433,20 @@ where return; } - storage_client.generate_events(block_guard, hash, items, child_trie).await; + let (tx, rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); + let operation_id = block_guard.operation().operation_id(); + let stop_handle = block_guard.operation().stop_handle().clone(); + let response_sender = block_guard.response_sender(); + + // May fail if the channel is closed or the connection is closed. + // which is okay to ignore. + let _ = futures::future::join( + storage_client.generate_events(hash, items, child_trie, tx), + process_storage_items(rx, response_sender, operation_id, &stop_handle), + ) + .await; }; - self.executor - .spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); rp } @@ -503,7 +499,7 @@ where let operation_id = block_guard.operation().operation_id(); let client = self.client.clone(); - let (rp, rp_fut) = method_started_response(operation_id.clone(), None); + let (rp, rp_fut) = method_started_response(operation_id.clone()); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -527,7 +523,7 @@ where }) }); - let _ = block_guard.response_sender().unbounded_send(event); + let _ = block_guard.response_sender().send(event).await; }; self.executor .spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed()); @@ -588,13 +584,9 @@ where return Ok(()) } - let Some(operation) = self.subscriptions.get_operation(&follow_subscription, &operation_id) - else { - return Ok(()) - }; - - if !operation.submit_continue() { - // Continue called without generating a `WaitingForContinue` event. + // WaitingForContinue event is never emitted, in such cases + // emit an `InvalidContinue error`. + if self.subscriptions.get_operation(&follow_subscription, &operation_id).is_some() { Err(ChainHeadRpcError::InvalidContinue.into()) } else { Ok(()) @@ -616,12 +608,13 @@ where return Ok(()) } - let Some(operation) = self.subscriptions.get_operation(&follow_subscription, &operation_id) + let Some(mut operation) = + self.subscriptions.get_operation(&follow_subscription, &operation_id) else { return Ok(()) }; - operation.stop_operation(); + operation.stop(); Ok(()) } @@ -629,9 +622,8 @@ where fn method_started_response( operation_id: String, - discarded_items: Option, ) -> (ResponsePayload<'static, MethodResponse>, MethodResponseFuture) { - let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items }); + let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None }); ResponsePayload::success(rp).notify_on_completion() } @@ -657,3 +649,46 @@ where rx } + +async fn process_storage_items( + mut storage_query_stream: mpsc::Receiver, + mut sender: FollowEventSender, + operation_id: String, + stop_handle: &StopHandle, +) -> Result<(), FollowEventSendError> { + loop { + tokio::select! { + _ = stop_handle.stopped() => { + break; + }, + + maybe_storage = storage_query_stream.recv() => { + let Some(storage) = maybe_storage else { + break; + }; + + let item = match storage { + QueryResult::Err(error) => { + return sender + .send(FollowEvent::OperationError(OperationError { operation_id, error })) + .await + } + QueryResult::Ok(Some(v)) => v, + QueryResult::Ok(None) => continue, + }; + + sender + .send(FollowEvent::OperationStorageItems(OperationStorageItems { + operation_id: operation_id.clone(), + items: vec![item], + })).await?; + }, + } + } + + sender + .send(FollowEvent::OperationStorageDone(OperationId { operation_id })) + .await?; + + Ok(()) +} diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_storage.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_storage.rs index ee39ec253a3..936117e66f9 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_storage.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_storage.rs @@ -18,45 +18,34 @@ //! Implementation of the `chainHead_storage` method. -use std::{collections::VecDeque, marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc}; use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; -use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::Block as BlockT; +use tokio::sync::mpsc; -use crate::{ - chain_head::{ - event::{OperationError, OperationId, OperationStorageItems}, - subscription::BlockGuard, - FollowEvent, - }, - common::{ - events::{StorageQuery, StorageQueryType}, - storage::{IterQueryType, QueryIter, QueryIterResult, Storage}, - }, +use crate::common::{ + events::{StorageQuery, StorageQueryType}, + storage::{IterQueryType, QueryIter, QueryResult, Storage}, }; /// Generates the events of the `chainHead_storage` method. pub struct ChainHeadStorage { /// Storage client. client: Storage, - /// Queue of operations that may require pagination. - iter_operations: VecDeque, - /// The maximum number of items reported by the `chainHead_storage` before - /// pagination is required. - operation_max_storage_items: usize, _phandom: PhantomData<(BE, Block)>, } +impl Clone for ChainHeadStorage { + fn clone(&self) -> Self { + Self { client: self.client.clone(), _phandom: PhantomData } + } +} + impl ChainHeadStorage { /// Constructs a new [`ChainHeadStorage`]. - pub fn new(client: Arc, operation_max_storage_items: usize) -> Self { - Self { - client: Storage::new(client), - iter_operations: VecDeque::new(), - operation_max_storage_items, - _phandom: PhantomData, - } + pub fn new(client: Arc) -> Self { + Self { client: Storage::new(client), _phandom: PhantomData } } } @@ -64,146 +53,71 @@ impl ChainHeadStorage where Block: BlockT + 'static, BE: Backend + 'static, - Client: StorageProvider + 'static, + Client: StorageProvider + Send + Sync + 'static, { - /// Iterate over (key, hash) and (key, value) generating the `WaitingForContinue` event if - /// necessary. - async fn generate_storage_iter_events( - &mut self, - mut block_guard: BlockGuard, - hash: Block::Hash, - child_key: Option, - ) { - let sender = block_guard.response_sender(); - let operation = block_guard.operation(); - - while let Some(query) = self.iter_operations.pop_front() { - if operation.was_stopped() { - return - } - - let result = self.client.query_iter_pagination( - query, - hash, - child_key.as_ref(), - self.operation_max_storage_items, - ); - let (events, maybe_next_query) = match result { - QueryIterResult::Ok(result) => result, - QueryIterResult::Err(error) => { - send_error::(&sender, operation.operation_id(), error.to_string()); - return - }, - }; - - if !events.is_empty() { - // Send back the results of the iteration produced so far. - let _ = sender.unbounded_send(FollowEvent::::OperationStorageItems( - OperationStorageItems { operation_id: operation.operation_id(), items: events }, - )); - } - - if let Some(next_query) = maybe_next_query { - let _ = - sender.unbounded_send(FollowEvent::::OperationWaitingForContinue( - OperationId { operation_id: operation.operation_id() }, - )); - - // The operation might be continued or cancelled only after the - // `OperationWaitingForContinue` is generated above. - operation.wait_for_continue().await; - - // Give a chance for the other items to advance next time. - self.iter_operations.push_back(next_query); - } - } - - if operation.was_stopped() { - return - } - - let _ = - sender.unbounded_send(FollowEvent::::OperationStorageDone(OperationId { - operation_id: operation.operation_id(), - })); - } - /// Generate the block events for the `chainHead_storage` method. pub async fn generate_events( &mut self, - mut block_guard: BlockGuard, hash: Block::Hash, items: Vec>, child_key: Option, - ) { - let sender = block_guard.response_sender(); - let operation = block_guard.operation(); - - let mut storage_results = Vec::with_capacity(items.len()); - for item in items { - match item.query_type { - StorageQueryType::Value => { - match self.client.query_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => { - send_error::(&sender, operation.operation_id(), error); - return - }, - } - }, - StorageQueryType::Hash => - match self.client.query_hash(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => { - send_error::(&sender, operation.operation_id(), error); - return - }, + tx: mpsc::Sender, + ) -> Result<(), tokio::task::JoinError> { + let this = self.clone(); + + tokio::task::spawn_blocking(move || { + for item in items { + match item.query_type { + StorageQueryType::Value => { + let rp = this.client.query_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } }, - StorageQueryType::ClosestDescendantMerkleValue => - match self.client.query_merkle_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => { - send_error::(&sender, operation.operation_id(), error); - return - }, + StorageQueryType::Hash => { + let rp = this.client.query_hash(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } }, - StorageQueryType::DescendantsValues => self.iter_operations.push_back(QueryIter { - query_key: item.key, - ty: IterQueryType::Value, - pagination_start_key: None, - }), - StorageQueryType::DescendantsHashes => self.iter_operations.push_back(QueryIter { - query_key: item.key, - ty: IterQueryType::Hash, - pagination_start_key: None, - }), - }; - } - - if !storage_results.is_empty() { - let _ = sender.unbounded_send(FollowEvent::::OperationStorageItems( - OperationStorageItems { - operation_id: operation.operation_id(), - items: storage_results, - }, - )); - } + StorageQueryType::ClosestDescendantMerkleValue => { + let rp = + this.client.query_merkle_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::DescendantsValues => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Value, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + StorageQueryType::DescendantsHashes => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Hash, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + } + } + }) + .await?; - self.generate_storage_iter_events(block_guard, hash, child_key).await + Ok(()) } } - -/// Build and send the opaque error back to the `chainHead_follow` method. -fn send_error( - sender: &TracingUnboundedSender>, - operation_id: String, - error: String, -) { - let _ = sender.unbounded_send(FollowEvent::::OperationError(OperationError { - operation_id, - error, - })); -} 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 c9fe19aca2b..98ddfbbdc63 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/mod.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/mod.rs @@ -42,3 +42,10 @@ pub use event::{ BestBlockChanged, ErrorEvent, Finalized, FollowEvent, Initialized, NewBlock, RuntimeEvent, RuntimeVersionEvent, }; + +/// Follow event sender. +pub(crate) type FollowEventSender = futures::channel::mpsc::Sender>; +/// Follow event receiver. +pub(crate) type FollowEventReceiver = futures::channel::mpsc::Receiver>; +/// Follow event send error. +pub(crate) type FollowEventSendError = futures::channel::mpsc::SendError; 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 14325b4fbb9..95a7c7fe183 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 @@ -19,18 +19,25 @@ use futures::channel::oneshot; use parking_lot::Mutex; 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, HashSet}, - sync::{atomic::AtomicBool, Arc}, + sync::Arc, time::{Duration, Instant}, }; -use crate::chain_head::{subscription::SubscriptionManagementError, FollowEvent}; +use crate::chain_head::{ + subscription::SubscriptionManagementError, FollowEventReceiver, FollowEventSender, +}; + +type NotifyOnDrop = tokio::sync::mpsc::Receiver<()>; +type SharedOperations = Arc>>; -/// The queue size after which the `sc_utils::mpsc::tracing_unbounded` would produce warnings. -const QUEUE_SIZE_WARNING: usize = 512; +/// The buffer capacity for each subscription +/// +/// Beware of that the JSON-RPC server has a global +/// buffer per connection and this a extra buffer. +const BUF_CAP_PER_SUBSCRIPTION: usize = 16; /// The state machine of a block of a single subscription ID. /// @@ -138,7 +145,7 @@ impl LimitOperations { .try_acquire_many_owned(num_ops.try_into().ok()?) .ok()?; - Some(PermitOperations { num_ops, _permit: permits }) + Some(permits) } } @@ -148,79 +155,36 @@ impl LimitOperations { /// to guarantee the RPC server can execute the number of operations. /// /// The number of reserved items are given back to the [`LimitOperations`] on drop. -struct PermitOperations { - /// The number of operations permitted (reserved). - num_ops: usize, - /// The permit for these operations. - _permit: tokio::sync::OwnedSemaphorePermit, -} +type PermitOperations = tokio::sync::OwnedSemaphorePermit; -/// The state of one operation. -/// -/// This is directly exposed to users via `chain_head_unstable_continue` and -/// `chain_head_unstable_stop_operation`. +/// Stop handle for the operation. #[derive(Clone)] -pub struct OperationState { - /// The shared operation state that holds information about the - /// `waitingForContinue` event and cancellation. - shared_state: Arc, - /// Send notifications when the user calls `chainHead_continue` method. - send_continue: tokio::sync::mpsc::Sender<()>, -} +pub struct StopHandle(tokio::sync::mpsc::Sender<()>); -impl OperationState { - /// Returns true if `chainHead_continue` is called after the - /// `waitingForContinue` event was emitted for the associated - /// operation ID. - pub fn submit_continue(&self) -> bool { - // `waitingForContinue` not generated. - if !self.shared_state.requested_continue.load(std::sync::atomic::Ordering::Acquire) { - return false - } - - // Has enough capacity for 1 message. - // Can fail if the `stop_operation` propagated the stop first. - self.send_continue.try_send(()).is_ok() +impl StopHandle { + pub async fn stopped(&self) { + self.0.closed().await; } - /// Stops the operation if `waitingForContinue` event was emitted for the associated - /// operation ID. - /// - /// Returns nothing in accordance with `chainHead_v1_stopOperation`. - pub fn stop_operation(&self) { - // `waitingForContinue` not generated. - if !self.shared_state.requested_continue.load(std::sync::atomic::Ordering::Acquire) { - return - } - - self.shared_state - .operation_stopped - .store(true, std::sync::atomic::Ordering::Release); - - // Send might not have enough capacity if `submit_continue` was sent first. - // However, the `operation_stopped` boolean was set. - let _ = self.send_continue.try_send(()); + pub fn is_stopped(&self) -> bool { + self.0.is_closed() } } /// The shared operation state between the backend [`RegisteredOperation`] and frontend /// [`RegisteredOperation`]. -struct SharedOperationState { - /// True if the `chainHead` generated `waitingForContinue` event. - requested_continue: AtomicBool, - /// True if the operation was cancelled by the user. - operation_stopped: AtomicBool, +#[derive(Clone)] +pub struct OperationState { + stop: StopHandle, + operations: SharedOperations, + operation_id: String, } -impl SharedOperationState { - /// Constructs a new [`SharedOperationState`]. - /// - /// This is efficiently cloned under a single heap allocation. - fn new() -> Arc { - Arc::new(SharedOperationState { - requested_continue: AtomicBool::new(false), - operation_stopped: AtomicBool::new(false), - }) +impl OperationState { + pub fn stop(&mut self) { + if !self.stop.is_stopped() { + self.operations.lock().remove(&self.operation_id); + } } } @@ -228,59 +192,31 @@ impl SharedOperationState { /// /// This is used internally by the `chainHead` methods. pub struct RegisteredOperation { - /// The shared operation state that holds information about the - /// `waitingForContinue` event and cancellation. - shared_state: Arc, - /// Receive notifications when the user calls `chainHead_continue` method. - recv_continue: tokio::sync::mpsc::Receiver<()>, + /// Stop handle for the operation. + stop_handle: StopHandle, + /// Track the operations ID of this subscription. + operations: SharedOperations, /// The operation ID of the request. operation_id: String, - /// Track the operations ID of this subscription. - operations: Arc>>, /// Permit a number of items to be executed by this operation. - permit: PermitOperations, + _permit: PermitOperations, } impl RegisteredOperation { - /// Wait until the user calls `chainHead_continue` or the operation - /// is cancelled via `chainHead_stopOperation`. - pub async fn wait_for_continue(&mut self) { - self.shared_state - .requested_continue - .store(true, std::sync::atomic::Ordering::Release); - - // The sender part of this channel is around for as long as this object exists, - // because it is stored in the `OperationState` of the `operations` field. - // The sender part is removed from tracking when this object is dropped. - let _ = self.recv_continue.recv().await; - - self.shared_state - .requested_continue - .store(false, std::sync::atomic::Ordering::Release); - } - - /// Returns true if the current operation was stopped. - pub fn was_stopped(&self) -> bool { - self.shared_state.operation_stopped.load(std::sync::atomic::Ordering::Acquire) + /// Stop handle for the operation. + pub fn stop_handle(&self) -> &StopHandle { + &self.stop_handle } /// Get the operation ID. pub fn operation_id(&self) -> String { self.operation_id.clone() } - - /// Returns the number of reserved elements for this permit. - /// - /// This can be smaller than the number of items requested via [`LimitOperations::reserve()`]. - pub fn num_reserved(&self) -> usize { - self.permit.num_ops - } } impl Drop for RegisteredOperation { fn drop(&mut self) { - let mut operations = self.operations.lock(); - operations.remove(&self.operation_id); + self.operations.lock().remove(&self.operation_id); } } @@ -291,7 +227,7 @@ struct Operations { /// Limit the number of ongoing operations. limits: LimitOperations, /// Track the operations ID of this subscription. - operations: Arc>>, + operations: SharedOperations, } impl Operations { @@ -307,25 +243,25 @@ impl Operations { /// Register a new operation. pub fn register_operation(&mut self, to_reserve: usize) -> Option { let permit = self.limits.reserve_at_most(to_reserve)?; - let operation_id = self.next_operation_id(); - // At most one message can be sent. - let (send_continue, recv_continue) = tokio::sync::mpsc::channel(1); - let shared_state = SharedOperationState::new(); - - let state = OperationState { send_continue, shared_state: shared_state.clone() }; - - // Cloned operations for removing the current ID on drop. + let (tx, rx) = tokio::sync::mpsc::channel(1); + let stop_handle = StopHandle(tx); let operations = self.operations.clone(); - operations.lock().insert(operation_id.clone(), state); + operations.lock().insert(operation_id.clone(), (rx, stop_handle.clone())); - Some(RegisteredOperation { shared_state, operation_id, recv_continue, operations, permit }) + Some(RegisteredOperation { stop_handle, operation_id, operations, _permit: permit }) } /// Get the associated operation state with the ID. pub fn get_operation(&self, id: &str) -> Option { - self.operations.lock().get(id).map(|state| state.clone()) + let stop = self.operations.lock().get(id).map(|(_, stop)| stop.clone())?; + + Some(OperationState { + stop, + operations: self.operations.clone(), + operation_id: id.to_string(), + }) } /// Generate the next operation ID for this subscription. @@ -352,7 +288,7 @@ struct SubscriptionState { /// The sender of message responses to the `chainHead_follow` events. /// /// This object is cloned between methods. - response_sender: TracingUnboundedSender>, + response_sender: FollowEventSender, /// The ongoing operations of a subscription. operations: Operations, /// Track the block hashes available for this subscription. @@ -486,7 +422,7 @@ impl SubscriptionState { pub struct BlockGuard> { hash: Block::Hash, with_runtime: bool, - response_sender: TracingUnboundedSender>, + response_sender: FollowEventSender, operation: RegisteredOperation, backend: Arc, } @@ -504,7 +440,7 @@ impl> BlockGuard { fn new( hash: Block::Hash, with_runtime: bool, - response_sender: TracingUnboundedSender>, + response_sender: FollowEventSender, operation: RegisteredOperation, backend: Arc, ) -> Result { @@ -521,7 +457,7 @@ impl> BlockGuard { } /// Send message responses from the `chainHead` methods to `chainHead_follow`. - pub fn response_sender(&self) -> TracingUnboundedSender> { + pub fn response_sender(&self) -> FollowEventSender { self.response_sender.clone() } @@ -543,7 +479,7 @@ pub struct InsertedSubscriptionData { /// Signal that the subscription must stop. pub rx_stop: oneshot::Receiver<()>, /// Receive message responses from the `chainHead` methods. - pub response_receiver: TracingUnboundedReceiver>, + pub response_receiver: FollowEventReceiver, } pub struct SubscriptionsInner> { @@ -594,7 +530,7 @@ impl> SubscriptionsInner { if let Entry::Vacant(entry) = self.subs.entry(sub_id) { let (tx_stop, rx_stop) = oneshot::channel(); let (response_sender, response_receiver) = - tracing_unbounded("chain-head-method-responses", QUEUE_SIZE_WARNING); + futures::channel::mpsc::channel(BUF_CAP_PER_SUBSCRIPTION); let state = SubscriptionState:: { with_runtime, tx_stop: Some(tx_stop), @@ -972,8 +908,7 @@ mod tests { #[test] fn sub_state_register_twice() { - let (response_sender, _response_receiver) = - tracing_unbounded("test-chain-head-method-responses", QUEUE_SIZE_WARNING); + let (response_sender, _response_receiver) = futures::channel::mpsc::channel(1); let mut sub_state = SubscriptionState:: { with_runtime: false, tx_stop: None, @@ -1001,8 +936,7 @@ mod tests { #[test] fn sub_state_register_unregister() { - let (response_sender, _response_receiver) = - tracing_unbounded("test-chain-head-method-responses", QUEUE_SIZE_WARNING); + let (response_sender, _response_receiver) = futures::channel::mpsc::channel(1); let mut sub_state = SubscriptionState:: { with_runtime: false, tx_stop: None, @@ -1349,12 +1283,12 @@ mod tests { // One operation is reserved. let permit_one = ops.reserve_at_most(1).unwrap(); - assert_eq!(permit_one.num_ops, 1); + assert_eq!(permit_one.num_permits(), 1); // Request 2 operations, however there is capacity only for one. let permit_two = ops.reserve_at_most(2).unwrap(); // Number of reserved permits is smaller than provided. - assert_eq!(permit_two.num_ops, 1); + assert_eq!(permit_two.num_permits(), 1); // Try to reserve operations when there's no space. let permit = ops.reserve_at_most(1); @@ -1365,7 +1299,7 @@ mod tests { // Can reserve again let permit_three = ops.reserve_at_most(1).unwrap(); - assert_eq!(permit_three.num_ops, 1); + assert_eq!(permit_three.num_permits(), 1); } #[test] diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/mod.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/mod.rs index f266c9d8b34..84d1b8f8f9b 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/mod.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/mod.rs @@ -34,7 +34,7 @@ use self::inner::SubscriptionsInner; pub use self::inner::OperationState; pub use error::SubscriptionManagementError; -pub use inner::{BlockGuard, InsertedSubscriptionData}; +pub use inner::{BlockGuard, InsertedSubscriptionData, StopHandle}; /// Manage block pinning / unpinning for subscription IDs. pub struct SubscriptionManagement> { 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 30a01b93b31..44a2849d915 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -33,12 +33,12 @@ use jsonrpsee::{ }; use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; +use sc_rpc::testing::TokioTestExecutor; use sc_service::client::new_in_mem; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{ storage::well_known_keys::{self, CODE}, - testing::TaskExecutor, Blake2Hasher, Hasher, }; use sp_runtime::traits::Block as BlockT; @@ -60,7 +60,6 @@ type Block = substrate_test_runtime_client::runtime::Block; const MAX_PINNED_BLOCKS: usize = 32; const MAX_PINNED_SECS: u64 = 60; const MAX_OPERATIONS: usize = 16; -const MAX_PAGINATION_LIMIT: usize = 5; const MAX_LAGGING_DISTANCE: usize = 128; const MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION: usize = 4; @@ -80,12 +79,11 @@ pub async fn run_server() -> std::net::SocketAddr { let api = ChainHead::new( client, backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_follow_subscriptions_per_connection: 1, max_lagging_distance: MAX_LAGGING_DISTANCE, }, @@ -142,12 +140,11 @@ async fn setup_api() -> ( let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -250,13 +247,11 @@ async fn follow_subscription_produces_blocks() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -321,13 +316,11 @@ async fn follow_with_runtime() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -631,13 +624,11 @@ async fn call_runtime_without_flag() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1292,13 +1283,11 @@ async fn separate_operation_ids_for_subscriptions() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1380,13 +1369,11 @@ async fn follow_generates_initial_blocks() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1538,13 +1525,11 @@ async fn follow_exceeding_pinned_blocks() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: 2, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1617,13 +1602,11 @@ async fn follow_with_unpin() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: 2, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1725,13 +1708,11 @@ async fn unpin_duplicate_hashes() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::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, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1830,13 +1811,11 @@ async fn follow_with_multiple_unpin_hashes() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -1977,13 +1956,11 @@ async fn follow_prune_best_block() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2165,13 +2142,11 @@ async fn follow_forks_pruned_block() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2327,13 +2302,11 @@ async fn follow_report_multiple_pruned_block() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2566,7 +2539,7 @@ async fn pin_block_references() { genesis_block_builder, None, None, - Box::new(TaskExecutor::new()), + Box::new(TokioTestExecutor::default()), client_config, ) .unwrap(), @@ -2575,13 +2548,11 @@ async fn pin_block_references() { let api = ChainHead::new( client.clone(), backend.clone(), - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::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, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2712,13 +2683,11 @@ async fn follow_finalized_before_new_block() { let api = ChainHead::new( client_mock.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2829,13 +2798,11 @@ async fn ensure_operation_limits_works() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: 1, - operation_max_storage_items: MAX_PAGINATION_LIMIT, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -2887,7 +2854,7 @@ async fn ensure_operation_limits_works() { let operation_id = match response { MethodResponse::Started(started) => { // Check discarded items. - assert_eq!(started.discarded_items.unwrap(), 3); + assert!(started.discarded_items.is_none()); started.operation_id }, MethodResponse::LimitReached => panic!("Expected started response"), @@ -2922,7 +2889,7 @@ async fn ensure_operation_limits_works() { } #[tokio::test] -async fn check_continue_operation() { +async fn storage_is_backpressured() { let child_info = ChildInfo::new_default(CHILD_STORAGE_KEY); let builder = TestClientBuilder::new().add_extra_child_storage( &child_info, @@ -2936,13 +2903,11 @@ async fn check_continue_operation() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: 1, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -3021,18 +2986,6 @@ async fn check_continue_operation() { res.items[0].result == StorageResultType::Value(hex_string(b"a")) ); - // Pagination event. - assert_matches!( - get_next_event::>(&mut sub).await, - FollowEvent::OperationWaitingForContinue(res) if res.operation_id == operation_id - ); - - does_not_produce_event::>( - &mut sub, - std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), - ) - .await; - let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3041,17 +2994,6 @@ async fn check_continue_operation() { res.items[0].result == StorageResultType::Value(hex_string(b"ab")) ); - // Pagination event. - assert_matches!( - get_next_event::>(&mut sub).await, - FollowEvent::OperationWaitingForContinue(res) if res.operation_id == operation_id - ); - does_not_produce_event::>( - &mut sub, - std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), - ) - .await; - let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3060,18 +3002,6 @@ async fn check_continue_operation() { res.items[0].result == StorageResultType::Value(hex_string(b"abcmoD")) ); - // Pagination event. - assert_matches!( - get_next_event::>(&mut sub).await, - FollowEvent::OperationWaitingForContinue(res) if res.operation_id == operation_id - ); - - does_not_produce_event::>( - &mut sub, - std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), - ) - .await; - let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3080,17 +3010,6 @@ async fn check_continue_operation() { res.items[0].result == StorageResultType::Value(hex_string(b"abc")) ); - // Pagination event. - assert_matches!( - get_next_event::>(&mut sub).await, - FollowEvent::OperationWaitingForContinue(res) if res.operation_id == operation_id - ); - does_not_produce_event::>( - &mut sub, - std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), - ) - .await; - let _res: () = api.call("chainHead_v1_continue", [&sub_id, &operation_id]).await.unwrap(); assert_matches!( get_next_event::>(&mut sub).await, FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && @@ -3121,13 +3040,11 @@ async fn stop_storage_operation() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: 1, - max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -3203,15 +3120,22 @@ async fn stop_storage_operation() { res.items[0].result == StorageResultType::Value(hex_string(b"a")) ); - // Pagination event. assert_matches!( get_next_event::>(&mut sub).await, - FollowEvent::OperationWaitingForContinue(res) if res.operation_id == operation_id + FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id && + res.items.len() == 1 && + res.items[0].key == hex_string(b":mo") && + res.items[0].result == StorageResultType::Value(hex_string(b"ab")) ); // Stop the operation. let _res: () = api.call("chainHead_v1_stopOperation", [&sub_id, &operation_id]).await.unwrap(); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id + ); + does_not_produce_event::>( &mut sub, std::time::Duration::from_secs(DOES_NOT_PRODUCE_EVENTS_SECONDS), @@ -3289,30 +3213,23 @@ async fn storage_closest_merkle_value() { MethodResponse::LimitReached => panic!("Expected started response"), }; - let event = get_next_event::>(&mut sub).await; - let merkle_values: HashMap<_, _> = match event { - FollowEvent::OperationStorageItems(res) => { - assert_eq!(res.operation_id, operation_id); + let mut merkle_values = HashMap::new(); - res.items - .into_iter() - .map(|res| { + loop { + match get_next_event::>(&mut sub).await { + FollowEvent::OperationStorageItems(res) if res.operation_id == operation_id => + for res in res.items { let value = match res.result { StorageResultType::ClosestDescendantMerkleValue(value) => value, _ => panic!("Unexpected StorageResultType"), }; - (res.key, value) - }) - .collect() - }, - _ => panic!("Expected OperationStorageItems event"), - }; - - // Finished. - assert_matches!( - get_next_event::>(&mut sub).await, - FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id - ); + merkle_values.insert(res.key, value); + }, + FollowEvent::OperationStorageDone(done) if done.operation_id == operation_id => + break, + _ => panic!("Unexpected event"), + } + } // Response for AAAA, AAAB, A and AA. assert_eq!(merkle_values.len(), 4); @@ -3420,12 +3337,11 @@ async fn chain_head_stop_all_subscriptions() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_lagging_distance: 5, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, @@ -3634,12 +3550,11 @@ async fn chain_head_limit_reached() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: 1, }, @@ -3675,12 +3590,11 @@ async fn follow_unique_pruned_blocks() { let api = ChainHead::new( client.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, max_lagging_distance: MAX_LAGGING_DISTANCE, }, @@ -3845,12 +3759,11 @@ async fn follow_report_best_block_of_a_known_block() { let api = ChainHead::new( client_mock.clone(), backend, - Arc::new(TaskExecutor::default()), + Arc::new(TokioTestExecutor::default()), ChainHeadConfig { global_max_pinned_blocks: MAX_PINNED_BLOCKS, subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), subscription_max_ongoing_operations: MAX_OPERATIONS, - operation_max_storage_items: MAX_PAGINATION_LIMIT, max_lagging_distance: MAX_LAGGING_DISTANCE, max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, }, diff --git a/substrate/client/rpc-spec-v2/src/common/storage.rs b/substrate/client/rpc-spec-v2/src/common/storage.rs index bd249e033f8..2e24a8da8ca 100644 --- a/substrate/client/rpc-spec-v2/src/common/storage.rs +++ b/substrate/client/rpc-spec-v2/src/common/storage.rs @@ -22,6 +22,7 @@ use std::{marker::PhantomData, sync::Arc}; use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; use sp_runtime::traits::Block as BlockT; +use tokio::sync::mpsc; use super::events::{StorageResult, StorageResultType}; use crate::hex_string; @@ -33,6 +34,12 @@ pub struct Storage { _phandom: PhantomData<(BE, Block)>, } +impl Clone for Storage { + fn clone(&self) -> Self { + Self { client: self.client.clone(), _phandom: PhantomData } + } +} + impl Storage { /// Constructs a new [`Storage`]. pub fn new(client: Arc) -> Self { @@ -41,6 +48,7 @@ impl Storage { } /// Query to iterate over storage. +#[derive(Debug)] pub struct QueryIter { /// The key from which the iteration was started. pub query_key: StorageKey, @@ -51,6 +59,7 @@ pub struct QueryIter { } /// The query type of an iteration. +#[derive(Debug)] pub enum IterQueryType { /// Iterating over (key, value) pairs. Value, @@ -123,7 +132,7 @@ where key: &StorageKey, child_key: Option<&ChildInfo>, ) -> QueryResult { - let result = if let Some(child_key) = child_key { + let result = if let Some(ref child_key) = child_key { self.client.child_closest_merkle_value(hash, child_key, key) } else { self.client.closest_merkle_value(hash, key) @@ -146,6 +155,50 @@ where .unwrap_or_else(|error| QueryResult::Err(error.to_string())) } + /// Iterate over the storage keys and send the results to the provided sender. + /// + /// Because this relies on a bounded channel, it will pause the storage iteration + // if the channel is becomes full which in turn provides backpressure. + pub fn query_iter_pagination_with_producer( + &self, + query: QueryIter, + hash: Block::Hash, + child_key: Option<&ChildInfo>, + tx: &mpsc::Sender, + ) { + let QueryIter { ty, query_key, pagination_start_key } = query; + + let maybe_storage = if let Some(child_key) = child_key { + self.client.child_storage_keys( + hash, + child_key.to_owned(), + Some(&query_key), + pagination_start_key.as_ref(), + ) + } else { + self.client.storage_keys(hash, Some(&query_key), pagination_start_key.as_ref()) + }; + + let keys_iter = match maybe_storage { + Ok(keys_iter) => keys_iter, + Err(error) => { + _ = tx.blocking_send(Err(error.to_string())); + return; + }, + }; + + for key in keys_iter { + let result = match ty { + IterQueryType::Value => self.query_value(hash, &key, child_key), + IterQueryType::Hash => self.query_hash(hash, &key, child_key), + }; + + if tx.blocking_send(result).is_err() { + break; + } + } + } + /// Iterate over at most the provided number of keys. /// /// Returns the storage result with a potential next key to resume iteration. -- GitLab From 72309bd82edbf923955bd81ff4e8142c55db5bbc Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 3 Oct 2024 22:34:38 +0200 Subject: [PATCH 038/127] Re-establish pallet_revive weights baseline (#5845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update baseline for pallet_revive - update cmd pipeline name - Fix compilation after renaming some of benchmarks in pallet_revive. [Runtime Dev]. Changed the "instr" benchmark so that it should no longer return to little weight. It is still bogus but at least benchmarking should not work. (by @athei ) --------- Co-authored-by: GitHub Action Co-authored-by: Alexander Theißen Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: command-bot <> --- .github/workflows/cmd-tests.yml | 2 +- prdoc/pr_5845.prdoc | 15 + .../fixtures/contracts/instr_benchmark.rs | 12 +- .../frame/revive/src/benchmarking/mod.rs | 4 +- substrate/frame/revive/src/gas.rs | 4 +- substrate/frame/revive/src/lib.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 8 +- substrate/frame/revive/src/weights.rs | 1703 ++++++++--------- 8 files changed, 785 insertions(+), 965 deletions(-) create mode 100644 prdoc/pr_5845.prdoc diff --git a/.github/workflows/cmd-tests.yml b/.github/workflows/cmd-tests.yml index 37f1747d0b9..af73c6a5b2d 100644 --- a/.github/workflows/cmd-tests.yml +++ b/.github/workflows/cmd-tests.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true jobs: - test: + test-cmd-bot: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/prdoc/pr_5845.prdoc b/prdoc/pr_5845.prdoc new file mode 100644 index 00000000000..6b214d7599b --- /dev/null +++ b/prdoc/pr_5845.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: Fix compilation after renaming some of benchmarks in pallet_revive. + +doc: + - audience: Runtime Dev + description: | + Changed the "instr" benchmark so that it should no longer return to little weight. It is still bogus but at least benchmarking should not work. + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: major \ No newline at end of file diff --git a/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs index c5fb382c327..0492652b0d0 100644 --- a/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs +++ b/substrate/frame/revive/fixtures/contracts/instr_benchmark.rs @@ -22,6 +22,8 @@ extern crate common; use common::input; use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; +static mut MULT: [u32; 5_000] = [1u32; 5_000]; + #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} @@ -29,13 +31,13 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - input!(rounds: u32, start: u32, div: u32, mult: u32, add: u32, ); + input!(rounds: u32, ); - let mut acc = start; + let mut acc = 5; - for _ in 0..rounds { - acc = acc / div * mult + add; + for i in 0..rounds { + acc = acc * unsafe { MULT[i as usize] } } - api::return_value(ReturnFlags::empty(), start.to_le_bytes().as_ref()); + api::return_value(ReturnFlags::empty(), acc.to_le_bytes().as_ref()); } diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index cbc4cc62d48..acca876f376 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -1727,11 +1727,9 @@ mod benchmarks { // Benchmark the execution of instructions. #[benchmark(pov_mode = Ignored)] fn instr(r: Linear<0, INSTR_BENCHMARK_RUNS>) { - // (round, start, div, mult, add) - let input = (r, 1_000u32, 2u32, 3u32, 100u32).encode(); let mut setup = CallSetup::::new(WasmModule::instr()); let (mut ext, module) = setup.ext(); - let prepared = CallSetup::::prepare_call(&mut ext, module, input); + let prepared = CallSetup::::prepare_call(&mut ext, module, r.encode()); #[block] { prepared.call().unwrap(); diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 2034f39e9bc..9aad84e6920 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -76,9 +76,7 @@ impl EngineMeter { // We execute 6 different instructions therefore we have to divide the actual // computed gas costs by 6 to have a rough estimate as to how expensive each // single executed instruction is going to be. - let instr_cost = T::WeightInfo::instr_i64_load_store(1) - .saturating_sub(T::WeightInfo::instr_i64_load_store(0)) - .ref_time(); + let instr_cost = T::WeightInfo::instr(1).saturating_sub(T::WeightInfo::instr(0)).ref_time(); instr_cost / 6 } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 114d51c8969..91c5fa5563a 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -880,7 +880,7 @@ pub mod pallet { /// only be instantiated by permissioned entities. The same is true when uploading /// through [`Self::instantiate_with_code`]. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::upload_code_determinism_enforced(code.len() as u32))] + #[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))] pub fn upload_code( origin: OriginFor, code: Vec, diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 4b5a9a04eb7..56e3b685dcf 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -310,8 +310,8 @@ pub enum RuntimeCosts { CallerIsRoot, /// Weight of calling `seal_address`. Address, - /// Weight of calling `seal_gas_left`. - GasLeft, + /// Weight of calling `seal_weight_left`. + WeightLeft, /// Weight of calling `seal_balance`. Balance, /// Weight of calling `seal_balance_of`. @@ -457,7 +457,7 @@ impl Token for RuntimeCosts { CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), CallerIsRoot => T::WeightInfo::seal_caller_is_root(), Address => T::WeightInfo::seal_address(), - GasLeft => T::WeightInfo::seal_gas_left(), + WeightLeft => T::WeightInfo::seal_weight_left(), Balance => T::WeightInfo::seal_balance(), BalanceOf => T::WeightInfo::seal_balance_of(), ValueTransferred => T::WeightInfo::seal_value_transferred(), @@ -1501,7 +1501,7 @@ pub mod env { out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::GasLeft)?; + self.charge_gas(RuntimeCosts::WeightLeft)?; let gas_left = &self.ext.gas_meter().gas_left().encode(); Ok(self.write_sandbox_output( memory, diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index b66c28bdf7d..6586cd1fc9c 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-07-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-10-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yaoqqom-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-jniz7bxx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -36,7 +36,7 @@ // --pallet=pallet_revive // --chain=dev // --header=./substrate/HEADER-APACHE2 -// --output=./substrate/frame/contracts/src/weights.rs +// --output=./substrate/frame/revive/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -52,11 +52,10 @@ pub trait WeightInfo { fn on_process_deletion_queue_batch() -> Weight; fn on_initialize_per_trie_key(k: u32, ) -> Weight; fn call_with_code_per_byte(c: u32, ) -> Weight; - fn instantiate_with_code(c: u32, i: u32) -> Weight; - fn instantiate(i: u32) -> Weight; + fn instantiate_with_code(c: u32, i: u32, ) -> Weight; + fn instantiate(i: u32, ) -> Weight; fn call() -> Weight; - fn upload_code_determinism_enforced(c: u32, ) -> Weight; - fn upload_code_determinism_relaxed(c: u32, ) -> Weight; + fn upload_code(c: u32, ) -> Weight; fn remove_code() -> Weight; fn set_code() -> Weight; fn noop_host_fn(r: u32, ) -> Weight; @@ -67,7 +66,7 @@ pub trait WeightInfo { fn seal_caller_is_origin() -> Weight; fn seal_caller_is_root() -> Weight; fn seal_address() -> Weight; - fn seal_gas_left() -> Weight; + fn seal_weight_left() -> Weight; fn seal_balance() -> Weight; fn seal_balance_of() -> Weight; fn seal_value_transferred() -> Weight; @@ -78,7 +77,6 @@ pub trait WeightInfo { fn seal_input(n: u32, ) -> Weight; fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; - fn seal_random() -> Weight; fn seal_deposit_event(t: u32, n: u32, ) -> Weight; fn seal_debug_message(i: u32, ) -> Weight; fn get_storage_empty() -> Weight; @@ -103,7 +101,7 @@ pub trait WeightInfo { fn seal_transfer() -> Weight; fn seal_call(t: u32, i: u32, ) -> Weight; fn seal_delegate_call() -> Weight; - fn seal_instantiate(i: u32) -> Weight; + fn seal_instantiate(i: u32, ) -> Weight; fn seal_hash_sha2_256(n: u32, ) -> Weight; fn seal_hash_keccak_256(n: u32, ) -> Weight; fn seal_hash_blake2_256(n: u32, ) -> Weight; @@ -114,23 +112,20 @@ pub trait WeightInfo { fn seal_set_code_hash() -> Weight; fn lock_delegate_dependency() -> Weight; fn unlock_delegate_dependency() -> Weight; - fn seal_reentrance_count() -> Weight; - fn seal_account_reentrance_count() -> Weight; - fn seal_instantiation_nonce() -> Weight; - fn instr_i64_load_store(r: u32, ) -> Weight; + fn instr(r: u32, ) -> Weight; } /// Weights for `pallet_revive` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Revive::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) fn on_process_deletion_queue_batch() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `1627` - // Minimum execution time: 1_915_000 picoseconds. - Weight::from_parts(1_986_000, 1627) + // Measured: `109` + // Estimated: `1594` + // Minimum execution time: 2_783_000 picoseconds. + Weight::from_parts(2_883_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -138,197 +133,150 @@ impl WeightInfo for SubstrateWeight { /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `452 + k * (69 ±0)` - // Estimated: `442 + k * (70 ±0)` - // Minimum execution time: 11_103_000 picoseconds. - Weight::from_parts(11_326_000, 442) - // Standard Error: 2_291 - .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + // Measured: `392 + k * (69 ±0)` + // Estimated: `382 + k * (70 ±0)` + // Minimum execution time: 13_394_000 picoseconds. + Weight::from_parts(305_292, 382) + // Standard Error: 1_217 + .saturating_add(Weight::from_parts(1_233_492, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn call_with_code_per_byte(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `800 + c * (1 ±0)` - // Estimated: `4266 + c * (1 ±0)` - // Minimum execution time: 247_545_000 picoseconds. - Weight::from_parts(268_016_699, 4266) - // Standard Error: 4 - .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(6_u64)) + /// The range of component `c` is `[0, 262144]`. + fn call_with_code_per_byte(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `4299` + // Minimum execution time: 78_977_000 picoseconds. + Weight::from_parts(81_687_641, 4299) + .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:1) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - /// The range of component `i` is `[0, 1048576]`. - /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `323` - // Estimated: `6262` - // Minimum execution time: 4_396_772_000 picoseconds. - Weight::from_parts(235_107_907, 6262) - // Standard Error: 185 - .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) - // Standard Error: 22 - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:1) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// The range of component `c` is `[0, 262144]`. + /// The range of component `i` is `[0, 262144]`. + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `303` + // Estimated: `6232` + // Minimum execution time: 179_690_000 picoseconds. + Weight::from_parts(149_042_544, 6232) + // Standard Error: 11 + .saturating_add(Weight::from_parts(33, 0).saturating_mul(c.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_644, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// The range of component `i` is `[0, 1048576]`. - /// The range of component `s` is `[0, 1048576]`. - fn instantiate(i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `560` - // Estimated: `4017` - // Minimum execution time: 2_240_868_000 picoseconds. - Weight::from_parts(2_273_668_000, 4017) - // Standard Error: 32 - .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) - // Standard Error: 32 - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// The range of component `i` is `[0, 262144]`. + fn instantiate(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `657` + // Estimated: `4111` + // Minimum execution time: 156_389_000 picoseconds. + Weight::from_parts(130_603_882, 4111) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_594, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `826` - // Estimated: `4291` - // Minimum execution time: 165_067_000 picoseconds. - Weight::from_parts(168_582_000, 4291) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Measured: `834` + // Estimated: `4299` + // Minimum execution time: 80_108_000 picoseconds. + Weight::from_parts(81_555_000, 4299) + .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn upload_code_determinism_enforced(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 229_454_000 picoseconds. - Weight::from_parts(251_495_551, 3607) - // Standard Error: 71 - .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn upload_code_determinism_relaxed(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 240_390_000 picoseconds. - Weight::from_parts(273_854_266, 3607) - // Standard Error: 243 - .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// The range of component `c` is `[0, 262144]`. + fn upload_code(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3574` + // Minimum execution time: 49_297_000 picoseconds. + Weight::from_parts(50_873_587, 3574) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `315` - // Estimated: `3780` - // Minimum execution time: 39_374_000 picoseconds. - Weight::from_parts(40_247_000, 3780) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `285` + // Estimated: `3750` + // Minimum execution time: 42_556_000 picoseconds. + Weight::from_parts(43_708_000, 3750) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:2 w:2) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `552` - // Estimated: `6492` - // Minimum execution time: 24_473_000 picoseconds. - Weight::from_parts(25_890_000, 6492) - .saturating_add(T::DbWeight::get().reads(4_u64)) + // Measured: `491` + // Estimated: `6431` + // Minimum execution time: 24_623_000 picoseconds. + Weight::from_parts(26_390_000, 6431) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -336,79 +284,79 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_528_000 picoseconds. - Weight::from_parts(9_301_010, 0) - // Standard Error: 98 - .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + // Minimum execution time: 7_590_000 picoseconds. + Weight::from_parts(8_005_868, 0) + // Standard Error: 396 + .saturating_add(Weight::from_parts(203_612, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(678_000, 0) + // Minimum execution time: 285_000 picoseconds. + Weight::from_parts(305_000, 0) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: - // Measured: `354` - // Estimated: `3819` - // Minimum execution time: 6_107_000 picoseconds. - Weight::from_parts(6_235_000, 3819) + // Measured: `272` + // Estimated: `3737` + // Minimum execution time: 6_711_000 picoseconds. + Weight::from_parts(7_103_000, 3737) .saturating_add(T::DbWeight::get().reads(1_u64)) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3912` - // Minimum execution time: 7_316_000 picoseconds. - Weight::from_parts(7_653_000, 3912) + // Measured: `365` + // Estimated: `3830` + // Minimum execution time: 7_572_000 picoseconds. + Weight::from_parts(7_888_000, 3830) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 721_000 picoseconds. - Weight::from_parts(764_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(264_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 369_000 picoseconds. - Weight::from_parts(417_000, 0) + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(356_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 318_000 picoseconds. - Weight::from_parts(349_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(286_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(628_000, 0) + // Minimum execution time: 255_000 picoseconds. + Weight::from_parts(296_000, 0) } - fn seal_gas_left() -> Weight { + fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(730_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(701_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 4_361_000 picoseconds. - Weight::from_parts(4_577_000, 0) + // Minimum execution time: 5_207_000 picoseconds. + Weight::from_parts(5_403_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -416,37 +364,37 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_751_000 picoseconds. - Weight::from_parts(3_874_000, 3517) + // Minimum execution time: 3_829_000 picoseconds. + Weight::from_parts(3_977_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 560_000 picoseconds. - Weight::from_parts(603_000, 0) + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(266_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 561_000 picoseconds. - Weight::from_parts(610_000, 0) + // Minimum execution time: 256_000 picoseconds. + Weight::from_parts(288_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(583_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(278_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 550_000 picoseconds. - Weight::from_parts(602_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(272_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -454,117 +402,102 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 4_065_000 picoseconds. - Weight::from_parts(4_291_000, 1552) + // Minimum execution time: 5_467_000 picoseconds. + Weight::from_parts(5_607_000, 1552) .saturating_add(T::DbWeight::get().reads(1_u64)) } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 487_000 picoseconds. - Weight::from_parts(517_000, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + // Minimum execution time: 438_000 picoseconds. + Weight::from_parts(532_907, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 318_000 picoseconds. - Weight::from_parts(372_000, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) - } - /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::DeletionQueue` (r:0 w:1) - /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(629_625, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) + } + /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:33 w:33) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::DeletionQueue` (r:0 w:1) + /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `319 + n * (78 ±0)` - // Estimated: `3784 + n * (2553 ±0)` - // Minimum execution time: 13_251_000 picoseconds. - Weight::from_parts(15_257_892, 3784) - // Standard Error: 7_089 - .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + // Measured: `272 + n * (88 ±0)` + // Estimated: `3738 + n * (2563 ±0)` + // Minimum execution time: 14_997_000 picoseconds. + Weight::from_parts(17_752_993, 3738) + // Standard Error: 9_865 + .saturating_add(Weight::from_parts(4_159_693, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) - } - /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) - /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) - fn seal_random() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 3_434_000 picoseconds. - Weight::from_parts(3_605_000, 1561) - .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } - /// Storage: `System::EventTopics` (r:4 w:4) - /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `990 + t * (2475 ±0)` - // Minimum execution time: 3_668_000 picoseconds. - Weight::from_parts(3_999_591, 990) - // Standard Error: 5_767 - .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) - // Standard Error: 1 - .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) - .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + // Estimated: `0` + // Minimum execution time: 4_277_000 picoseconds. + Weight::from_parts(4_023_910, 0) + // Standard Error: 2_210 + .saturating_add(Weight::from_parts(202_823, 0).saturating_mul(t.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_141, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 1048576]`. + /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 443_000 picoseconds. - Weight::from_parts(472_000, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(834_030, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `16618` - // Estimated: `16618` - // Minimum execution time: 13_752_000 picoseconds. - Weight::from_parts(14_356_000, 16618) + // Measured: `744` + // Estimated: `744` + // Minimum execution time: 7_705_000 picoseconds. + Weight::from_parts(7_923_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `26628` - // Estimated: `26628` - // Minimum execution time: 43_444_000 picoseconds. - Weight::from_parts(45_087_000, 26628) + // Measured: `10754` + // Estimated: `10754` + // Minimum execution time: 44_510_000 picoseconds. + Weight::from_parts(45_840_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `16618` - // Estimated: `16618` - // Minimum execution time: 15_616_000 picoseconds. - Weight::from_parts(16_010_000, 16618) + // Measured: `744` + // Estimated: `744` + // Minimum execution time: 8_842_000 picoseconds. + Weight::from_parts(9_363_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -572,85 +505,85 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `26628` - // Estimated: `26628` - // Minimum execution time: 47_020_000 picoseconds. - Weight::from_parts(50_152_000, 26628) + // Measured: `10754` + // Estimated: `10754` + // Minimum execution time: 46_172_000 picoseconds. + Weight::from_parts(47_586_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. - /// The range of component `o` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. + /// The range of component `o` is `[0, 512]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `250 + o * (1 ±0)` - // Estimated: `249 + o * (1 ±0)` - // Minimum execution time: 8_824_000 picoseconds. - Weight::from_parts(8_915_233, 249) - // Standard Error: 1 - .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) - // Standard Error: 1 - .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + // Measured: `248 + o * (1 ±0)` + // Estimated: `247 + o * (1 ±0)` + // Minimum execution time: 9_158_000 picoseconds. + Weight::from_parts(9_708_320, 247) + // Standard Error: 36 + .saturating_add(Weight::from_parts(499, 0).saturating_mul(n.into())) + // Standard Error: 36 + .saturating_add(Weight::from_parts(672, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 7_133_000 picoseconds. - Weight::from_parts(7_912_778, 248) - // Standard Error: 1 - .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 8_885_000 picoseconds. + Weight::from_parts(9_597_656, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(649, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 6_746_000 picoseconds. - Weight::from_parts(7_647_236, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 8_473_000 picoseconds. + Weight::from_parts(9_246_006, 247) + // Standard Error: 47 + .saturating_add(Weight::from_parts(1_468, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 6_247_000 picoseconds. - Weight::from_parts(6_952_661, 248) - // Standard Error: 1 - .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 7_996_000 picoseconds. + Weight::from_parts(8_784_165, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(591, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 7_428_000 picoseconds. - Weight::from_parts(8_384_015, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 9_246_000 picoseconds. + Weight::from_parts(10_239_803, 247) + // Standard Error: 57 + .saturating_add(Weight::from_parts(1_305, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -659,302 +592,270 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_478_000 picoseconds. - Weight::from_parts(1_533_000, 0) + // Minimum execution time: 1_422_000 picoseconds. + Weight::from_parts(1_531_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_485_000 picoseconds. - Weight::from_parts(2_728_000, 0) + // Minimum execution time: 1_858_000 picoseconds. + Weight::from_parts(1_944_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_195_000 picoseconds. - Weight::from_parts(3_811_000, 0) + // Minimum execution time: 1_443_000 picoseconds. + Weight::from_parts(1_506_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_902_000 picoseconds. - Weight::from_parts(4_118_000, 0) + // Minimum execution time: 1_592_000 picoseconds. + Weight::from_parts(1_651_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_571_000 picoseconds. - Weight::from_parts(1_662_000, 0) + // Minimum execution time: 960_000 picoseconds. + Weight::from_parts(1_060_000, 0) } - /// The range of component `n` is `[0, 16384]`. - /// The range of component `o` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. + /// The range of component `o` is `[0, 512]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(2_465_568, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + // Minimum execution time: 2_151_000 picoseconds. + Weight::from_parts(2_294_801, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(375, 0).saturating_mul(n.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(415, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_012_000 picoseconds. - Weight::from_parts(2_288_004, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + // Minimum execution time: 1_880_000 picoseconds. + Weight::from_parts(2_259_773, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(356, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_906_000 picoseconds. - Weight::from_parts(2_121_040, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + // Minimum execution time: 1_755_000 picoseconds. + Weight::from_parts(1_968_235, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_736_000 picoseconds. - Weight::from_parts(1_954_728, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + // Minimum execution time: 1_618_000 picoseconds. + Weight::from_parts(1_811_972, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(174, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + /// The range of component `n` is `[0, 512]`. + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_872_000 picoseconds. - Weight::from_parts(8_125_644, 0) + // Minimum execution time: 2_482_000 picoseconds. + Weight::from_parts(2_682_484, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 8_489_000 picoseconds. - Weight::from_parts(8_791_000, 0) + // Minimum execution time: 9_899_000 picoseconds. + Weight::from_parts(10_342_000, 0) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `t` is `[0, 1]`. - /// The range of component `i` is `[0, 1048576]`. + /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `620 + t * (280 ±0)` - // Estimated: `4085 + t * (2182 ±0)` - // Minimum execution time: 122_759_000 picoseconds. - Weight::from_parts(120_016_020, 4085) - // Standard Error: 173_118 - .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Measured: `626 + t * (140 ±0)` + // Estimated: `4091 + t * (140 ±0)` + // Minimum execution time: 33_645_000 picoseconds. + Weight::from_parts(34_407_662, 4091) + // Standard Error: 36_930 + .saturating_add(Weight::from_parts(2_062_425, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) - .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 140).saturating_mul(t.into())) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `430` - // Estimated: `3895` - // Minimum execution time: 111_566_000 picoseconds. - Weight::from_parts(115_083_000, 3895) + // Measured: `457` + // Estimated: `3922` + // Minimum execution time: 26_924_000 picoseconds. + Weight::from_parts(27_753_000, 3922) .saturating_add(T::DbWeight::get().reads(2_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:0) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// The range of component `i` is `[0, 983040]`. - /// The range of component `s` is `[0, 983040]`. - fn seal_instantiate(i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `676` - // Estimated: `4132` - // Minimum execution time: 1_871_402_000 picoseconds. - Weight::from_parts(1_890_038_000, 4132) - // Standard Error: 24 - .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) - // Standard Error: 24 - .saturating_add(T::DbWeight::get().reads(5_u64)) + /// The range of component `i` is `[0, 262144]`. + fn seal_instantiate(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `703` + // Estimated: `4160` + // Minimum execution time: 117_979_000 picoseconds. + Weight::from_parts(105_415_117, 4160) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_293, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_sha2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 966_000 picoseconds. - Weight::from_parts(9_599_151, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + // Minimum execution time: 620_000 picoseconds. + Weight::from_parts(3_414_286, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_463, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_416_000 picoseconds. - Weight::from_parts(10_964_255, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + // Minimum execution time: 1_043_000 picoseconds. + Weight::from_parts(3_402_639, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(3_667, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 821_000 picoseconds. - Weight::from_parts(6_579_283, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + // Minimum execution time: 642_000 picoseconds. + Weight::from_parts(3_359_294, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_590, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 773_000 picoseconds. - Weight::from_parts(10_990_209, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + // Minimum execution time: 606_000 picoseconds. + Weight::from_parts(3_789_868, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_575, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 125697]`. + /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 43_195_000 picoseconds. - Weight::from_parts(41_864_855, 0) - // Standard Error: 9 - .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + // Minimum execution time: 46_195_000 picoseconds. + Weight::from_parts(31_420_941, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(5_165, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_747_000 picoseconds. - Weight::from_parts(49_219_000, 0) + // Minimum execution time: 46_933_000 picoseconds. + Weight::from_parts(48_054_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_854_000 picoseconds. - Weight::from_parts(12_962_000, 0) + // Minimum execution time: 12_531_000 picoseconds. + Weight::from_parts(12_690_000, 0) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_set_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `430` - // Estimated: `3895` - // Minimum execution time: 17_868_000 picoseconds. - Weight::from_parts(18_486_000, 3895) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `266` + // Estimated: `3731` + // Minimum execution time: 14_694_000 picoseconds. + Weight::from_parts(15_032_000, 3731) + .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `3820` - // Minimum execution time: 8_393_000 picoseconds. - Weight::from_parts(8_640_000, 3820) + // Measured: `304` + // Estimated: `3769` + // Minimum execution time: 10_205_000 picoseconds. + Weight::from_parts(10_707_000, 3769) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `3558` - // Minimum execution time: 7_489_000 picoseconds. - Weight::from_parts(7_815_000, 3558) + // Measured: `304` + // Estimated: `3561` + // Minimum execution time: 9_025_000 picoseconds. + Weight::from_parts(9_517_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - fn seal_reentrance_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 299_000 picoseconds. - Weight::from_parts(339_000, 0) - } - fn seal_account_reentrance_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 324_000 picoseconds. - Weight::from_parts(380_000, 0) - } - /// Storage: `Contracts::Nonce` (r:1 w:0) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - fn seal_instantiation_nonce() -> Weight { - // Proof Size summary in bytes: - // Measured: `219` - // Estimated: `1704` - // Minimum execution time: 2_768_000 picoseconds. - Weight::from_parts(3_025_000, 1704) - .saturating_add(T::DbWeight::get().reads(1_u64)) - } /// The range of component `r` is `[0, 5000]`. - fn instr_i64_load_store(r: u32, ) -> Weight { + fn instr(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 766_000 picoseconds. - Weight::from_parts(722_169, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + // Minimum execution time: 9_451_000 picoseconds. + Weight::from_parts(10_620_260, 0) + // Standard Error: 77 + .saturating_add(Weight::from_parts(84_885, 0).saturating_mul(r.into())) } } // For backwards compatibility and tests. impl WeightInfo for () { - /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:0) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Revive::DeletionQueueCounter` (r:1 w:0) + /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) fn on_process_deletion_queue_batch() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `1627` - // Minimum execution time: 1_915_000 picoseconds. - Weight::from_parts(1_986_000, 1627) + // Measured: `109` + // Estimated: `1594` + // Minimum execution time: 2_783_000 picoseconds. + Weight::from_parts(2_883_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -962,197 +863,150 @@ impl WeightInfo for () { /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `452 + k * (69 ±0)` - // Estimated: `442 + k * (70 ±0)` - // Minimum execution time: 11_103_000 picoseconds. - Weight::from_parts(11_326_000, 442) - // Standard Error: 2_291 - .saturating_add(Weight::from_parts(1_196_329, 0).saturating_mul(k.into())) + // Measured: `392 + k * (69 ±0)` + // Estimated: `382 + k * (70 ±0)` + // Minimum execution time: 13_394_000 picoseconds. + Weight::from_parts(305_292, 382) + // Standard Error: 1_217 + .saturating_add(Weight::from_parts(1_233_492, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn call_with_code_per_byte(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `800 + c * (1 ±0)` - // Estimated: `4266 + c * (1 ±0)` - // Minimum execution time: 247_545_000 picoseconds. - Weight::from_parts(268_016_699, 4266) - // Standard Error: 4 - .saturating_add(Weight::from_parts(700, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + /// The range of component `c` is `[0, 262144]`. + fn call_with_code_per_byte(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `4299` + // Minimum execution time: 78_977_000 picoseconds. + Weight::from_parts(81_687_641, 4299) + .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:2 w:2) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:1) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - /// The range of component `i` is `[0, 1048576]`. - /// The range of component `s` is `[0, 1048576]`. - fn instantiate_with_code(c: u32, i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `323` - // Estimated: `6262` - // Minimum execution time: 4_396_772_000 picoseconds. - Weight::from_parts(235_107_907, 6262) - // Standard Error: 185 - .saturating_add(Weight::from_parts(53_843, 0).saturating_mul(c.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(2_143, 0).saturating_mul(i.into())) - // Standard Error: 22 - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:1) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// The range of component `c` is `[0, 262144]`. + /// The range of component `i` is `[0, 262144]`. + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `303` + // Estimated: `6232` + // Minimum execution time: 179_690_000 picoseconds. + Weight::from_parts(149_042_544, 6232) + // Standard Error: 11 + .saturating_add(Weight::from_parts(33, 0).saturating_mul(c.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_644, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// The range of component `i` is `[0, 1048576]`. - /// The range of component `s` is `[0, 1048576]`. - fn instantiate(i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `560` - // Estimated: `4017` - // Minimum execution time: 2_240_868_000 picoseconds. - Weight::from_parts(2_273_668_000, 4017) - // Standard Error: 32 - .saturating_add(Weight::from_parts(934, 0).saturating_mul(i.into())) - // Standard Error: 32 - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// The range of component `i` is `[0, 262144]`. + fn instantiate(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `657` + // Estimated: `4111` + // Minimum execution time: 156_389_000 picoseconds. + Weight::from_parts(130_603_882, 4111) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_594, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `826` - // Estimated: `4291` - // Minimum execution time: 165_067_000 picoseconds. - Weight::from_parts(168_582_000, 4291) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Measured: `834` + // Estimated: `4299` + // Minimum execution time: 80_108_000 picoseconds. + Weight::from_parts(81_555_000, 4299) + .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn upload_code_determinism_enforced(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 229_454_000 picoseconds. - Weight::from_parts(251_495_551, 3607) - // Standard Error: 71 - .saturating_add(Weight::from_parts(51_428, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// The range of component `c` is `[0, 125952]`. - fn upload_code_determinism_relaxed(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `3607` - // Minimum execution time: 240_390_000 picoseconds. - Weight::from_parts(273_854_266, 3607) - // Standard Error: 243 - .saturating_add(Weight::from_parts(51_836, 0).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// The range of component `c` is `[0, 262144]`. + fn upload_code(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3574` + // Minimum execution time: 49_297_000 picoseconds. + Weight::from_parts(50_873_587, 3574) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(193), added: 2668, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:0 w:1) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:0 w:1) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn remove_code() -> Weight { // Proof Size summary in bytes: - // Measured: `315` - // Estimated: `3780` - // Minimum execution time: 39_374_000 picoseconds. - Weight::from_parts(40_247_000, 3780) - .saturating_add(RocksDbWeight::get().reads(3_u64)) + // Measured: `285` + // Estimated: `3750` + // Minimum execution time: 42_556_000 picoseconds. + Weight::from_parts(43_708_000, 3750) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// Storage: `Contracts::MigrationInProgress` (r:1 w:0) - /// Proof: `Contracts::MigrationInProgress` (`max_values`: Some(1), `max_size`: Some(1026), added: 1521, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:2 w:2) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:2 w:2) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `552` - // Estimated: `6492` - // Minimum execution time: 24_473_000 picoseconds. - Weight::from_parts(25_890_000, 6492) - .saturating_add(RocksDbWeight::get().reads(4_u64)) + // Measured: `491` + // Estimated: `6431` + // Minimum execution time: 24_623_000 picoseconds. + Weight::from_parts(26_390_000, 6431) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1160,79 +1014,79 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_528_000 picoseconds. - Weight::from_parts(9_301_010, 0) - // Standard Error: 98 - .saturating_add(Weight::from_parts(53_173, 0).saturating_mul(r.into())) + // Minimum execution time: 7_590_000 picoseconds. + Weight::from_parts(8_005_868, 0) + // Standard Error: 396 + .saturating_add(Weight::from_parts(203_612, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(678_000, 0) + // Minimum execution time: 285_000 picoseconds. + Weight::from_parts(305_000, 0) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: - // Measured: `354` - // Estimated: `3819` - // Minimum execution time: 6_107_000 picoseconds. - Weight::from_parts(6_235_000, 3819) + // Measured: `272` + // Estimated: `3737` + // Minimum execution time: 6_711_000 picoseconds. + Weight::from_parts(7_103_000, 3737) .saturating_add(RocksDbWeight::get().reads(1_u64)) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:0) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `447` - // Estimated: `3912` - // Minimum execution time: 7_316_000 picoseconds. - Weight::from_parts(7_653_000, 3912) + // Measured: `365` + // Estimated: `3830` + // Minimum execution time: 7_572_000 picoseconds. + Weight::from_parts(7_888_000, 3830) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 721_000 picoseconds. - Weight::from_parts(764_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(264_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 369_000 picoseconds. - Weight::from_parts(417_000, 0) + // Minimum execution time: 324_000 picoseconds. + Weight::from_parts(356_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 318_000 picoseconds. - Weight::from_parts(349_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(286_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(628_000, 0) + // Minimum execution time: 255_000 picoseconds. + Weight::from_parts(296_000, 0) } - fn seal_gas_left() -> Weight { + fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(730_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(701_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 4_361_000 picoseconds. - Weight::from_parts(4_577_000, 0) + // Minimum execution time: 5_207_000 picoseconds. + Weight::from_parts(5_403_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -1240,37 +1094,37 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_751_000 picoseconds. - Weight::from_parts(3_874_000, 3517) + // Minimum execution time: 3_829_000 picoseconds. + Weight::from_parts(3_977_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 560_000 picoseconds. - Weight::from_parts(603_000, 0) + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(266_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 561_000 picoseconds. - Weight::from_parts(610_000, 0) + // Minimum execution time: 256_000 picoseconds. + Weight::from_parts(288_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(583_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(278_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 550_000 picoseconds. - Weight::from_parts(602_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(272_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -1278,117 +1132,102 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 4_065_000 picoseconds. - Weight::from_parts(4_291_000, 1552) + // Minimum execution time: 5_467_000 picoseconds. + Weight::from_parts(5_607_000, 1552) .saturating_add(RocksDbWeight::get().reads(1_u64)) } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 487_000 picoseconds. - Weight::from_parts(517_000, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(301, 0).saturating_mul(n.into())) + // Minimum execution time: 438_000 picoseconds. + Weight::from_parts(532_907, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048572]`. + /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 318_000 picoseconds. - Weight::from_parts(372_000, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(411, 0).saturating_mul(n.into())) - } - /// Storage: `Contracts::DeletionQueueCounter` (r:1 w:1) - /// Proof: `Contracts::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:33 w:33) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::DeletionQueue` (r:0 w:1) - /// Proof: `Contracts::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(629_625, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) + } + /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) + /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:33 w:33) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::DeletionQueue` (r:0 w:1) + /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `319 + n * (78 ±0)` - // Estimated: `3784 + n * (2553 ±0)` - // Minimum execution time: 13_251_000 picoseconds. - Weight::from_parts(15_257_892, 3784) - // Standard Error: 7_089 - .saturating_add(Weight::from_parts(3_443_907, 0).saturating_mul(n.into())) + // Measured: `272 + n * (88 ±0)` + // Estimated: `3738 + n * (2563 ±0)` + // Minimum execution time: 14_997_000 picoseconds. + Weight::from_parts(17_752_993, 3738) + // Standard Error: 9_865 + .saturating_add(Weight::from_parts(4_159_693, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 2553).saturating_mul(n.into())) - } - /// Storage: `RandomnessCollectiveFlip::RandomMaterial` (r:1 w:0) - /// Proof: `RandomnessCollectiveFlip::RandomMaterial` (`max_values`: Some(1), `max_size`: Some(2594), added: 3089, mode: `Measured`) - fn seal_random() -> Weight { - // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `1561` - // Minimum execution time: 3_434_000 picoseconds. - Weight::from_parts(3_605_000, 1561) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } - /// Storage: `System::EventTopics` (r:4 w:4) - /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `990 + t * (2475 ±0)` - // Minimum execution time: 3_668_000 picoseconds. - Weight::from_parts(3_999_591, 990) - // Standard Error: 5_767 - .saturating_add(Weight::from_parts(2_011_090, 0).saturating_mul(t.into())) - // Standard Error: 1 - .saturating_add(Weight::from_parts(12, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) - .saturating_add(Weight::from_parts(0, 2475).saturating_mul(t.into())) + // Estimated: `0` + // Minimum execution time: 4_277_000 picoseconds. + Weight::from_parts(4_023_910, 0) + // Standard Error: 2_210 + .saturating_add(Weight::from_parts(202_823, 0).saturating_mul(t.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_141, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 1048576]`. + /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 443_000 picoseconds. - Weight::from_parts(472_000, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(1_207, 0).saturating_mul(i.into())) + // Minimum execution time: 309_000 picoseconds. + Weight::from_parts(834_030, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `16618` - // Estimated: `16618` - // Minimum execution time: 13_752_000 picoseconds. - Weight::from_parts(14_356_000, 16618) + // Measured: `744` + // Estimated: `744` + // Minimum execution time: 7_705_000 picoseconds. + Weight::from_parts(7_923_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `26628` - // Estimated: `26628` - // Minimum execution time: 43_444_000 picoseconds. - Weight::from_parts(45_087_000, 26628) + // Measured: `10754` + // Estimated: `10754` + // Minimum execution time: 44_510_000 picoseconds. + Weight::from_parts(45_840_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `16618` - // Estimated: `16618` - // Minimum execution time: 15_616_000 picoseconds. - Weight::from_parts(16_010_000, 16618) + // Measured: `744` + // Estimated: `744` + // Minimum execution time: 8_842_000 picoseconds. + Weight::from_parts(9_363_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1396,85 +1235,85 @@ impl WeightInfo for () { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `26628` - // Estimated: `26628` - // Minimum execution time: 47_020_000 picoseconds. - Weight::from_parts(50_152_000, 26628) + // Measured: `10754` + // Estimated: `10754` + // Minimum execution time: 46_172_000 picoseconds. + Weight::from_parts(47_586_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. - /// The range of component `o` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. + /// The range of component `o` is `[0, 512]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `250 + o * (1 ±0)` - // Estimated: `249 + o * (1 ±0)` - // Minimum execution time: 8_824_000 picoseconds. - Weight::from_parts(8_915_233, 249) - // Standard Error: 1 - .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) - // Standard Error: 1 - .saturating_add(Weight::from_parts(39, 0).saturating_mul(o.into())) + // Measured: `248 + o * (1 ±0)` + // Estimated: `247 + o * (1 ±0)` + // Minimum execution time: 9_158_000 picoseconds. + Weight::from_parts(9_708_320, 247) + // Standard Error: 36 + .saturating_add(Weight::from_parts(499, 0).saturating_mul(n.into())) + // Standard Error: 36 + .saturating_add(Weight::from_parts(672, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 7_133_000 picoseconds. - Weight::from_parts(7_912_778, 248) - // Standard Error: 1 - .saturating_add(Weight::from_parts(88, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 8_885_000 picoseconds. + Weight::from_parts(9_597_656, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(649, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 6_746_000 picoseconds. - Weight::from_parts(7_647_236, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(603, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 8_473_000 picoseconds. + Weight::from_parts(9_246_006, 247) + // Standard Error: 47 + .saturating_add(Weight::from_parts(1_468, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 6_247_000 picoseconds. - Weight::from_parts(6_952_661, 248) - // Standard Error: 1 - .saturating_add(Weight::from_parts(77, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 7_996_000 picoseconds. + Weight::from_parts(8_784_165, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(591, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` - // Estimated: `248 + n * (1 ±0)` - // Minimum execution time: 7_428_000 picoseconds. - Weight::from_parts(8_384_015, 248) - // Standard Error: 2 - .saturating_add(Weight::from_parts(625, 0).saturating_mul(n.into())) + // Estimated: `247 + n * (1 ±0)` + // Minimum execution time: 9_246_000 picoseconds. + Weight::from_parts(10_239_803, 247) + // Standard Error: 57 + .saturating_add(Weight::from_parts(1_305, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1483,288 +1322,256 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_478_000 picoseconds. - Weight::from_parts(1_533_000, 0) + // Minimum execution time: 1_422_000 picoseconds. + Weight::from_parts(1_531_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_485_000 picoseconds. - Weight::from_parts(2_728_000, 0) + // Minimum execution time: 1_858_000 picoseconds. + Weight::from_parts(1_944_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_195_000 picoseconds. - Weight::from_parts(3_811_000, 0) + // Minimum execution time: 1_443_000 picoseconds. + Weight::from_parts(1_506_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_902_000 picoseconds. - Weight::from_parts(4_118_000, 0) + // Minimum execution time: 1_592_000 picoseconds. + Weight::from_parts(1_651_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_571_000 picoseconds. - Weight::from_parts(1_662_000, 0) + // Minimum execution time: 960_000 picoseconds. + Weight::from_parts(1_060_000, 0) } - /// The range of component `n` is `[0, 16384]`. - /// The range of component `o` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. + /// The range of component `o` is `[0, 512]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(2_465_568, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(201, 0).saturating_mul(n.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(223, 0).saturating_mul(o.into())) + // Minimum execution time: 2_151_000 picoseconds. + Weight::from_parts(2_294_801, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(375, 0).saturating_mul(n.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(415, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_012_000 picoseconds. - Weight::from_parts(2_288_004, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(239, 0).saturating_mul(n.into())) + // Minimum execution time: 1_880_000 picoseconds. + Weight::from_parts(2_259_773, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(356, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_906_000 picoseconds. - Weight::from_parts(2_121_040, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(225, 0).saturating_mul(n.into())) + // Minimum execution time: 1_755_000 picoseconds. + Weight::from_parts(1_968_235, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. + /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_736_000 picoseconds. - Weight::from_parts(1_954_728, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(111, 0).saturating_mul(n.into())) + // Minimum execution time: 1_618_000 picoseconds. + Weight::from_parts(1_811_972, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(174, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 16384]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + /// The range of component `n` is `[0, 512]`. + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_872_000 picoseconds. - Weight::from_parts(8_125_644, 0) + // Minimum execution time: 2_482_000 picoseconds. + Weight::from_parts(2_682_484, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 8_489_000 picoseconds. - Weight::from_parts(8_791_000, 0) + // Minimum execution time: 9_899_000 picoseconds. + Weight::from_parts(10_342_000, 0) } - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `t` is `[0, 1]`. - /// The range of component `i` is `[0, 1048576]`. + /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `620 + t * (280 ±0)` - // Estimated: `4085 + t * (2182 ±0)` - // Minimum execution time: 122_759_000 picoseconds. - Weight::from_parts(120_016_020, 4085) - // Standard Error: 173_118 - .saturating_add(Weight::from_parts(42_848_338, 0).saturating_mul(t.into())) + // Measured: `626 + t * (140 ±0)` + // Estimated: `4091 + t * (140 ±0)` + // Minimum execution time: 33_645_000 picoseconds. + Weight::from_parts(34_407_662, 4091) + // Standard Error: 36_930 + .saturating_add(Weight::from_parts(2_062_425, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(6, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) - .saturating_add(Weight::from_parts(0, 2182).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 140).saturating_mul(t.into())) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:0) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:0) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `430` - // Estimated: `3895` - // Minimum execution time: 111_566_000 picoseconds. - Weight::from_parts(115_083_000, 3895) + // Measured: `457` + // Estimated: `3922` + // Minimum execution time: 26_924_000 picoseconds. + Weight::from_parts(27_753_000, 3922) .saturating_add(RocksDbWeight::get().reads(2_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) - /// Storage: `Contracts::Nonce` (r:1 w:0) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - /// Storage: `Contracts::ContractInfoOf` (r:1 w:1) - /// Proof: `Contracts::ContractInfoOf` (`max_values`: None, `max_size`: Some(1795), added: 4270, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) + /// Storage: `Revive::PristineCode` (r:1 w:0) + /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) + /// Storage: `Revive::ContractInfoOf` (r:1 w:1) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) - /// The range of component `i` is `[0, 983040]`. - /// The range of component `s` is `[0, 983040]`. - fn seal_instantiate(i: u32) -> Weight { - // Proof Size summary in bytes: - // Measured: `676` - // Estimated: `4132` - // Minimum execution time: 1_871_402_000 picoseconds. - Weight::from_parts(1_890_038_000, 4132) - // Standard Error: 24 - .saturating_add(Weight::from_parts(581, 0).saturating_mul(i.into())) - // Standard Error: 24 - .saturating_add(RocksDbWeight::get().reads(5_u64)) + /// The range of component `i` is `[0, 262144]`. + fn seal_instantiate(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `703` + // Estimated: `4160` + // Minimum execution time: 117_979_000 picoseconds. + Weight::from_parts(105_415_117, 4160) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_293, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_sha2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 966_000 picoseconds. - Weight::from_parts(9_599_151, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(1_336, 0).saturating_mul(n.into())) + // Minimum execution time: 620_000 picoseconds. + Weight::from_parts(3_414_286, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_463, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_416_000 picoseconds. - Weight::from_parts(10_964_255, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(3_593, 0).saturating_mul(n.into())) + // Minimum execution time: 1_043_000 picoseconds. + Weight::from_parts(3_402_639, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(3_667, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 821_000 picoseconds. - Weight::from_parts(6_579_283, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_466, 0).saturating_mul(n.into())) + // Minimum execution time: 642_000 picoseconds. + Weight::from_parts(3_359_294, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_590, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 1048576]`. + /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 773_000 picoseconds. - Weight::from_parts(10_990_209, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(1_457, 0).saturating_mul(n.into())) + // Minimum execution time: 606_000 picoseconds. + Weight::from_parts(3_789_868, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_575, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 125697]`. + /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 43_195_000 picoseconds. - Weight::from_parts(41_864_855, 0) - // Standard Error: 9 - .saturating_add(Weight::from_parts(5_154, 0).saturating_mul(n.into())) + // Minimum execution time: 46_195_000 picoseconds. + Weight::from_parts(31_420_941, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(5_165, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_747_000 picoseconds. - Weight::from_parts(49_219_000, 0) + // Minimum execution time: 46_933_000 picoseconds. + Weight::from_parts(48_054_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_854_000 picoseconds. - Weight::from_parts(12_962_000, 0) + // Minimum execution time: 12_531_000 picoseconds. + Weight::from_parts(12_690_000, 0) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) - /// Storage: `Contracts::PristineCode` (r:1 w:0) - /// Proof: `Contracts::PristineCode` (`max_values`: None, `max_size`: Some(125988), added: 128463, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn seal_set_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `430` - // Estimated: `3895` - // Minimum execution time: 17_868_000 picoseconds. - Weight::from_parts(18_486_000, 3895) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Measured: `266` + // Estimated: `3731` + // Minimum execution time: 14_694_000 picoseconds. + Weight::from_parts(15_032_000, 3731) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `Measured`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `3820` - // Minimum execution time: 8_393_000 picoseconds. - Weight::from_parts(8_640_000, 3820) + // Measured: `304` + // Estimated: `3769` + // Minimum execution time: 10_205_000 picoseconds. + Weight::from_parts(10_707_000, 3769) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: `Contracts::CodeInfoOf` (r:1 w:1) - /// Proof: `Contracts::CodeInfoOf` (`max_values`: None, `max_size`: Some(93), added: 2568, mode: `MaxEncodedLen`) + /// Storage: `Revive::CodeInfoOf` (r:1 w:1) + /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `355` - // Estimated: `3558` - // Minimum execution time: 7_489_000 picoseconds. - Weight::from_parts(7_815_000, 3558) + // Measured: `304` + // Estimated: `3561` + // Minimum execution time: 9_025_000 picoseconds. + Weight::from_parts(9_517_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - fn seal_reentrance_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 299_000 picoseconds. - Weight::from_parts(339_000, 0) - } - fn seal_account_reentrance_count() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 324_000 picoseconds. - Weight::from_parts(380_000, 0) - } - /// Storage: `Contracts::Nonce` (r:1 w:0) - /// Proof: `Contracts::Nonce` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) - fn seal_instantiation_nonce() -> Weight { - // Proof Size summary in bytes: - // Measured: `219` - // Estimated: `1704` - // Minimum execution time: 2_768_000 picoseconds. - Weight::from_parts(3_025_000, 1704) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - } /// The range of component `r` is `[0, 5000]`. - fn instr_i64_load_store(r: u32, ) -> Weight { + fn instr(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 766_000 picoseconds. - Weight::from_parts(722_169, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(7_191, 0).saturating_mul(r.into())) + // Minimum execution time: 9_451_000 picoseconds. + Weight::from_parts(10_620_260, 0) + // Standard Error: 77 + .saturating_add(Weight::from_parts(84_885, 0).saturating_mul(r.into())) } } -- GitLab From a995caf789a10b5be8a740bcf5c203d0e2154486 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 3 Oct 2024 22:52:22 +0200 Subject: [PATCH 039/127] Simplify bridges relayer cli configuration (#5912) This PR removes the requirement to set the `LaneId` in the relayer CLI configuration where it was not really necessary. --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 - bridges/relays/lib-substrate-relay/Cargo.toml | 1 - .../lib-substrate-relay/src/cli/bridge.rs | 23 ++------ .../src/cli/relay_headers_and_messages/mod.rs | 52 +++++++++++-------- 4 files changed, 33 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46c8b607e8b..f72aaa7e187 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23883,7 +23883,6 @@ dependencies = [ "rbtag", "relay-substrate-client", "relay-utils", - "rustc-hex", "scale-info", "sp-consensus-grandpa", "sp-core 28.0.0", diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml index 89115cfeee9..b0f93e5b548 100644 --- a/bridges/relays/lib-substrate-relay/Cargo.toml +++ b/bridges/relays/lib-substrate-relay/Cargo.toml @@ -22,7 +22,6 @@ num-traits = { workspace = true, default-features = true } rbtag = { workspace = true } structopt = { workspace = true } strum = { features = ["derive"], workspace = true, default-features = true } -rustc-hex = { workspace = true } thiserror = { workspace = true } # Bridge dependencies diff --git a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs index 2e15562f6c2..9467813f86c 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/bridge.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/bridge.rs @@ -23,12 +23,9 @@ use crate::{ parachains::SubstrateParachainsPipeline, }; use bp_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; -use codec::{Codec, EncodeLike}; -use messages_relay::Labeled; use relay_substrate_client::{ Chain, ChainWithRuntimeVersion, ChainWithTransactions, Parachain, RelayChain, }; -use std::fmt::Debug; /// Minimal bridge representation that can be used from the CLI. /// It connects a source chain to a target chain. @@ -102,22 +99,7 @@ where /// Bridge representation that can be used from the CLI for relaying messages. pub trait MessagesCliBridge: CliBridgeBase { /// The Source -> Destination messages synchronization pipeline. - type MessagesLane: SubstrateMessageLane< - SourceChain = Self::Source, - TargetChain = Self::Target, - LaneId = Self::LaneId, - >; - /// Lane identifier type. - type LaneId: Clone - + Copy - + Debug - + Codec - + EncodeLike - + Send - + Sync - + Labeled - + TryFrom> - + Default; + type MessagesLane: SubstrateMessageLane; /// Optional messages delivery transaction limits that the messages relay is going /// to use. If it returns `None`, limits are estimated using `TransactionPayment` API @@ -128,4 +110,5 @@ pub trait MessagesCliBridge: CliBridgeBase { } /// An alias for lane identifier type. -pub type MessagesLaneIdOf = ::LaneId; +pub type MessagesLaneIdOf = + <::MessagesLane as SubstrateMessageLane>::LaneId; diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs index e875c53a2e7..bb6c689a76e 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers_and_messages/mod.rs @@ -31,7 +31,6 @@ pub mod relay_to_relay; pub mod relay_to_parachain; use async_trait::async_trait; -use codec::{Codec, EncodeLike}; use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use structopt::StructOpt; @@ -47,7 +46,6 @@ use crate::{ HeadersToRelay, TaggedAccount, TransactionParams, }; use bp_runtime::BalanceOf; -use messages_relay::Labeled; use relay_substrate_client::{ AccountIdOf, AccountKeyPairOf, Chain, ChainWithBalances, ChainWithMessages, ChainWithRuntimeVersion, ChainWithTransactions, @@ -239,20 +237,9 @@ where + ChainWithRuntimeVersion; /// Left to Right bridge. - type L2R: MessagesCliBridge; + type L2R: MessagesCliBridge; /// Right to Left bridge - type R2L: MessagesCliBridge; - /// Lane identifier type. - type LaneId: Clone - + Copy - + Debug - + Codec - + EncodeLike - + Send - + Sync - + Labeled - + TryFrom> - + Default; + type R2L: MessagesCliBridge; /// Construct new bridge. fn new(params: ::Params) -> anyhow::Result; @@ -303,7 +290,7 @@ where self.mut_base().start_on_demand_headers_relayers().await?; // add balance-related metrics - let lanes: Vec = self + let lanes_l2r: Vec> = self .base() .common() .shared @@ -312,28 +299,48 @@ where .cloned() .map(HexLaneId::try_convert) .collect::, HexLaneId>>() - .expect(""); + .map_err(|e| { + anyhow::format_err!("Conversion failed for L2R lanes with error: {:?}!", e) + })?; + let lanes_r2l: Vec> = self + .base() + .common() + .shared + .lane + .iter() + .cloned() + .map(HexLaneId::try_convert) + .collect::, HexLaneId>>() + .map_err(|e| { + anyhow::format_err!("Conversion failed for R2L lanes with error: {:?}!", e) + })?; { let common = self.mut_base().mut_common(); crate::messages::metrics::add_relay_balances_metrics::< _, Self::Right, MessagesLaneIdOf, - >(common.left.client.clone(), &common.metrics_params, &common.left.accounts, &lanes) + >( + common.left.client.clone(), &common.metrics_params, &common.left.accounts, &lanes_l2r + ) .await?; crate::messages::metrics::add_relay_balances_metrics::< _, Self::Left, MessagesLaneIdOf, >( - common.right.client.clone(), &common.metrics_params, &common.right.accounts, &lanes + common.right.client.clone(), + &common.metrics_params, + &common.right.accounts, + &lanes_r2l, ) .await?; } // Need 2x capacity since we consider both directions for each lane - let mut message_relays = Vec::with_capacity(lanes.len() * 2); - for lane in lanes { + let mut message_relays = + Vec::with_capacity(lanes_l2r.len().saturating_add(lanes_r2l.len())); + for lane in lanes_l2r { let left_to_right_messages = crate::messages::run::<::MessagesLane, _, _>( self.left_to_right().messages_relay_params( @@ -346,7 +353,8 @@ where .map_err(|e| anyhow::format_err!("{}", e)) .boxed(); message_relays.push(left_to_right_messages); - + } + for lane in lanes_r2l { let right_to_left_messages = crate::messages::run::<::MessagesLane, _, _>( self.right_to_left().messages_relay_params( -- GitLab From 261b3daebd6231462e06f9c787e170aef48857f5 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Fri, 4 Oct 2024 10:37:25 +0300 Subject: [PATCH 040/127] polkadot-parachain: Remove shell node (#5911) Removing the shell node variant for the polkadot-parachain as discussed here: https://github.com/paritytech/polkadot-sdk/pull/5586#discussion_r1752635254 Resolves https://github.com/paritytech/polkadot-sdk/issues/5898 --- .../workflows/check-cargo-check-runtimes.yml | 16 +- .gitlab/pipeline/build.yml | 13 +- Cargo.lock | 72 --- Cargo.toml | 4 - .../parachains/chain-specs/shell-head-data | 1 - cumulus/parachains/chain-specs/shell.json | 38 -- .../runtimes/starters/seedling/Cargo.toml | 79 --- .../runtimes/starters/seedling/build.rs | 26 - .../runtimes/starters/seedling/src/lib.rs | 400 ---------------- .../runtimes/starters/shell/Cargo.toml | 99 ---- .../runtimes/starters/shell/build.rs | 27 -- .../runtimes/starters/shell/src/lib.rs | 453 ------------------ .../runtimes/starters/shell/src/xcm_config.rs | 100 ---- cumulus/polkadot-parachain/Cargo.toml | 3 - .../polkadot-parachain-lib/src/command.rs | 3 +- .../polkadot-parachain-lib/src/common/rpc.rs | 21 - .../src/common/runtime.rs | 2 - .../polkadot-parachain-lib/src/nodes/mod.rs | 1 - .../polkadot-parachain-lib/src/nodes/shell.rs | 152 ------ .../polkadot-parachain/src/chain_spec/mod.rs | 24 +- .../src/chain_spec/seedling.rs | 55 --- .../src/chain_spec/shell.rs | 45 -- prdoc/pr_5911.prdoc | 16 + 23 files changed, 21 insertions(+), 1629 deletions(-) delete mode 100644 cumulus/parachains/chain-specs/shell-head-data delete mode 100644 cumulus/parachains/chain-specs/shell.json delete mode 100644 cumulus/parachains/runtimes/starters/seedling/Cargo.toml delete mode 100644 cumulus/parachains/runtimes/starters/seedling/build.rs delete mode 100644 cumulus/parachains/runtimes/starters/seedling/src/lib.rs delete mode 100644 cumulus/parachains/runtimes/starters/shell/Cargo.toml delete mode 100644 cumulus/parachains/runtimes/starters/shell/build.rs delete mode 100644 cumulus/parachains/runtimes/starters/shell/src/lib.rs delete mode 100644 cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs delete mode 100644 cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs delete mode 100644 cumulus/polkadot-parachain/src/chain_spec/seedling.rs delete mode 100644 cumulus/polkadot-parachain/src/chain_spec/shell.rs create mode 100644 prdoc/pr_5911.prdoc diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index 16263788b8b..7e9f0dc77b7 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -85,25 +85,11 @@ jobs: with: root: cumulus/parachains/runtimes/contracts - check-runtime-starters: - runs-on: ${{ needs.preflight.outputs.RUNNER }} - container: - image: ${{ needs.preflight.outputs.IMAGE }} - needs: [check-runtime-assets, preflight] - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Run cargo check - uses: ./.github/actions/cargo-check-runtimes - with: - root: cumulus/parachains/runtimes/starters - check-runtime-testing: runs-on: ${{ needs.preflight.outputs.RUNNER }} container: image: ${{ needs.preflight.outputs.IMAGE }} - needs: [check-runtime-starters, preflight] + needs: [preflight] timeout-minutes: 20 steps: - name: Checkout diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 931aef80233..0d509879b44 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -256,7 +256,7 @@ build-test-parachain: # DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-bridge-hubs # DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-contracts # DAG: build-runtime-assets -> build-runtime-coretime -# DAG: build-runtime-assets -> build-runtime-starters -> build-runtime-testing +# DAG: build-runtime-assets -> build-runtime-testing build-runtime-assets: <<: *build-runtime-template variables: @@ -298,22 +298,13 @@ build-runtime-contracts: - job: build-runtime-collectives artifacts: false -build-runtime-starters: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/starters" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-assets - artifacts: false - build-runtime-testing: <<: *build-runtime-template variables: RUNTIME_PATH: "cumulus/parachains/runtimes/testing" # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: build-runtime-starters + - job: build-runtime-assets artifacts: false build-short-benchmark-cumulus: diff --git a/Cargo.lock b/Cargo.lock index f72aaa7e187..2747a3dece0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14937,10 +14937,8 @@ dependencies = [ "sc-chain-spec", "sc-cli", "sc-service", - "seedling-runtime", "serde", "serde_json", - "shell-runtime", "sp-core 28.0.0", "sp-genesis-builder", "staging-xcm", @@ -20223,40 +20221,6 @@ dependencies = [ "libc", ] -[[package]] -name = "seedling-runtime" -version = "0.7.0" -dependencies = [ - "cumulus-pallet-aura-ext", - "cumulus-pallet-parachain-system", - "cumulus-pallet-solo-to-para", - "cumulus-primitives-core", - "cumulus-primitives-timestamp", - "frame-executive", - "frame-support", - "frame-system", - "pallet-aura", - "pallet-balances", - "pallet-sudo", - "pallet-timestamp", - "parachains-common", - "parity-scale-codec", - "scale-info", - "sp-api 26.0.0", - "sp-block-builder", - "sp-consensus-aura", - "sp-core 28.0.0", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime 31.0.1", - "sp-session", - "sp-transaction-pool", - "sp-version 29.0.0", - "staging-parachain-info", - "substrate-wasm-builder", -] - [[package]] name = "semver" version = "0.6.0" @@ -20549,42 +20513,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-runtime" -version = "0.7.0" -dependencies = [ - "cumulus-pallet-aura-ext", - "cumulus-pallet-parachain-system", - "cumulus-pallet-xcm", - "cumulus-primitives-core", - "frame-executive", - "frame-support", - "frame-system", - "frame-try-runtime", - "pallet-aura", - "pallet-message-queue", - "pallet-timestamp", - "parachains-common", - "parity-scale-codec", - "scale-info", - "sp-api 26.0.0", - "sp-block-builder", - "sp-consensus-aura", - "sp-core 28.0.0", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime 31.0.1", - "sp-session", - "sp-transaction-pool", - "sp-version 29.0.0", - "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", - "substrate-wasm-builder", -] - [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 70eda144b5c..8c9bf848c5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,8 +130,6 @@ members = [ "cumulus/parachains/runtimes/glutton/glutton-westend", "cumulus/parachains/runtimes/people/people-rococo", "cumulus/parachains/runtimes/people/people-westend", - "cumulus/parachains/runtimes/starters/seedling", - "cumulus/parachains/runtimes/starters/shell", "cumulus/parachains/runtimes/test-utils", "cumulus/parachains/runtimes/testing/penpal", "cumulus/parachains/runtimes/testing/rococo-parachain", @@ -1187,7 +1185,6 @@ schnorrkel = { version = "0.11.4", default-features = false } seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } -seedling-runtime = { path = "cumulus/parachains/runtimes/starters/seedling" } separator = { version = "0.4.1" } serde = { version = "1.0.210", default-features = false } serde-big-array = { version = "0.3.2" } @@ -1198,7 +1195,6 @@ serial_test = { version = "2.0.0" } sha1 = { version = "0.10.6" } sha2 = { version = "0.10.7", default-features = false } sha3 = { version = "0.10.0", default-features = false } -shell-runtime = { path = "cumulus/parachains/runtimes/starters/shell" } shlex = { version = "1.3.0" } slot-range-helper = { path = "polkadot/runtime/common/slot_range_helper", default-features = false } slotmap = { version = "1.0" } diff --git a/cumulus/parachains/chain-specs/shell-head-data b/cumulus/parachains/chain-specs/shell-head-data deleted file mode 100644 index 032a8c73e93..00000000000 --- a/cumulus/parachains/chain-specs/shell-head-data +++ /dev/null @@ -1 +0,0 @@ -0x000000000000000000000000000000000000000000000000000000000000000000c1ef26b567de07159e4ecd415fbbb0340c56a09c4d72c82516d0f3bc2b782c8003170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c11131400 \ No newline at end of file diff --git a/cumulus/parachains/chain-specs/shell.json b/cumulus/parachains/chain-specs/shell.json deleted file mode 100644 index a02734316d3..00000000000 --- a/cumulus/parachains/chain-specs/shell.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "Shell", - "id": "shell", - "chainType": "Live", - "bootNodes": [ - "/ip4/34.65.116.156/tcp/30334/p2p/12D3KooWMdwvej593sntpXcxpUaFcsjc1EpCr5CL1JMoKmEhgj1N", - "/ip4/34.65.105.127/tcp/30334/p2p/12D3KooWRywSWa2sQpcRuLhSeNSEs6bepLGgcdxFg8P7jtXRuiYf", - "/ip4/34.65.142.204/tcp/30334/p2p/12D3KooWDGnPd5PzgvcbSwXsCBN3kb1dWbu58sy6R7h4fJGnZtq5", - "/ip4/34.65.32.100/tcp/30334/p2p/12D3KooWSzHX7A3t6BwUQrq8R9ZVWLrfyYgkYLfpKMcRs14oFSgc" - ], - "telemetryEndpoints": null, - "protocolId": null, - "properties": null, - "relay_chain": "polkadot", - "para_id": 1000, - "consensusEngine": null, - "codeSubstitutes": {}, - "genesis": { - "raw": { - "top": { - "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xe8030000", - "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", - "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", - "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x08147368656c6c", - "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", - "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058449e04ae95c504114c108066940e98db59ddf6af6efb57b7dde0b64469d187d03f0c9208338b0a9eb6f31f3d0dab728101fedf5603adc13d96d400acfcf610cbaa56a582dd86ca95fddffd6def4db6dc52ca94520a3b115310ff106bf4ed568cbaeacc358aede3df1a119009a9ebcf35e2d7a3228081c842f12de71abdbaa558cb26ed8b1e4708e99b4ff5c2375fd2f34d0ec57cfbd42f3fd5d97f5aa37e47cd7ed70809d6d09020c290c54f265f38c2ab0be9118597d725020bb23c10d6c13fb7f4884c8fa6e0f2cf298c88f5f3032ea882fcf98122e8e0495e70f043fae5cb1e607e1299610c252b7e1255b1c232c34fa21498e115066390603046942db480072de0c1b39330226be59f572cd8f2e5cb6b10269b007d9542fc7cf21222f843fae59b5318103d628114fe79931f283d51e9f9a75d5c15bce0350e599606d1a329c8f0cd3736e7f89cb121edeb96bb140b32b45fa273c6829cfff4c6a947537879760a23c23bf8e70f13c2fe56aaf2ec747295679dbdc5c9bfde04beba1e4db182576c487ff9f2ea456f9f1762e0c5dff89df20e8b6fb74194bf31b4cf6e71746e291684a5f3c586ac37395579a61ec5008cc780708bfa2ecea26fde116788c342c80199fb2c9bcc686ffec3968b43fae5cb97f5c688dcf8f56db1c98c2f80d87876587c47227e1bcf6ef1a5584721f676c680b09344d62b9ba61e51a1e589f85b4ef626fbed7de33bde788efa1b89ac572f72cb62581ca47e14d3de2e8daa8777b15d2e3e89a209e751c43c5174b9f82453acf90f6b9cea384248af7228e69b0ce53c117f93549cb7ab17bee72d97077f9746adc3bbf8a4270a232df8f2e5cb1720e7967bbadceec10ced97a702c5819cb3bae506f37c352a18ff62f39fb65ae4576fb1df0a5a40a6fec34f54cb154ca86fc2df8125fb46640ac6ab7754309ebfaf8efa86b7dc2587b79a9bb6b6ef06cc04418bfc1d950eaf1810ffc31cf78bd6c91675372bd6be15c380acbd415920f692b6d8715d310664eb21b082648d4d99a54264cd8378e5e2082578d7311959c20b07443d31deede0d3a4bc72e7a62ae2086946ed3be3875ec57d1d3e4128974076a2f7eb166cff61783dfb7ad9b7bb95eb12a2f7eccf15233274837de3642095cf7daee4bc74f8e45cb10428cb49b9bea85f5191e14501234268408dc378e709e0d3341d41013ecdd20f9fa66631824ffcce7ba878e744b04f03e9174db37ccdcf9ec7a1bc9ddba558107406c25e79c380ecb353d16a18037eb8abc818907576c682d8dde2d8bc00f6f5f5c586f4108c9243d639874f943bb7a8a85fe293f3acc67d894f7a7402162874e796553915a1577152fe288771163d152957cc882227505464a37f51685d5f31210cc52f9fb8bdbcfe50e41666b184406fb7ba1d6c87edf6a98eb323e51052de9473a7ffc3941482eeacc52cab2f224e5e6e294651de0dd961905b1b71dad7db39e2b4b3536e2d96809f57dd31295f5f8c083be50d849d72c5822877d0895ee5d65eaef225edcf61f4f29e5771ee3f77feded07e79e85647a2f7ed2f4e76de73bfa154ddd1fec38a0171de4fcea27f4ec5d92e049d72ab31204b2e5f42f4def22543eac5c8e88966be72a2eb496e5d975fd77579f94a95a7fd8a73bfafe8dc524cc872f9ca2d521c32c0aff240ef38f9a13b270571de3eb45fbe72c51450f49554ddc11810e750b6ff34f55cb4dad917c87e7bcb480bad686f1378d1dc6ab1a41863ba32d97a49bb302ada858025a472e894abf3450a42f2ca7f9814f515aa52b1c5b88709a99c1449bece0ee3dc87be1810e815ccc961d3c6c626dd1fde5db762f62dc58274653236e5b0491b164492b3e849fed316293aafaad8647fee3b27c526fbce2dd665caa92a3aab97bb6186fa7180f4438772bf8a2b8528af9c14f59dab8ffd175d79d41b9f7a5ad0107ce74328afe415a36c9a39afdebcbdc426e8ea2420fb904d0a23ce73edc2c8e883788b8a9372e896a5582567d1574e8a739fe430ce206bf4437bf4aa3c2487fed3a4b85e45f61f86d70e352acee64f0eed3b59f4f645611555898296216c59c31e62500620c52ce5129bd63336ad13914ddfebd7b7c7afaf17c127f7ebce27c5d245caafebb0cf022d11beae3a2b85f4a8d93b2220136a07e2e7af5bee52f5317bde1529a07a67b9e6b3e574f2edb33242cff04e5269ee1a9bbb280121bc80e50b907ac7f9c4f77c9ea4a88aa77c3a79e7aa3f5485987a2445099e689f924a348c1fea2f5ffe492a4fa83b29c2c20b509346f3897772b6cfead97fba23106fedd5a2ba6216d02b44015220be9d6c11376bc1b2f42aea1ad813285ef43376656f47cf0eaf8c08eb275b571680ba5a961220d3a328bc703eb1b39bf8c4cc5164f1dc40cccc4c58eb1b3d094b5f7566f6ecd6aecfeb797723ffd13779185f904f731faafa589f3dd97e076909db635ebec94c8cd5d7c67eacc9bbb87c930b467d2c59a31f32c0afb32b46a4192d262433faf58b4ffa6cf4ab9abdfe50d4cb70b0f4954f939d7d7d36b75aecc9d89bab2b46845da84965534bf9c345bc10b0940059f31e3e4dd8834f3333f169f6bc73e744f0a9bdf3129f144b1c8ede790efb309073e553ff2ce21dbbf31a3eb53b777ef169ddb9ea3829a4474e2c2540e67c9acdc4a7e9dfdeaec327fe3e7a62e5db2ff661a01dcf3f73be5d779358ce5567f67cbbe5e2beb5978bfad6628d4dce357feed8029a53fec396c5eaeae284ef5ce374578bd01563398b9e7565544ee7cd7f9a237417f5b7c50c3a773596d59762413a6c6a5ff7b891a38bd3dfb9d5578b952b06e52c7ae83fdc98aecc84269693a85fd9e49ce4253e356fcf69bb72ae937cafec9d677cda8dca266593e58b05d96f6cb2e4b49ce41bf5ad48d4cf6e2deb368c8bfa5c7ae7b07da67020e59dd9deb92acfcc79e7ed3f0d63e5a498c509ff3963aa32179bd61b9b9eab2f1654499c21944387d7b3532f2a16e42464137b566a51df61fb342323232323a07693fa689d6fef79fda1a85ae785a53e991e4141c6eb11145d78bc1e4191c55bba23d353a64750a8e0f5f5d91b1340760445153fdbb3c37508b57d947766f6eb33fb095f9dbdf1c9b9ab433ee9ab5b8d0535394446b98b945b7b51fec38c1951f4945b8b09794ec9f9fcb9c558d090987ffec32deebba8cf5850736b2f2a3e574cd9346b7e2dc59c9c4ebc732a4ef894bf389f2b9b28ffe917f5a938db8bfa8a3949d4cfa675d5e1b7ccc5a26193a9ebb3944fed13beba6ed46f7c6257b73aea73dcb7b8e37ee3d3babac557c7758b47d65ee38d91356f675f2c88afc5580e91ad5b0d0b5a5facc9c62676cb5d8bad2bd652d9c4fec3d65e1bf71563807e47fd22defd81a500c8ac18d599fecea7c6a676754b77648b05ad2b16645fd9d4fec31b954d137e7b0032f6762be393b249bdbd864febedcaa7fd097f66df6eed95f149bf5db1f51ffe618e3843e0b3c367893384bd397b9344fb2b954db3bdba6242d6594ef6f51fe6dd24584a808cbd5df9344bdfde5ee2937a7b0e9fd6db1bfb2c507bc627e5c2065cbe5df9a43f6bbe5d755a0af9eb0f45bb412c1522d323232e2ff3ca05942baf3296ea8b2293af58d410c553af58d250c6efab8da5a64c9db5ae7c9aea2b954deaea45bb332c3d329af24a59ca93a9b3742753f7b400e40553484f0e2f0e0f0caf0c4f0a2f0c4f0c8f0c8f0bef8bc7c5cbc2d3c20be361e169795b7867785f7860bc313c2abc2a3c2f1e973785f78497e5e9e04d7956bc123c285e94d782c782b782a7829782878207e59de0b9e0e1e0dde055f16cf06af068f0a4bc193c19bc183c2a1e0cde0b1c196e0e4e0e2e0eae8b0ba36de0d4e0b83838bca3ae82c570657066e829382f4d856687ae8223833b432ba3e9a1e5c119c1a5c0cdc0c9c061e170e09ce09ae098e08ee092e096e0b47057381ab81c3818b81fb42f4e076e05ae8a1b82abc201c149712570460e8ab60747859be25ce0b0b820b83b70154f8b77e525c19dc041816fd070683d683c7839705f7062682374111a8bb6d244e821b410d6047c85f6c25f3412d80aec054b81a7c050e02a70172be5c161cbb057582fb60a4b857542103adc97a5410420c001258a68200318b840050e70c4c7007a00bd2d1e1326400197078787d784352a7242822746208210380182261f2062b264a8092694782089d044f2a83c1f382dab048706c7056705e785ab4283d25cb059dc145c96b68246458bb25e78667026682960373430da96e685b78366a5a1a0c160a5d05ed088d058e0be7065383db83d74165a0c0d466fa1a1d05a68366c150d0637468761dde0a27052b827ac065e037771547058705758315c1d96097bb45dec11d60adb83a6413b01a381c3d81b6c171606cb86b685a6a55da159a179c158ec969dc24661b95828ec1316cb6ab149d8256c1b56080b84dd01a761b56c95cdc152591f2c0ff68ad5c16a61bfb0536c0956066b83adc1a260a378c010053c23210e28c2009cc5dad76d68a8b88f30416203459a40e2c812458a58db4a283952a40849f748920d2041a4e4031c284294c4912548945082a448911b07d332502638a0044991248a30c10125429430818408892449983800511247922461e20041740c8890587244c9910ec4a6429984e088501126458e28c14412479428c224044786286102091326471e70a35fa02040d2c42f3922b48489091089ad023d42848412489220d1a4c8910f1cf9400320a0c4910e7400090804d129502642491c592289964299102939b2e4c8124a90781b2941c2011cce832681848912244b889c38522404499a680289224b8e289104c907808492602209500edf413d5004092224944082e8c80264d80e2240920409244448942099c17250a1249848b2812247883e90c411278ecc301c74091307b8645a0c8a8b2b5bbc16597a6464f45bd4378fa7c053d826966aaaaa3a76765cb75de675ccddda39d78dd9355e755ba3b9c66b6d6b40d7dac2d71cc2ca44ea6632c176bb37cc6d9d73cbed9cd3e61cb3e55ceb1acc4d5114e51ac31aae063b08bbc1aa2184eb1aa9c11a4e9d53e8b4e1ba57c335e6edd61a1031ba95bb6e3b07371cadadd3f961d9ad5bc7bc8dd7ed2e4376ce396eae313be79a73ee6a55b5cbcddd5c63d7ad21ec6ecdb5065d7bf2b9768dd755ce6d7330ec5c734ea606e59c73d0ed6e3bc7aebb39e75a43ede61a352ad78ed9f1cdcd8dbb71cdb9beb9b971cceea6dbb91bbee11b66e76e9ccc0db36ce8fa4976ea5e2bb783ad9dd3d6ae356ce7dc3676ce717371bb3599d69c6539c78e1d376edc1c376e8d5d73ccce3576ceb9d6989949ce39b7aead6bcd39e77897dd4d63c76e97ddf23a6eccdc1caf73cd35e798991db773bcccbb8e1b6407e1766b8eb9b5b6636eae612355b09fc31a356ab4d620bb6dfddaed6ebba1baabbb3674b7edeebed696a276176aab6c5ccc3eac73eb761536754cb5d66ad4a84151356a3c2dbc2f8088b9c9a9219404121b489284091d383568d8a07183d5204a0209264a90584254840889073cc084a946c2e03039d20125482c91845472a4892690c8f1001222251f5822613840f4011f8e2c5184480992254d14498209120fe4501eb0c1478e2ce14091254c10219104134992209143c4c907acc7100128e204132020b2d1012548966082c4125e83680912444c80186209931f6e7488234c981c59d284079848a2c892234d9848e2c892224830512289234502207412c0104d247184898fcc12491021b181224b3041a20409074a1c40b2e44893220308800004308412478a3041d444089624f1038f2448847a6c80a0314309248e2c3185e088501122278e28d951d301206834004c88982071e2888e191dc209241c38b2a4899e98231d40620913444c8a289104c9124914214af20124387044891d9924479a1c4952c409243c7023eb0013498a744009920e748089243731479838cc12248e2449a2881347945082a4881224482c91040738c0441122278ec8264a8e34a60128a28409244c961c695284880926961451e2c892234a2c61a2c84900431419e2881012493650e40892254445889c38a2a38992232da31c82254c78e088075e1325477a0793c2eac489132745ce4911171539235ae4c44951512b6a468ad6c916151515751134a24545da46548d3871e2c4899366c449d13a71c2469cac13274eda48d1aa1175e2a4a88da813276c449dac91a275b2469c6cd11a71b24e8ab8883252b445ce48d116ad112d7a46b4a8888da81615151535235ab46ca4688b8a8ada48104cc5aaea7a8009121a2e45e634339ec9f88c9ca20424858c1457907cc6657cc6336fee3d7c82debc08f6d929ba3375bef9d53e0aa493882a5d58f1e29b2bef952fbef9ae20db572c5628e3e74ef9e633517ba75d2666517b27266aefc044ed1d2c7aefb493a25232199b2e277947a5642e925436919c47d6d625a36cbaa4b289e44057acf00987e4ed243de281976f6f5ff846464ff2c582487288acbd622d81ae586193be0e9175f7c238a18aaab6e942599bab363961cb99bd02b19c259aecc915eb2670a02bd88b78972c91edef12b8ed0ab2f61acedb97f0b3e9c7f34951ec3fbc649f88d7c7fa6c0ff5d43e9bb76fce42cd1b9bb8c9d9228c2ac8f67581843423a3c684ac91d1b733260452de24cc1a0cd715043090b187261481065528e37a40141518210e586c610578b0bcb0f8d5b57d8a7090072b6ab0050da290c31740a49fd9b6c0d22332c502041e3cf4cb9f966f57f551a90fe80ae37e17cf4e61014216df764ac6ed98cf9e87f149448ccf1e5cc4537ef934bde573c7b74f366a30dede660ed658e972dcf6e8671f7d674f92d743b71c143e350fe23fe3d0954f323edbc326e78cbc641e06f79b0cc53cc9b32c368771ba2851aaa892c591175bc2f0e2ca8042c514215c795d5858dc7750f814e3ed0f1be29eb0a93d46ce1a36b5c34807854ded30ee3b2c6353fb5a914d67f4ed333b220a36027afec358d427a910f3f2750a23e2e42f38841dcac97f79c3a03b287cbabcbd61500e61bfe42461436228df8611b1e4d03ec97fd8e2b7ca0de6c973e74f8a54a038d073cb455ebe5c5ca0179fb01190091ce80ae816d52e8a9a0f4212e9a22297e061ec2f5f3e8aa7e47402c543b7ae0b4621e85fbe2afa4939143e5144d867d153ff54e4775810033c94430678e81616448fbe32fa6a8fbe92437bf4d01d047250f8b4df4ec57d20c5b8819c917be2a03c2daf3f145d4eb888dbf2c10e7616efb6206bce25e7cdfb8e7b78d79939333232820248d95b182c054016447f9d075b5673238a18b07fa9dc1664edeaebbd3a4bded4f737eef76046147d8f572f4283560e91ed2bf6ee90f92f63a1bd7c734bf9f49e663c32cad5f43c328ab9c1a8f2501e44253ea73c73cb6141991c227b71bf31238afe492a501c28bac557e6835b3f6cf910f7ad6eddba45f5f1fa297f3b05e5696ef1e543d4b76c6cbce4945b950d6563e3038e5b7d598d01b9e1cf2dcb5d5cc31b46a41b23527373035e54d47f35eec309b27c3b8e5b7aaa22458164ca0514284f73956e56298a4746e38a05cd70561fcd5b4f9437d7fdd1595688e6357c9666f6975faeead3557aa7f9e53458107f1ade55da67b37496fdd15eac10cd1be635e42cddc89939afa9223b4bfbb4969b1a8ee398b796f6f9c19bb717fbe3798de3b496e835e4cc7eb69698b7f19bd6f2cd7f8894eed838155b4d361ea3eaceb4791baf89aa3cf3c6dbb8f2dbb417aa42d8f866d91fea433f0bac483a6ad2130da77c879e6828e53834dc2687460dcc88a2a7e18a01f1a7e17bd5d0701b0f2af90f6771daf0cc67e29cd9b711f7df73abc6860d973ed8b081831951f4365cd288356e131b9b6afcd988739f86cfc080d4d4c0d4c849c333b7f11fb6665c8a01a10183d1701bb7a1c165f029c79bb7119f70bc793f81f1cde28596175b0a9b9acbd855b0a9b98dd8534a578c4fa29bc081a2bc02c73dc729b76ce0e0386644d1e3780c1694e35676311684bf24935c8f33b49f7993f6cf8762fe49d9643f73c580646ef90f635ea3864f37de7cbfe053c99bef163ed978f305834f34bcd9f0192c888d1b6eddf0c582e00cedbb37693e780dc719dacff126ed87d6cbe3f850cce348559e89e3ec1846a4c67d904df65dcee7377e6144da716493fd1c399f673724ce101b8ee338522873cb6d48a11b27daaff11fd6b76af0ea4c0d07f3de87a89f133b0a9b9ae3c4a6824dadcac7bcb85eb0a9798db85ad8d4bc26ee176c6a7e13770b9b9a97e282c1a6e63671b9b0a9398db861c8c4b92fe3331890fdcda23ccd174a666253731a9f1175d8d4dca2713d9f37994f191fed9d1a9fd553aeea2388be8c53beeab3597aa739e535b0a0cdd2bc464e98c999cdcd42237d4f90355f9f9e05f531b45e9648fbcbc227f6e6eb059fb4a80f1dd2fe669aeccff84fcf447d99a89f45fd98381d26ce0b8bb3f4575c427d732b12edf773929c30fbe7959cd937dd91d6cdb5182587c87a5eb13541f6e4542ebfbebe59dae76708517847ff670852d8a4efe4843f37cb376f2f3e341a4bb05ef440e985da597d299f3849e927f4e73c9394fe4518350d1974e781d23bf6e7a3404df65736e981d23b29d4bc65c62db0f607490aee6e5e2696ee50f5f181f642eceda7e7dac5abef936c7d12f1eada9c04cac1166cd25f37f1e949202bba00cd1dafdd0e4b1d67d6bc01da855111d890072ee82086190035a9bdc3fc904fd5b367ed5354042098a08a315821c615b000554364fe3f9cb1152c981a9f30f399c5f82c659ef934bd9315f421a664939ba732e3d33974c83ea9190e877e7a0625a94461e333b2448a91b3f4995b7ed5c8093f6bd2bf84737d1a6e398f4c888a931ea02b287f5890f3e70ff249a8f98dafcfeb5fa41c260e399f45ef241320927291469c4e8e9e862f4644c69f62ce3952b1f1928b4c68b84c9c94d3f017677b1ab28af388876ed9c42b9e53f126d6786464347cbf3c948d05514c434a2a2467cc490fd015cead261d9845af847aa27ee745cf5810e743eb7084b4770ebfbdbb89fa36518160d4a321548133e4b9f3a742648ac54a95cf79c56245056f597ab93877975079d0c31ebefcf321f8a404503c93a26f4259ed827c1252179dab3cc65d9cfb316e597ac150eeec43e3ecce3e4e139d774c33333e342ee3ca3e364e79f6bc042939339bec394d54de7972967c262aef587c4dd374199fa599653ea18ff20ebb1372aeaf6c82693fdf979f31febe3c0dff61551f33e08ce734e27497aa8f49f9d07ce2631cbaaa0f279bdc7c8dcf9821fd43d09908cdf018179ae1ae49ff124e0e419fcf9bf42f41e3c921e8cf29777136e76f86bbd85e311a52d534673835430ed5482a53d0b814334e334323a728014941f219390504920273292e9722d31d76928cb4d82f9742752793133ecb79c4cf243d2b14e36b14239dc3384cc4e215b3de61b722293e41b2220747a0410c9c508529404e744b153d48f145185cd8c31540da3b45275593f3f96a3c3b64cf9dcff6ee9f53fedc0971fee4ec7744a8a1fdd7ee0d69b2ff248e10f8cf9350dfa49f7a429a3b39d70917b5132e6a4e5a1145cdd4c052b6948be8eeee6e539bd844013665a66faa995a6bdddddd5a6b3a408aa2288a821445398c8d2493003665ee280a42485110420829a7288a82d0298a720821a4ece05a6bad396fada9baa8950c11179b76db16f639e79cdb1fea412c8879d9313be7f8cdc1b5d65a6b6d9d7bad35fde65ec362e75c6bcef16e6b8dd739e75cb342fdbdf7de736d9923d8047d964c3b66cf37e79cfbf39ca63a5eebd452befeb005ec530bb1a0ddd57d8fa27c5755576a53942ff5de5b2aa32245edab1e5161801042081bb47864d53e5f5f29b4cefe7caec41142712577b009565e55105655554158555555418710c2ca21845e555505a5901e315362f4781763f1c8201674b1c9f90f13f16e07ef38f6c59ebf0765729abb258d59dd733def4cbde3dedb5d666ea6f6294dc8904fcf9d5f7c627ff18d9131b311454fe5400759bc8ed7231d404942c6ef783dca81174a613fac94cc12cf352d41afaaaa32b38c12cccb41bc5275b7bb7bd779d5555579950ddddddd3adf39bcd3aebeae657a2c69df5a6bae8fbabbdb7fb835190cb0a972483de7ddcde1d5aa0823851951f4b839bbc316b0df606bddad3933b7ee6eb1a9a5a0f0a15c7b2a1a79ef39f7babb1f15f89c73ce0ded33297ae8ea8da21646551ee72f3e1904346f4751d4a3de93cebd072184ef3de7ce39e764b0bbbbeb8a0595d8b4cc4e0602eccbbcdecdcccccdccbccbdc5a63e65edfdde57d3070ddddfd7cc21277c3f51f6ed72053e1999939a69d5b5395e95957bfd8d45aebf6b0a0d69a6badb564ee76ceb9eef6d6ecccccaddb3a397477773f570a0ba2562ad6dce291ad2b1694c3a65e7f6e5558d0934364ee614614bdf36f6c65a97573d75e74aeb5e65a6badbbb5f75e6badbdbbbbc9b9fdd4e0dab4b5d69a3f574cce22271573ee5c9b8c116c823e6129874fedcdc53746d6ceeeda150beafdf6acc61bc3396fcd2d8805393944b6eb199b9e53f1614614fd3e77cff9f3862d60dfbde7100b7aef518fffb96bcd397f8fa27eda397fae796badb926857a281384414a18a090d1e58bec150b1ca6f058c090e5f548075dfc34fdf3d22b1630e0a0e754b97a738b47065db1202aa41a6f8c0c6748f3ca2b295439e5cd6725718438a7dc3925a940712092845e4199c3a6b7165f3c32ca17830e1dcad9a042cab5653236656ca22e3651bea4413e153de50d836ca21c480b2e5002991ee5608bd7793dca81942f3a41d7b754171bb20fe5a49ef849c42f81b41003480b2e0d2590165cd8a44fb9bed5fc940369b18592405a6cf1e7c23ea5de79ae0fa405186ff1c880b4e8c2267dcb7a62b6a7dce29155aed86c5fb1f26916bdb2a9a288f8a15311488b30d8a40fa4851841405a6ce153d1eb33566a1fd83bcf5fef4c38e1cf920c90165fb0e9016901069bf4f52939f781b4d8c226fd1f7e4eb6a8319502ab5dd9312b06beaab78b47d65c63412d87c83aae2fe6a4e5ae2613a02f053e35e167665f229b5956c32722fee65c7a6e11c8fa507ff9f2e54bfbf5f62b71866c6b3977bf608002c0bd7261c5150664d61415b4155b7cceeb911538782be65931aa3ee67b72160da9bee37b3e295f399dbcb28985b4535878f996ef8967d72b44b1dae77cd68bef3bce7eaf95e815a27c2b21c093a36f7944952608e1052e9cf816c20b5cc07cc72a809c93461ac6a5578842a252020d03c888004f8ea0105ee0e25131c2318508591a15231f56d0032aa6f0cff526348c37a231062ca25883911190ca27d49d28c1410ebe0045194646404d1acdf695b38987804c19c6e0833d3c31320252ff612368bed909176d534fbaf9b27b94f376caf973ceedeb911555386fcd294f6b662eb4b706d547731ce7fbfc38cef5c88a19b46edfabf4ed4b7943efe7edf039f5dafb79777b7bcf0a295f62d3f3e69e8b8d4db0357f91041eac0a648ce1a00c2359052ef5c13b074b8fde7973eae3458843793f3f0e25f508075bc240049b1c0ec8f81e36395f4cd7fb154b1cb4fc621580a5f6516f9ff621b9f31eeac39507badb815e45c83bd0333e4d1bd83e25de81ee8e70c0e52711efdc467dec3b0b460c7abf1ee1400c5f6213f4a1fdf254d0507b551e48458735e551ffe117f79d7051e3de828c67c741958f6129d4de9c6bfbee73f64d40a64737e8f2edfa8c29c049c516d0ceedec3ffc6263d373c8fe1423d242fd82ec7b72b63fb728df37db295f6f5188a2b021cda90540a7e462434e3c29011446bfe43993a27f726882200f7af8f24c544b152a50f0953329fa4a3e392977fed33f0ce374e21b3604cae9c437a7dca2bce10c69fefc7973c5829e14723ef4c4cf0ef26473c680905e95673e778d0535576c08f5cddbd779bfbd1edda00caf6c52e569c5828aa87551ccb090646fdac1a7f6ed597b0dfb2cd0c3eb023034b05e8f6ec0822fe558aa71557595b074652e36b51acf02366ffe9aff6b4e80cc790f982dc1a7e745f0a9fd730a50f1cf31c03e0de8f5967fdac5bf1edffc87ab88f2cf73f8c4ffde7bcf55e74921f5c262505105142d4a24d32328471f9800b25972ae71bf5404309e3dc7b4e3d95d2cf5eea164e54a0e163678964550810e1664b0c1e2c90c2b70a08184192cac80315b09c2f3cdeb51118680b1c0722fc0b8dcaae2147a5e393bb7f89cddead6bcf2f6966e8528af8050ee60b5556b18665d72c220528cfa9890cab7f295b372c68248be9148e524392ba1e71c2b6fcd15a384dab5cdab284479d5a817a53698155dfbc0b0d87964ea1c557754cec89e5ca1eb4e767db0b89039027ef8d4ed10d0f2eded3dda67bfc856cef59e687271c73ab9a3874f2b658f49dfd43d507d8ab698220d5970010d46356003507ba63e5a06165b68c128082cd07205a8bda43e0640820eb410010f5860d102a8ddd587004673d88210aea8c1152f00b52b972960180cac766acdfca3adaeeeaaebca6555f51d9c80e6680c4380af475dc0c310b43c7b73f629d47c9e4037810341794513d29c65737df66efbde62931837825553bbd6045bc073aa3df1bc2a44fb8bed8b09e96fcc72497322f8cfd9c98bab3c6db5d5d630cf9f1cea6fcad3cdc9163d6e340c81cb6b6b150d82b0071504e10441a042082f00828b128c6106170c360b2860d8320413f4e00939a0d802042880812bf010c6d11562c0706bad41387fb882cbf55e8f9f1e45f4681fd3eb21a762e1cdeb5f768516601a34998a60d346f1de7befc99f1e6cda71bd62394215debd1e75218b1896eacc4cab0a8785e33d28701ca9421dc2404109a2d803171c62f8a97200051bae84e10628ac808b34e8cce08a296f6a96ea8a2990e480450a585460e906818b6a39a27d7eb6cc4c0fceec3f09683ef508016c5aa720c0a61b5052401ec12647c92198a3fee85121eef0fccc4db6cb1c9ff0464ffb5c3d50b51c018177e5d711c0a7f7627005964a08563016a0600858b0000428d0000c5cf8e10a1424014608650801ca5b4265056a6a168aeab1bab002e747b0699d73cfd7df83c20b25700db64fcf3273114515387bc50203287ef6b87665e818d0200b1da6008615f2600617482148e148085440404f8f9ad7a320d0e1ad572c52b04212a05073bbf31572fedc79db38df66915555054655f56c504ea8bb1fb16cf1623122cf574ea176f6f75cb11672de74e562ed8ac122b8ad858ba3d25a12625e8f80808246835e7af4f3c40545b8d2e50a25ccc04a1662152c7e4086e756a06d88a20b3dc8a18a28d4000baf22841f9ce19900075214010c528401873c74e1061743308ae2e57a3d42c3916b81c9d43e3d660f10a0a8b8c74f8f22d8b43e86a558f0f2b033343658ed597c4abcb3aecf67fbd51ae413cb8b4d0b79675b93bd5a7ab7dbdb77db2d5cc9668fdf1eed636a3e7f1003bbab8f7d37f5389fa6dfc1a6f51ebdb3ee28b5c2afcf1ffef9f52fea8f05a810ebef0760f8f7cf618d9c49741e09997e8f22ae963d3d8af841001618b8e00764d8d6b28f3384081674cd39e728caa773ce51ce398a72ce39aa41e79c7bce3de79e73cfbdd6f36a3ab57eddddedffde7bafbb5d7753cbdd989919a6b17639eb91a80ac28a223dcb5d0d6b188ed9ec523e75b70c0c7659a40ac6c0dc288ffa4f666687de4ead79f94737af6b4ebb9d7cfed34cc4dfaebb1dfb730a0b72ddcdce75777773ce756bedc4efb52fd5efbdf74edcaebb9b4fddddd4a57cea6e78299f5ebbeee69ecc170956555555d5d47992e7b08954c32692bbf2901cb2c939e79c73ce39e79c73ce39e7a073ceb9e69c738ec4a4162dab9ddc7b569c24276141fdde73ce39e7d8bdf71eeb2c75ad956c3d8715c29494c7c9536b7eafdf6386eff57befbdf7169e5ab3bf06dbe9f10f2a4ea2f6edee313333e4062f3629e5cc0dfa74ceb9766aed5a7bcd39e7dc73ceeddbaabbf9458a9d7bcebd76ee39f7d8b9e7dc6b90b44678c1523eb173563bb5e6e5f6e95c73ae39d79c6baeb7ef60299f7e9447bd881efdbadf7bfd5ebff7babbbb75de7bafdf7bfddeebd7ef3d2fd55cddafbbfb7577bfeeeed70dd94475bfee7eddefbd7eafdfebd7ef75f7d3d12162079bd4df7befb5a9bbfb7577eb58edd49a977f74bfeed6cddddddded6c5a66cea1dc724c62be2ebe2ebeae6e6d45a2fee7453ce5a7d6555531735744b089f21d6ca27c8b90ad9bd844b9b389726bb97372de7befbde7a512c9adb78e5b7763b78f99e173148cf355e579937ff092c5829c0f6692e38a99d939276bdeb6536b66fec1566bffdebbd844bdf71e33cb0bbeeea13c94bbe7449e0bb17337b7c7ddeccbed7193667bfdc3cdbedcdea31cd5989979c93e8c334911df41edeccccc8f99297d563bb5e6e51fce9dbb7e7beadef6c712b2e9f9be6efeb675fbf75cf7b5f75a77abb540acb01a9248241289e4ed44fd1042087777174228736ad8a49ead7abbd8a4edd49a7ff9072f376f3e77775b846cd2ddddd6e25cafd1eb44d6673bb5860b9db7652f35a165b5536b7e22e61fbc2fb46427917b2cc8676bae35d7d65dabf3e68238d984ac11dc1963145144114a7492e87cf74622d601ba62bdf9b60e37af143a608723306829030cda00061b3847a8f2ebd96afbf5a32348f1e11a05c4a00439b822062d9e1065073e58313c32a6e46241975bd6fe906a1ef19687e12d57cb31229a87b75c31215a86b7dc927b45211a06cdc3be10c5f2eb17112dc3af2b197ee5d07eb9e2be458aaa3cd0190baa547758569cebd0ab5d2cc87228c4f2950e89587225ce7a23495513f4954210b63ff659202eb2b6e1608390ad3fea417fada749a1c00333d8c1080d4798011072bc7d1a1033a41ef37a17cf5e536a1f2579d0e20e5a56a0451aa20031cef30ba0f0cceca28eff20ab5967c5f2822d7e7b5c2b839f482a00f2832a55d80296268c9105a8c637cfccc085511768bc726174f4d72b174630b0c1b71b28b02e74810a1a282a2f40b1a000010575b002961fd4200f35b8e9021cba80075e2abe7df392638912836fbe834fdb93063885e6072150c219ae2c01095788918312952750e9820d6aa81ce10c57a8410f5820031b2a50a8d0402606574a7087279801087140821168f8c00c7388998119b210421954d8028a2c9630834a16585a90061caf473ee0c1cf783d72039426342aba10842e1061e6f588ca171b90a1044be0010f64b085156a5ae0ae63dae1dd022dc30e34788309a8c0820dd440e3032d5ba05a0a5aaba6b0b3f495152ddfbc864fd637d752e9f5e80b3b5471810b82116568030fb650210b6080c30c0bbc7837b78bcd4239fb8b42943b0a5eae810186bb944fab65bf78bd9ae7137aee9cb121429e94d6bee7f0b53b2fdef973fe84d04e6df194432e52208477fedc92b96466646472689c9cd7bbf6cf55cee7559c101ba2efc56042a0bfc626ca5f9438247faeaf184902e1573659fc3fedae924e4f1109808091244e3c89524595cd824ffccdad9a8b54526f17364451b0c5f702da9d850979ee324861422035e3523e09c18b7d66b3663e6757afe29016820d6977de3021cf59ad79b539984b0b5fb2f8f6a237e2c13be7137314627a42dd771321cdc88885584984046d9d97504e048114bdb79cfccd511f52500ef553be00271bb50dc5d21d6dfa573cb276fb16eeca5939d562f31b3b7e1d12f1ebcfe9403e55555579f3169b3f742e399f59d55ec9092525fdf5f9c9099d9cd977ce6f739f06c4aeede1361a32f51baf473d88c1cbbc1ef5a07514625eb1c0800e5a7e7e7e10d0d323e707010930c2c6eb1111eaf0fc8ae50427d0f2034b75b6c8bca77da00ab688810fa280431d9a00862b3261384323c1a02a4214d994f7de7bed3d3047566ed01e0520c0a63da27d9cfb387904470134739eb17c61e5990b28c8f0cc7e049b588f544b0dfbfc24804debb326e7f7c9fad4a35f3f020214c0009fd8e6154b0bf4f034af474590c24ff85c5a572eb01e1b1c1a62c8b0cadb6c522456105d5f5d1a53585eb6290237378d2aba7523bda0b9e69e0adc738f7900e55d7bbd1eeda08c2f51d7c6b5c197d804b5065f6293ba4ae5610a1b5a786d5457aca1f2b0d71063e33eec6824d3a31d80f1f37a6e95fad80116bec4a655eab9d6bc104695479d17638105958643d52acb6a4a390d0a194f88c31eae70832b7c90830ccb196e20a1ccd894d5c0a6595653cac96a625e8f8a10c6b7d7232364f13373aa4fa0a4e79768497a1e5271ce91a83f480e69cfdeb60b4628e3f5c808620cf17a6404257c4ffb2c15bc5cde6d15ef7c960adf283fc87adac79b9c44ed5a34b9963aeeeddc5d15f7099b9c37783184f2c457d51a8d215ba36fd7b7c80f634d1025332ed9dc28efb4385fa37de2bc3d6ff03be6b0c995b864b3e7ddbe73237c6a3d32c295774ec4a7e6ce9dd81ffbce15feece9a959b4f8d42f1c68aa185c5c150bc88aac7776dd73e79ba4f4943b1ff6b9e3d54d5c4431e5655eb9888205afd80c2cd79834402bcb74a05ff8829730aad8c216cc4087981e64d1832e2ccbb22ce87a8382222d52e0458b0aa020450caa00d3833af8800595e556154d6caa7cfaebb069bd7d48beae59fb90a4ffac7101d0ccf9a6f30198d28323c0e1054448421d809a433e61df8e7c20e59bcb2108dfe44cd263da8dc1741f055aa762a9864deb2e664f5c874fcdb7a3b3699da30eefac2f1599005e8f765083a7f17ab483273ffd29275bb48e7282a5eae3c6f9f461961a8d29d323e6f99cf949fa1f7085609f90f269fdb856087618e5619f7d826d7fa80ad1827876929cfc3df687ae107346e7d92b39f9678f6787d1464dec1d7fe89aa68d67d3c4f10cd7a2a13cececd7fea85921d8d97fece8c127555393f3e6d987675f39d93fe3d393ecbc5f93eb1416d45ce325ed67f62c85f4a8c330e59d5bdaa31480b9f3ea33bede340567f65af3546c11b2a97915493103c2cfb2467966e84e73226ef094707cf399bd0fdff6c7b64bfbf2092d9fd9df9837de5179fec3a6f6a140ef3887979c30b34cbfe59d37394b1605da2704bae31c0a2e53bc73eb8277ce57799c73dcdf176453b5bcf3a964c0d4288ffe117c9a3320c0a729f44d4deb55bcd4b40e2354d33a159b9ad654935d40e0b76739f4c4b76f526aefcc357a824dce9dd8294eb668fbd9c0521d664bf747bb74f707437129e563adef3216443d48c549f13aa76207b20ed2ae26b662e31de78a5527ca1dc99d239124113f49e20ca9bcbd6513fef5ca2d8ed38de0dbb992aba6e78b0969bfbed890fecaad6d4f2c600951f6ff7c5588e78c09e95fb778a8bf94f195739cfccf37364c081b7d2597544e045f9587bdb1a02a36e1afdc525e6d18d2dc7e921cea378b1e4a29b2e6dc60e1905c7ffa93dc22c979c4535eb5cf06fdc5381fc9daa08a096c72f3cf553e49658ae65238d79a3b75eb4d96a47032eb1d752964a97794a57a916b5058aa33f7e89b6f955728304a2645bb402f4a8672f9d6b3581011385e0346cb9b7812d193240946cb497109e816c9ad38042184de91249d1c11710ea18b1347cefdcbadbd7060531fba2dc8a64c993b2588bafe15f1cd6bc40b7a8d77846ef1c87eb8fda76bdcd2abb1a092c358f292370c488d9766cce4b8b558d08dd7f069f3307be8aab309a27fe2d7a8779a433923f0d1e711df3e21f0ddae7fc91dfb3bdacfcb872c9f4fdcf0d97eb9894f42d04d7db90dbfdc86bbbb0d97433e481d28b2e6347cee94a17de83f8c139707cad3dc822d2fbf66e340bf2c877e794718673b8eff74431bfec36b64e3cd6bc869f3738d6c6cdcc9095b4f279ee4caa71cbf71cb2dc823533639a22779130ad027b975c493e4d06cd2bf04c99d5bae7fb9c721929324c999143d49326927872cbfbc864377de4344fb6ded458aee8a05b90d771127462a32eeed4c8abe25931a32c90dd9e4e67d701b52d53471e4900f92ca1498c3600e037ba7498149189702f3cca5889159ef349771293219e3d6e53ba57d64a491de691e23715cdb07465aaeed83c91b572ca78d348e9cb0869cd0923323c99939394b50b9cc9d7213f4ee90dd780f9b9a43572cc88df7e8374676e333bcdde29bbee19b19d978077a2936e555d4b7894b1c8d38fdafeb29ca6be24c72bdf39962c6a5909991c160fb4c11e352c0c4c0c064ed3345e652c46431313ee3cd4bed33054daeeca3bcb30dca37e6008a4b031628c87e795dc8a037b7164b80bee5442cf9ee90e10ca1bca24205df3cc87e796a4a4bf9e6aaaaaaea00b85db87cf3dddd5d0750fcac7c6bcecccc0e784d86976fdedded000744f2a32bad39a03907e59b3be7807e5a648539808148be259b4d8a031688e4d675591891ca29b72e388b9e9254482dca37a7248e90ca290f52c92dca53f43f1ce4798f808c6f0ee59b4cd2a03c03e5600bddb68a60a9cebca63c5f6ce2dedde71d1b0b6a5833fad6647c9bdda5d45cbe796f691fe74dce240d8ad562d6846c2f3671a97d9a94de61af9c54b2e42c5da576c4ce580318c849cb4befb03729ed43b925a753959c196c529eb7fc6cd9fa4fb7e6f2134a79bef8a4a441f98cb941e90665c7c8ae298f0d96eacc1c5e9ed2f26df190597ced95dea1dcd5220684fd391ff189ab406df1cda924e889b35047ead3c39b5359d46700de9cbaa23e4ede669b1284cf66a43eae4b1ed4d43e1b8c6fde5fd09323834d51d487086fdea4a88f00bc7973519f1e6fde5dd42700debcbda88f8f376f32d4878737ef2dea43086fb3870ed71ef5519faa79a63e9595de691d6fbeeac387376f7acae1cda19e4cdefcd21310dee44c426979caf90a9bf8e80e195ff9de42f2119bb2f0a93ae22ae8a97a429545cbb7bb2eed7371e91d32f874796193cb839eae39a82908e9bacc67f4edd594f6c9a4f48e95f6c9744c791c95956f6fbe978f48798e18849b220e0722e2f0f73653f83484d481c39beb70abafea884fd995ea097aca96a0a6f6ec28cbb75f5cda87664bef78e1134d976b0e7aa269839afae2322f32be3d93d23e3651b22aed639349c9aac49ab75a6bef5190a2288a8215849044b22ecbb2ae0bbb1666b5616c74c1306ce990c15c4b876ce244a14396b90573e9449cb7666af094de6997c252d8ca0c8c0a57a9f2ed5676e9c41bc934302a0c85a1d4d4d4cc94281d4d7a99b974a2cd5b3468dcf0133662a3196f8a520ce9a506169443ca2e65b0a97dc920e3db651741320b26e4c923e569b7c18e90577a0773c56c70b16e2e22e20ecdf388528bcc226564e44e5c2001c4f58251d92e5dbe77a202e950ad41b1c11ac040398ec8a82a9256241269a6479447f28a0f3ed8b821e546141b64d8f062a38b0d2e36b6d8c4687303c78ae344f101c4d5b2597a470e9ba5b2b1457de0709c28edb37395b89472302abb65b7d490b1bab0b03d3abaae9bec7bc41c56aae4e454507e30da02b3050cb657aec4c4d4e091e1604136bc688ece348c8d2e6ccab978643958504e17363565230c31d8d4578ed85c27c6bc4f944757ac54519eae5103e78651d699cd17365bd864a305270a0e15ea05c70798b789d2353659fce0ca3e3659f04efb0f3e8098a347cc718a39b225645766f8cc8e8c24193e0188d24a9529cad34eb385060db7b14146ef74a9e4ca3e36b8788b385268b0204ae2445921380db655aae0b0d172e335385170a2e0d011378b9a1a6cad58c9c180f0db01e7ca1171b84fdcf153d4e13a11fb00447924af482bca23233373038a8d30d8d46e8a36b8d06034d8c48972f4ed345bdac7464bef34cd966f0110c1c3468b966fa7a103475c1eb0a99d060d6ca774f10103c22fbbd8e1c6f5a64885c7858839ac284ff3c4c47843d813b784515923231c3678e24ae99d761b581025312a1ba5ddf2e1d289969487f9214aefb4036113a57dac097f905d62f540c4244b74216ac2cb134eb45b372e9d083f4754c248bbf5c3453d10d18804b49b22048ec81131c0a6f61c18052c9d6b88b8847fc889d10fe1168f6c8737c7e13a18111cde3c88f8c6c870b8b24987eff020fc87ad20220ed7dfe13ab0a01d72880c875b7bedc0a10333a2e871b862427048226463930ed9d3f3a38351e9d1a39f4dbcda0d4607e58d721d38ae46e1b872441dee03107358c951a577da858839a6288f4f8fb7d3b4a19fe4ed345d48de964f1a2f928cde69ef8936c2e09d0e6ad2061738643b6e60a631b2f51c56d4c78e89134576699f1d6f3f7accdd06171b5c5c474747103a31e775e8b074ae1c5187bb080037c5201c8848b9107187ebc4f6345ebe71a2e8d854691f9e56bd8ea4b233e52b0740fc014aefb4f344e9a577da71a234d9800a6847ca2e75c0b103874e10140e2c48070e1e59109e030b4ad2a0bc0ecf8105e9c0e1b341790aca377f4e1ca1bd337b4b15df5c75e890e9cc27a47cf3f9c4946fb3bb7c108e23ce240dca531e440e2c884927f69b22951d0762c76da2b48f0d2e6d33c586077c0ac2db6daaf04987b7db08814f948e4d946f7731eb9d76c5a8c0de9143b6414429069bda7544e945d2814ded36b874ebc81c1890f673d8d18151593964ebb384e3d2891c4410392e9db83b760071e9447d53c4a1824ded40441c296cca2171a2c421e3e236c2601f2906f360414dca2e6c6a5f2fd93a006e4a363466d4d000312393c598726070b84e24bdce741c519547a7c91f6ca67cbb8c536ef9f618674f21bb11a7c9469c37a67cbb0f71ca247c7b4e9c8d05274e538d387b0a373851bebd1467fb6993c5b7dbc4e934e26c2c33e2f49a386d8e666926ce1e99382516df9ed9e0327b0a3f6597d983c5097fdae0f2d08ab3942307df5ec569fa29bb643f6597676fd810ca790b3ef1153e7af0156b549c2b466901baa2492594966f6cd21c5e5e7fe875c21803c4e0c2ca936f3a7c7b0e174428e3db5b3bbb2bee9578e58b8276b5582158751a0b0d9a003eb7440b28df9e917174842e3fe333de6ec5d4382ba6c65ae8d4c2d0f8c388b477e6104ee8336e913022ce639cc280143d8cf1197d9a3829b77c264eca17e88a3551a8aadc79e53171167de5ec307156de228e906eb784780d9f28af714b7fc6691cc61a9f89599c459f79bbc5d7e5397cc27c557966c633a1b74c4e8c88738811810ee33f0d331865e28cf1e659148a719c21ce63dc798cc41902e395c3c81c2a50a23000906a00485814ba64c6a628547915219ba6734ad6909c6c51b7934356b9624778a128b9ce7276762b6aefb4e6c0903d578a924df8294751158c19253fc0ef7ca8bf7cf9407b27a9c651f080ce0b7573cb5db4dcd4c9e43ce233b72c57d6f92c4acbf7e9bce5304e9d871042084f98b367723a1a2a9227804f938822f814e399b32340c63367272293892698a8ca83456593a9e62dcb92ae9d14276cca71b64e0a2ab0e72dabb59e1e397bbe61707d609c2f3fa21d3b4c7c71c3c86096917c9abec55943e49a5b1b86cc397faebafb7c679d72ef5c0d203dd0f342ad5c5e6b65587037b625d2539eddb2009045797fe522075decc0e87b5eb9d001169e8b2358e141187f5dce050f54f0a6572e7610c6efe08bb7de0aa2959c456ff9f3cb2d1a7f951331e9c9c0640b2f1a2bf6803272421839b33d2ab9b4dc91e4029c53168dcb49dec4d81fce49b379a9e1b33d97b79aad85a2654a162947519e7df6780b4aefb0571811ebafca2b2c880ebfbcc427f59c4adb1b9a972b96b0204be640997066b3347d9aa03cfb1ef98e9b1c08cfe1385c479b188d0e3f79fc6a6fd86101804810009093bdfc453967c1cb7bccd0c8c06441f8e49194573e8750975b8ead00c06d08e1377adc03e07108973cfc076d624c1e7f3e9b9727b98e38f775788505b920a987f390b36708394d301e00395dc67be42cd1b8107266330e8029048f04008505b95cc8e2f18711b9fef21ead3d43e32a63398ce5992585900b009ed76e0af564b5473b3ec3e43480709c1c7e83c36d74bce42e363176449207116bb80e1deea87d968c1d39a149ce0c08394b39e4741c7ba4b347cfbe64b40f5bf941ce9e929c3d524e9329cae9ee72966ec8996536e49261cdc809218d9c59562a6510be9265e3d4e27846c36166b8cc8cd340bf7c562539e4b2fcc959916c6e7068cc98612bed4323674f8f8c9c26138c9cee999ca5d24a2b94b4f27c2521e22d9f4988983d4f5ae74a52be3d2a8730cb4aa54ace2444fce599acb1dede703989e4ed0dfb439f042f08af2bbab7665ced0d106659a95455b3a7874c424412229210f130323dcf5d7a877d96dc347bda726a616424110a86bb64dce57117ee7271171277e9f9e6854756c36d70dce291cdb8459344e7699cb1201ab72a27c5ca2d1e590d9b48a5cae291412ce8826c22491229cef875e333ae58d00d4663c969c4bf7ae2abd2cfb81589dcdf346e6dd5c1585e72c5822c1e99cd0cff611b0c08f4f1c14998bb981acf5cd927c7615cf934777ce53ec41ebdc39e136ba2f6ce53c7a2f60ee55654e59191b3e62bc78995d788ca26183975bef29b5899323973bef299ca69c4525436cd883672b68fc299a99ca527a7ffecf1a69f3d7a2eef2ace4684904bb642d9deb042382fe5687b43f3524283e99d5823aba43e9efa703f00e5619f37ad0b51eb6632d854521f9cf36ed5a053521f69c8980c3e4db642073ecd243dcdc5e002343e5595b3b3158bba4aea837ae630b2997d4507cb9994e5cc9aefa13cec4590e2acc948ce979c4bfce5a458c5997d55795579e556b45e18da9c9dbd58995724777ec524a5af2aafb9c0c82c974ddab3922cff6117db67318997672759243a3cfba2219bdc85bbb48f11edf2ec93bd3cec71c5154366f9bcde923571b6b79e932ce7973fe7964fe89625dd252d399df409a56539cfdc9272ee15277fe6d69b44add810e759e6165f4a919c82a9a8607e79e31315280e84f9252749ce85c188586fc56041649c44aaf4adaaf28dc227cb2b6f2ca822c50c0bb2646393564e13493e1361fcc54a52804d587c52e32670a018b72ce8975b15153f6351b03fa05f42ce2d223372f25b4e434e7eb751feba1c460b3abc3087ce2d877eb962432eafe45ca2c681fe9c647925a710cb2b2296574e013e91fcf285b23f2abf6455cd20390d16e4cabc0623723de9cd908f5443f218d27b3e83e43448cfedd15b5574cef9bbf65088f5d97317170a4d842e13f567228c5b7dcdca5d7549214bca3c49013639c7e293183781036155e5167fe69388a79c0ac6575ee3d92123eacfa410c92fb77c566eade58a2de0aafc49925712e7b97358b99b0ac643f9c6c82651ffe5ce05001d8b8d0500e5246a9808c64fe7375810cb852e09bd0646e47acb929349d1570e298ad27fff6608de724b9719668ee02df7965744dc5bee2dbeb47d9a57be9810ca2daf2c273de5517e12f15b5ef496cdd5245d86f961eb923812688eb9039a63b2e8ab38617b925b579c953ba7a0ff702c7a52d42d5f11f5537251b042506051b03ff4dd4679d73beb1d6b7a0a0d27dc9c2c74b2456c01e5412d9285599765d5bc057d96deb262e21b23836e798c6f9cfe3172882c26b6878958bce26591620539874dae897bc6825afb8dea9e870cf00516c12c87b145ca97bc44e373c73f9f3daf3e337ceadcf8cc7975cdaa29ddc84c6aefd0c4374646e3303030b1d1449352aedbf364d407e522f697f5a4ea2d775111bef52e2a526f511715dfbbb7aa7ecbbaa8c86f5d177551aa3cf3e6d961dee22b531fed3be7619cc66bf844e3fd68e410d9a389ed611a0b42f95053dd5fc3a71c36f1e7f0a9c6dbb319398b7ec661e2d479187fafe61f8dcfd2bf9c7ffeefc1b88d63f18d91d1f873cca988c921321a8cc65d79287f11d2c838c482c8c8c89c3364ed242c68c6170302e334fca76762c96184e83c0dffe11a3ed9784b253a4fc36d6c3c475299e10f9b512375d894a90ffdf63d4356c3de9ea90f8a1dc65f9cd9bff634f0697c5e4fe334723eb79c8a337bca2d99b83ff31c73c582c44498388b1ec61703f29ce43f0d132fd9a43d09632cc892436424d6b74892a87d55c326f67fb149d84eb668a691aa7074042831f85695512abe4b398bb9d89447a505323dca828c7f3dca220effe48a751c99d7a32cb800654646464640ecbb07cb6a6f27eff8c3a649397421ea63312094439e45c00f9bb8c1e5236c71be864c1f3ae5950329fa1edc2ecbdaab41c711d2daf76be530f22b26a4728bba5cb4aab8a4b2aa0bba4572cb2debb965412771f94a0eddb0e2e4b7e29bfb503677a90f26fb9437365dad8528cfbab5ef044a7ab4243d7eb9c716babb9b77db17dddf60f7362fb4d6f44b16d7dc77bb5f6f0668dd2d4bfbcf92146b28e695676760e9bebecae6263e95d8739c8ae71c366dc6a667bfda39a425a874aa96e973450c3ac790a1119a49002883150030301c128a8603124d54541f14000f93a65c56210a644994a3380a520a19c3082084000018112198216d00c95b065e65fd3cc27551dc8222c2552c37d1aac814fe97141a819b4296c99a44d0c5570b689f0544a4bd23e18e8d1de0f2f00bdff97c3598eb29760dc5af787fd2b9959a729a1acd1aa6e45a94c3c7897b6f483a0f1010f2d64052fc3a4ada9e639b0da1e617bb9b42bf2eb277d1acd271d20b9556c18b948106eda9a18de22bcb0a15b194f06e10a61de741ad0f151da3b048c1c4b43df4099d71e353bb50854bb6cf3c9dcf7e3dd9dbba2e8e9e73fd634e960f2d54b9d128afcfa2206ae5474fe07b6aaa7dc04ede8eadd8a4fb2dfc22bcc7b6218ab771148e8007c92b763917a6835dde339e1a1609dfc4fefedf14b48fcae013c9bb39db61c14a2fceb3ee6cc2edb17482c4934700f2c4866012a29df3dc655ac9d9b371d56678f6fc5cda5524777c83b7f612634608a35ca2a35908e8fc83ca2d01143801a4d3707e66d119998ef43a1312ee7eaf2fba9167aec609dba9dd5b69ade8d889b1a839e9fc82ee392bf60daa51b89da1e95f00ca90a99f8f4508c8ce0e3af75b18eb669a12f06f03f340ca157ad6b61c9d5f6e5723f2377c8ca82b82698eb0bc647d64524177f027205d4762662f046bde317b77e2c8de792c9c6a469dc80f3c5d50139cc255d437e030ecddcbed9035a3addc125b3650ab2e3127aee0be17362517e2322e430d31cea3ec7fb2cab225bfd434b3746227fe9a3e9b94bb6ca7fdfc697e48d7387074b893b61d2aff82fe7c222705bc7ab362635b97278c83fb29043f44a20edae8718372c329b64f74c8712c1ffba292ddc8debc6a72bd8a1d698905bdd374fc6109efa95dcc98428ada3c5b048cc4803df212ae7fb7e2a93c2e12c65092f4a2d0113a06942f67f231246d795d697878791937e83ddaaceb8ed7c5a764eec98b6fa08a81eaa2aa744fb1865f170681fc61ebb6170689cf6f6f80a529d33d0aeaf7e85ae3f07582c975ec1f2796b593b32579ddc1a57e0cf17cb8b2ba56c660b380f9e7e40decbe7aa63df7b02ce0ddcdf998ee8a076a0bd8db5ddf8fb4cdae74573cac0d542da0509eab91e79a852d465c5276c92f23d3460189e68a863753bd0a73d377d53c552f4059583f133b429434d918a0a0ac5a71c269f1d43bbad42ed93ed7e2a4b3397227c9eb937a2110a748771a822cb5d68d3430e271100e8760a0163db5e10783e00d16663a580ef36761447b7b8bd40611fe14a60fefba6b509d07f98b45cf40b246e5f967b3fd3e61d94cdc46c54d22eef565e27eb21108d8cccda5bc934a2419020efc5ffb40d463a89f09d03b9060ecd01b890d3503c279f1e0ec57dd45d8d3727c45a1475e9492a04678c1a5c7fba5f3c3c79bdb2be78e775ac81bb5299db2f8aad78770bf84080e0d4aded3f782dff3558977ffee8778f6be59e4294b1c6432aff8ddfe8538d7972502f7faf038f20423fe92fbd57f254184595bcd88267cfbf783295a5eb73efd4f2071f87ee7a778299c8a5e030295f31a7b446c182d44b43ba612551e4409543513253198f101176dc37213d22e54f5b7088b84924c0e4364f1fc2c4a1426fe562f31d12f9c7eb48f2eed985b879d096163cc379a3776f723f23470bb1ea7a6b7e7c0c897d1f1eaa1806239a600e00f716493fba261013804acfb6bf72eae4d19f63a8b9d7db623e93af8f9505208207081e2bda73650eedfa9208f9595dd773cf09a5114e44ff43e785eebaf893f1195500c4bb8b2071213617f0f1939ff25d742fcb45e0ea2c246821a9039513c854bb410ae2c2e743cdc2bc4e30f21bb287d6b7722bbe98cbf2ac0a344b0cbd4e955321e0875b00e752c4b6004988b6af3b9e5638161b9965e2d939410ef8e4c6da6326deb54dd8a4882de02961ddbf50d020cc0a0d2dabd2155e43cb6d29033751f1c9ac910250413f763be3221926e8c201d13451e81c79de799e9ede865bb874f062cbbcb3ae51221105a7ed6b379ba2cd2962cdfb7e1bb0c622d8e8cc88ada46fa45db7d59165720ca69f97b99c791e92ab4508c1ce288290fa1e20367841ff2ed32b8e73c94531fe7eef51dd680000716e1d5d8e881c742e112f10b24bba22972bc0005a0ac761714af9f24629c5a6e3feca756901ddbad75f8a42eae95dae7fa4c61e6eae4e89209e75cc028abcb396d6c14b75d1069d1c70443fc2065093e1679e0422fa20f88b8641992a139f4c956587c48ac870cdfd9699b6606ebe52762757d222a2b0b526101fa775e9ff5ffc7673a13837ae31461a5e4ae140460a6c4c7235097ba548b8f43e77d49c1c5641cf8b9f888adecb8bfb01ea0a033a7d43d3ce4b67754c5bc90094cacf3af48cd2fdbfbaff6c77313502ff851819791bfa70c2776341098c06d3ebd61649ef8d0bf317734afaf83a21163073428ba0c2dc3e6e888e81201972d544e0288dc4ef4ae3d2e78b00604f60b6c014a38628c3b958647318ba032976c4ad903af44a2ddf153fad3967005d58e846a4091ec827d20527e1e36dd76f57ee4c8dcdde9cf8bbdebc8e78e870124da16d1819f6ac93c01a58e456dfde329c5311a1af599a1abbbef95024f36a3e3aa600df54f67364b7c7f457c143450ba0e448af44558f3059e0df615a595d11f115be2b240670fe442a47251fc81ef497e3d478518a30ca0be474fc1ec000e6044205a2312b69c8c011875f496a8e316cd95c513dd9a11db7619904c666dd14f8ac6430fc82c896d95169f408364e923758985fb4692e6a50865371d35deb7798acd8a4110c96979ca6a1fc0cef3f1429345fad6c8fee0a30eac32c72b7968b8ffdc8ea684b5120020e05cbb77e8e28c3c4fa8582d11594d7f60ba1fe7fa2606334069c82843c748909e34e6a5dcd36f5219053ef6227d229ef220809aa18ff7cdd05d7f442cd688e8f46c0dd5404639b7a2b5dcefa5a5c044f68a48d35a4688e51b7681125b3055f971aefe47c648f5669c2925f6397ce1d51ab2645e2e0dbb87717947535e30dcc852264e1bf4cffc90a1a88740685b008b7d0268b577aa56e8ece9ac84962a11c4621e0310f14420da3bdfbe2e2ded15bfdb33c4f69f382b4ef740dcab7c03531a83518c56cac97d60cdc519870c729234911528c866e2929b33af148a66e38ec44943dee2609b72655b35315f2cb43625aa59eb9c70eb7c8cf617c9684e51c933622f87c2312c695881800c2211b9ffb4888b2ce340c401df4c087b47aa738a33092ddd0f4f1f4080c634bed35295050684a98dafc419048af85c6ee267ef94cf640aa02a54a641555ec9b7d245ed862ce6931000e8c7750a2520403887711d6a798b42be5e5e86181de50520a24c1e14c8233d1d4676b1fbd5de487d756e615dc7797253ac84d5a621958600a7166902ec23c657053d3dd189fddb7bf59181392c5c9b02f685e1784c8e5dc120027bddd10d3684462706aae145e52798b45f7e7bce1afd65c3ccbe9cd16a14d78611a3a60617e570819186b17c87308c63bb6df3f38cf3f0fb5bf1740c1a8c23b3ca5a3fda3dad681c8459d00310f58276fe0cf3e8501c32b87bc8af436d8c56005daf7897a5a868853dd41c06fe59b76082af400681235a6179a516607fa3aa6ab15074af0c444bdc846de40e746fe7673115a3e49441aed21ace0eee69aa4536aab617054c3f41af2468db3f4eedfdb972fe40222f65c0511d84e4983e7f793c6effeaa93ea06bd613232671ac02f409ef430e9192962a5b9359531609b4b384aa846eec0f662574624df5968013ae8835aab275586f64ac1de2f12e0457a1e76f851de4b015bdfe559d787501785218b923550d87a09f191e2b4f0ad588d80150510b9f6de8bf57cc108ba1588bd5cb9e81a803a1ad1e0b7fd34773b40978fc6e90653a0ea039ea01a2b53710334d1febde6d5765490b36000a5f4731e55a74d03cf3a967469260fc08ed094e51fdb1d9b667be2fb28108b3f04498e27e4f5f672a6ee52c0e8d11420e372aa6907418c18d55b984dec68ea6aeaba7bd73328ab26e9168b6a31e93d7216a01d209fc9d5fcfc051086eac9dbcd8879bc876b724a39be2b08f27ad117f7d2053267a919fc3acdc70002c15590ffb60a2487b0e5a3bd0c12efd5d66a2bc450c03ce000aaf97d2da5f9c0a2ab897a1e029b40ad09ef8290f7c4c1f594d3481a8f04133c49bea27d9aff27261cc3bee8c73217e55d22af784772b28d180ed6bc50d76779be2460b21e8fb10c215f2207ec98e94d07683a5f07396de7962cb9cc3bd40ad8949e9c2d24f1bb9bdd47eac0c4430963300413fa5cb1c18ba4d74b69e5d745de62c0afa8052cb2e50053e0173bcf1397d289c4e7627891144ab5226d35c1f2c02bc2583d77bbb368d11acbb6c2607f8396be26c9bdaa1ff8e0fca481ca0c956f2b97c224f66c9d93a7438a63a80c9e9c694310eb134c3f12eb6197080dda9e6b6ad46b6d06bed7c515fc6107eaf98b59f5a2e80518c2e04cf1248a48b1ae2d16a090562b54e2d8792cb217a3994608e68f9a78b08dc32b0f7d8ae284460667f965a5ff929cfdc9c5e0b3bccdcde0a1fe53c200c5e36099523e4e469c6580e751dd83cb1b2562cf8e609fc22a377d8c293c1bb4ad68efdd634c12254343f635131eafcfc2910e1df08181697b2e9cb2ce11ff651f68a67a89d5afb941516da95854959d2aa08890b91bbfed95c73f5f15b5d2fa2be4d1fcbd9f0193420d196f75141673c311ff6a28d599693b5618e73f6906046f2d04192eb46a045e8410b45494fd992b5397acf5df0f5f3db052a86762845184f604412402924e32ecaa18aaa80e0939d70be740c51672381539c9d33825759df62df1af3a25ac66dd4fafd9aa3ecdfa786a72cd0e47e662969d16b0aa1565d08b81834b7868fa037514cc3d6d4e637922e36cea616e632dd964c622cdf913a7e38cbd823146e5649caa83f96818ddd0097dfb1627125ff0aeb618462c218e2d5e53a912e0c00b157a69d97e01136431f96e2f9b9802a809404ea9e63c73a76acbef12f9ffbde0b3be1085427b4e6456a3ad3feba1c7173bc841f5c40dc0ac0f109160de47726a5b82a7ef9b0f21e6244e08404c450a6d6a88924852574889d41ee98d84b17150d222abf629c99b95e83e72482f09a3ee4a1856e9b9a3b439e0175d4e1dc9dfdb843ee0932e4a1607ecb5ea63380731946e1019d98485f0686e9c194a87972e5fa6f63392dff881624b96969e886c65c5758dbe4dd9a9d854b88d0d31c9a482b322f0bbde7655ed1dccfc59e1dc09f79880951a1ddc463bc0c0e07f2a2f78621c1e83b2a0c8d7ec916927267f652b82cc97d4a097910c89f020ca1ad881cc4c1601d52d63874b36efce62d87a55c238a61ebdffb6d25adac0329ef29688ecd6272283834db18e51fe93f054cd28d298db18e577da81524da2141d0cc9b9e28b250e6b7f2a1be7f410cc28f71f41e64d31782ddebeb2d4a3f695474aa1be179d9c4971574702b334aa00f1d4541d79a87e3fe9e7b3f358f0de1f347264226f9405ba8fe9af2c090c7479aa736cb74621bdf65618fb656774c38b047eb8c783d6fa9d359d74da4fd82383e6b6c7d7316469bd79786341b0db05a863a7af0cf299c600940cf010bc39c201e778b73ec8634f28c478470d9512d9c6d47e0a2e3f9ec306cc3d402397bb3d3479ffafd720adc480529777b07933396cc5d7ab10065efedafd71932603990fc284112916a92dc7d32745a477c17faa0e7de3db592d92c8439ab364e5cd4309fcd34d00d88b8b89d9d0293092a057addb393014f73e529724e5039485b370ca8baad5cb02629974c0adbd11e06b0258f84816bc6d4d9739aaf13d42365da7264cf14d407c2fe01da8f598cdbf5cacddc99110814174f514b70c9e3fdcf92e841d97c95b8d9a01da7bcf61c4a83fd91969177b099488847579ed9315056cd824fe5ba453437f5afcf6a967bfff076c3a4bb2ca150b5401b9c15eabe193ce7156842280a28e13378ce048a0971f55e7139b93a0acc0918dd28dfa4a84c06a2a51a0005ad9eb0cde939c37987c1050c9e0956de52403d37a4e92143abe92950f6d44e4ea3355b386230a549dd59d23f86daf9d6839d78c30aac71ce5e62524c81cedf922e9c76c5e2fe91546189694da853ec5440cbc870204203ebf520834b1841b574d878c48052217c6c90cf2c380a82cb03bb8f81b65c626e0b0b06028bdee617b22e1138401cb4d0e47d918276180eff010b0ff1959c813db1710b5328f5c419eb61f176d54b4ec01f6777cb8dbddf96f7c598e3704fb368b0ebb202c8438f0a9750060df0285a261e348da01d2f1d6f6c9c59af9bcabc1aa4338efe7a06ab64d5543dc20ba1fcff0e4e38663401e49bd4953dd61f9fa8851cf7e30a15a0223caf425312306165c06eb5efac732425de3ee9c51d567565ddf87b2b711d2f97c5eb542f86ed2b450ab189fb8b988749db7f89a2c2603bee732b80c338ceb36cd7fae73a61e1d407a04150cd62bd19d03f30371b91a6fd16ce71340890196b10abd3763bd1901b7743f8f59bfded775b1c7b499e7b1ee3a49e0c6dd495b58e868ac7f3b004be129d8920ad9c701a4a1144d78ff83816b0780ae73324c2ef8a7b3ec9cce297407bed7d268664e14240cc68f4d2af290066440b8abc62148f390175d54401987a48b24c1f30565ea180bcb8bf581de92ecf280c321eb32eb7beba3b5afb827b9df4415682b2fe31146f790b0bd02609c44ab8954bb8e38e8b6cc3a55630c6242ecee2bbc193e3b0d1f7c42707f769c37c060daeb71510c666a09f8b06eda9cac030d862b4c818b262df0cca22dc4a63542c7ed937106a09b54259c88d49a55c6a39c3d34a2296f71014a7c07b571b35c1f4f9bbe428f2ab8fbea54379fcaa6625677ba5dd5db57421b5c14a9e3992b35b8110c44a442caab50d0a2bceb7220af96eafe1e5a4137cdc51271ca34605e10d29ddf134f8088dac5cf58bd852a2d4bb896f8f012231b298602d3125a4f999f13a7519a2fc013e9a53031c0f30f570e0b3377a4bf5f043e232f3de763652e6d173f0a4720c917a617c63a785ca3617a23fd13ee19190d61152f6d584c19900333098b9b6a4e56ba0e4248fd97555dd7292565f2aa10ae56275a2c3eaaebc2e463e639fec58471c2a7786046f5ad9f6eb4f30d41ebc58f04c6c51c35fe4aaf4467d5a9f8759db3d949614e4f358ac0ba591a0184ab5ea7afebbeb72cd8ea872d392b0fe93a8086994b964a8082c50b7396c38425739eaf46940cb3fdce7a20b97f79a8966c9e29dca688c7c9b1f3ae1fdba70b29ba40e7069f2f742fc4929ae3723f607f4ad464182d0b8b43955e1976f11c9a0f9cf8a7e2eaa88cb17c51c2a80e82ca594e89074ca6a62b156db132738ab2b010f728c07fbbf3dac830670b7ec3dba647032fae1d3940e9eef7ea48086e33e02d6b6ad9824d47ffcf70ff09d332b9294aaa260f4102dcff9c3c6944edf937788abfd71cb69f619966bf191db8b80961755007a685d995f1469564934dc7f50657548834d00705bd0210a1ea937bd3524443b7cf00b791c6b8371911ca86205a51bc0117ebb4cb0089845ca0976337aa85e6e84df82ad3d2ccb44d290446fea2a3b619be4067a24ed5198642955080472e7049bea65a1f5ea6b725e319afeec4caf683f65dcf0781433b68cfdb7964e541f9c39f41a3b04cbe3671fa037c0f3d30c485dd45b056306e155b82324e189f3ccd85a2c741e1425dc5e8918b0e70ac6063db82ac7e202e84636166ee5b750d5f3ec43c496a7098b94032e4d9cdc127dc9252328f20617b3ba095de6a0359153b835e63fe830af1cbb8b520e1e55253cf70ca9f465980718e4aeeb094f1ff06bbf62119ef80feb24b66ec735cd3d5bc16f6c0c453805af24f9b6a53903fc9da9b34f5ec1d51ca03482b7f07b23aff95a99ab46619b50f30e6d06f7590f119e32d7bc6e6b6e4570c391f43adab8189ff73b3ca291a251c2a66b297a64ab577462243ea5bfefb60d5b45f98bb9ca476504b3676157e40edb8174c9153426714dfdef2777795c8fcaa0d9dffde27910993b7307a5fe1d626a28a519dd70ef2f36d21d940a0780e272dd5387606ebca7cdcc4e8ef73ea60dd99fdf72ba0b87eac0dd4c3d1a508898855be3f6a1a4beec30a068dc492eb3cd1b844c6dc4e14509a99e980b18b686f318567097c22ecac9907dd88695864545e852c7f83065fa276107c4a82902b98e0e163bb23062a094c4573e8b756c097ba22e94aadfabab907b908cd81699c07ab628d25a4b9dab8cc5da4ba41d10c10552f0f0897cbdeddeacd1fcdd74d24134199c45e6f51e21adac07c5046501c9bc0b582c31c6760013a72c65c2087fd7da7c40d3ec5741fa1b493fa8b47993d0525816b1192f28cd7023bc9496182bce43f639856a326f359b704ba992dc2906febb9849f8d5490454db87f1ca8cd83ab7ce0d3953349a7ef2d1bc46ad970d5644166e6dd075394b370ccfb8734c7c351e55f14cec14ee5d32fb7811aec50ab32ded47b5b4a75c2689ce8e9311b444466d2b6e0d12539d922035afd61b585035390daa4ec55ffa5321e243dd6f2d8d5494ca2d2dc0d77040203ac867967e2a44788ab7232119890d07103b2ab6c1194d5e7c3b503bd8ed91491fc803b01b5d05861eea051f96514b449a8630f1dd7ed1b07539e5cbd1cea27319a6f0cea5348c3d94e3a10ae69f42e84f16bf922fdd2e88fa0e3642dc039af193b0dfe5089cda711dad2cc4c543fa07aa7f2de933bff9d3f42f8c8f7ffa24ea4b335f5eb52045a46ae36237d097d13ad68acb7259f796551ac7c2518396210765bce2e563d73eb338ae053c33620d78629f61e9de6f85dc0ede045bb7b23c2e9ec8fc46cd37ebd4aa1c853f33389764d6aa47dc6a4dd6fdfb1691b2ada1dc19c655fb1e71cddd16007727eef77de19610908e0fe3246088b497a1029f469160318a45236358f769f65164fc9f3ae08b84d790f70364095618eb57fab490904c2aa01dd6ff81f5837680c6cf730a55861e284f5f518ad6e85712c95042489ef787cb1bf34d2923d3af601f2e09ee579bba0cdd929333b5bf8665a76b6cc39570eba6a74f92243b36558d38eed95bee1c14de21d43aa9fdbfbd32bc0a4fb80bba4b07e04835eea4e08156e315a20ebca38ba5b6f949b514848d019e3cbf54aec2a9bdb9843d5e10d4a93046c5d1a6dcd6000e14c44bc6089ab2d606c6c30169778bec65ad2c44eadc30e8193f76546ac966dfb7f5af83812b5cc53d5e5e97eb7eac16654ceb069bfbb56a1c15f3b2f5cee784c554a2024e23aa93bead1c59d6df34405d5489aad2b1477b38bec8f8ca9b43ca9087ba033166c30a6944fa667e83f72ca3c55f40193a5c63ba4b055fcc3b09573e951d42a73be8406e80d80efd97c37121308997201c156b31611e6952518e9c4d9ebb3f4d774a70f06ac31a2fc25404150f55ec895bffffd387d92866d8bd21af67b2b509efadf4963905dea68a27b37e508b6c365e6946330aa27b9192ddfeceaa21118bae8b474c5142b447b469bce34085e56ae2187d3ec67844c72bbd130e27b4821492dc84bc58f921d0c3bf65e5194f1aa7a559c6e8283b0bcd897f9861e5274f628b3d2cf6efcaba27ac44c487f969baf5d8674a0c921d532028a1e4d7abe8e2c54131335de02580646bfa72fd33ee3808d3c88cc8ff74ae744096815cded3856e4c4f183098e9b9d6b86d34cdeba517d37a2abcee5a961216126eb2b7e20b11c0508b9bb98531576bc6a3e1d19ef2a5d476ffca7568759b0c2dd313456b42da93ee8ff3f3b4d3ea4eeaf27b7eb25bf6e09d2a676e1a42f860ab63ec2e4179418987261e6006bf8a6847243d3d7c2271aabb0f4ab2e6695ee9e5b1799d6dca4c1d071437a784b842179c637004e2aaf80428aa4a6246aea02e0b6c8aa2a9e68bc9b311ba2331a9d6bb0442f257a2386f3aee7dd6e2e46f952142c9db8d82d50390c2b1e50e4ed32a1ca48b643ba21c71242943452829158403ae1615a679985a74fc5ce68be220f47499cfa58c109a1421c2acf9984e85909df51e847b05979f39f0b93043205469278df41a4ac75bb55cc0da143f94d590d62170d9295eba13b54379931cb6cda2a6297c76e246f40eb9be01eecc145d4465dbc720d75d2a2b3b62e2919afa76c41a9629467a08e324f53a5b2a7981dddc362a52d0e73a125812e66711652834ade530da041462dc9443f9b5c341e2dda10d54420c942bdb88863b0d12258e9a8042bfcc7a41155e84c792bb6116477497a3eaba966d492ff704e80f609768bd5835bec28f2fab00b013805ca6166a9eac6a2b6d770ae1f21fba62cf2c548aaa4cf9f11d4c0797364c4e50e0c3983866a2a89bb8a711662020f6ea0b5960dd1c42df07255046d2bab3f2965cba940ac2c3ec5ff323ffd292251542e3ca9270028740a293125a67c584bac8613272fc895edc8e7511d3a1eef141680184aea00fcb3f4ca86fce79ba57060c234a9cf2c21da3bc6f98cbc469c542e18bb3b543ea7388ec1108c207f6c4902f360702057d6055f059c505c88890ca63fa04ef46919698a2f4514ae537472ae108231143b98995c9084848ab8c6b4314fedee23605383496b64b57add89f18e2ed4f59d524b05abdf7c2d96068175c91d120a29a0e7eafd71a60e18a2df3c783a314a68b58fb53197354e1bf68cae8831003c1bc32c24d8f872ddf01064adc490c3d6102850a0eb15a7a0147cc37ae856ec2a36b3853595b668d0c4caf6f88af85c02cf230e8ced70a90aea08ed2d992db6105a8be15b083e6676a99ff8dc0a83fbdc002e69933e4b49fc09f9a3fb83f312e61dcd127e47a266dbf506a9879b24089efc5a6236d64fbf56229186e812700c0ec4d10d48b9bb3d814a363211090909c73ff8427131a67ef226269883cab2cdac8726926255bed597cd4f17ad1b8ace0e870b4f4dd19f3167f9d324c3dc4d6e7d3f9a84236688edd07044cf06583661969e81c9bb7050f9e5a702a087c118b5d4e573df4f4e804f0e9ec1705e88274397f8e41eb9d065b1a0e5dc3c783e15fbedae7620bd3d0cf3ca3503c0e54a73dff838f5811b8c0a4b633410ae4530a41be3598c2ddf7343357d3e22fd5f8016ec71cac219730a32e41107d1fbfc2738fbd52e5c2d203e7784b623ac5f5669dc6b65e6359abb1add5d8d66a6c6b35b6b51adb5a8d6dadc6b656635babb1add5d8d65ae38ab8551a44a5d6d3017dd0d2c2ff004c4f7306bc7e716a8f969700283228cc228ab3d647cd7abce536dcc458b0554c90bc7b744c48119cc1244f07ef39913f8c29d4c6f0b627cd617f402b12b2d768ced8a5e2ccf43aba650ff831ad0ce4e9f1d6f69f6c91301083e16b9dbe81c7d41920d0f48f244dace56112fb67ef8c17e8a8ffca16ff47ec89afdbe7dbbc0611849270ca0c065de20005e16b0f461d8b2dc65d98c32ad78dfe982ec607dbe7042a290a6f42a70abef7eb0c244d080b84c864571fea23a5afcd7b70381e731d34190144fa05cd179b10289f950850ce900732b616adb10aab63525cdb5acb9ee281a3e8b1a7d6b9749ccabc059f77201f352a19b7598ec747096ac20bf73ce5a1527256d0c6f8826d8508580f98348976756437d414911e4df3689ca63f555deb130f77433f66e9f778e13bc7ceab0429d6d26db87411b1e6f30de307d1ddcded3ad15d68df205668f06b734e7efdc0cd710f787212c6b4ab7ecf7ae8f18f0dcbf3a051152a9900bdccb39209e9f4f6b0f2e9d8e4222433b085009f3f449d81a360fbd35dced8f5e26bb1ec286de953f6811e13a46147943b490a22d1a906cd624ef85c09def565cfd86f08f3e0b7fbe0c743e64bee3b5f555ba62173faa90d3981e92bd6627cfd4ef65f4cd676ffa21dff84a2e4b451b8f1fd6b05ebcef5b95724cdfac9ee9a841724af94f4119b0055fa71e27d03cadb6e6496cca3ea1c9b08e06e2e3b38ef0dbd7b5abb4a69c8f98ee271777b7bac2ea9454aed8517cf39bfd37839218466069f3eccf2934d150d33aa4aa6e3ccd624dd40e778d3216a0ec7510a3c08448426424e6ed7a6e95b3b76dcf89dcb2612a82263697666574925c2e68f2d6240d8de5217f9228ae77dff6502d9efe8f1a4b1a5a13405f4a058eca097e3fc480e123f6e4639708c88974b75c33ff99661cd86f234b6c8585385f688d8a7d2b1436e0053622ce715069cdfcbfaaf22f23344c91e20d0d5c90163d325532901a48ca10e2ae2cf76c98362f519f8e502185ca36019ed4bb8b1193fffc1d398c51a3b9123cf1b1124aca1ed38a51734809141e7c7b6c4fbbc41fa41867107dc04a4a7b3fe4cd0c27d8870ac0b862d813d77287fea071e82721bb235f49855076c73aed4ebcac06927621bd866bcecf81c14622d00128bf44001e70d25b442bd4680def4136df25c7bb498cf46e723abdd4aa06c5e5f3618dd95b4733b0a96cc0388f28f2e645c3a23a4277cdac8b01f253a080177760c23ee2a7b8d337c9462cd8457b637c6ea7c683ddd47487bef47018e81165faf472d3a393887b3de67ed0103dec253fc24e5daf8f3343dab3a96684612a9e3c3cdb620ced7d8595ddf28d565fb27cfe71086835e70b8b98dbcd9ff022de443f345ea2dc9d358ae6ddc2772b7fd4d9e2f0b24df170538d0fb89bb45e09c92b8e5abffeff0bf22fe147f7e1d67adfdd7f356f1bcd2f88206f60b80173099fd3ef9ca3aa535f4d8c8051cd622e355aaa59b148683e64e4d1f56c57bce7689cbbf7565cb275c67271527074f27b1b4e52d6cd48d773a71021e5d877ad322d261e0f37c2f1328beaf0bd4dff7b6b44f4da8e3f3097f88848279a5d64b6e4dcf22fa1a91d0b347a7db7d8574503d3ddd17cba3a633c1cc857b902eb2baa1aab26efd6b9098b9caf6007a5f71b6a5d4b0f1075f65d2ec1da4644cad4937723127a0cd4bfb356a7f83e0f5ed1664e3a75f62cbe8e5f78f339c0f551a56b0f8bb1885970052f8411d3fc35daa23c15cab16ea7e893a3648e76e225796fc2003aa14354940fb94711cefe1379ae383e2392242bf288710be4fce2571a5f4138d88a5145b7248e7fe9b357bcf0baf739cd0b9fed4b9a5f6be387aa03a633ad3310e8ef957c45e1213df8874f26b5b64a9fa57700b2c95802b0cdf64bc3df6f1d1db74c26c51ed1ab8125ef0b17f6ce3aa42709b2b75b102b1589913df2204cf230aba77393b18b68cc421132ac29c44412bff8247db5c91a3e73303bbd62aa1469098931726437a7c34df4d6b74d31ad46ccd365e334dbd465a9b934cf1e22da9aa69f424d7ae9e4d7dbc6863f4265decfa1368109479de0881dd0bc024cbb5fea7113eb071b38af3a3e199be7aa0101b5f3a93cc08980dd99e34829df2204823cf42535854637ba67ab976aeebaa7fa1dbd3a906e6f4f7978ee56def1b0786a2b66fffae818a63a501e193ddf3bb11b8fcacfa26b832f972054c3552ddd618d8981a2fdccd9a5d2281b88abbebb3d1844f860883052838778d02729ba3fcc3756c4d6fdc58a33bd5e6611e293483333950a63614442b7faefc989aa5da3efa8f83c9e20a28070d2fe20f7cffb4c834437c0751b9f72f3e58fac8da379dc8b80546e9fbea5e53dcbb172aa4d11e1871408191587b76b01752a68406ebc783402fba05060cd363cf2c03490c8b7766191beca700669bd981b3cf3f220afa9f4a8e2477536de347e14acffa0ca7fd8147d8a9ebc48dc16b6176ab5cf819dac48f20eed3825fbc49fe0e33675c39ba7774e65beb945da4e89d7079482a422c436dead65030103748bfe8a151ff289293d209310a5e8b9fd1cf458bb9621a46b4fe4924e39207bb73e5b37e7a84b34a883447274e92b24df7d5072a4bed8449f0ef1cb565e3c3f1d25c299588859730a66277278929a999ab070f55c6fda4be8ab97b168b3ef0cc2b9610111f85d2cbe7db22935983f60d9dcf2a1be2eaf565f6ab8ef9a58b092a0568239c36b0773e070d5ebefce22da569f66ba73164a342c75c43ebc6b3c81f200ed014387d57285e53d7ba143be9c1071daa5068523c9c6338e96f43cbc2a2391e514759d1e0ed9070abd4d128a5d0ca4c2865f69490005d85fb0c07274c79c165f3e6be1d2607c2ecdb332cc463dc264c4c457872d3aa23e67571fbe4f6bbae9fc5b3e87a66674b22a273f6e824ba5003fc73d860ba1a5685cc0a90802bf72335a1f665dc5c6caa3841b8686f71f07b318a63e62ce44dad9931315b199c1d9b4036b18e09a585bfbb893f37749ba3757556404b6dd9592c4c610d8504548ea4c62f4aec5314e18500e1328b1bf44ff8b080f11867daf6783d01113d05d0051e1a00c3ee106abc3de5dafe6b7be8874e3230ec33652189615200ad969b74d622544093b4397e9818560af21f2af3771bb9b54a514d777815982ac8febcfe542288ac17674241c7412e29038fe5fbfa3a44a9ee05dc1a67ad5af6b7b919a99446adf0f399dc49972b1dac3ad5cbc7641d8c1684e9ff80d1dc023e693a1772327de496d43580e28c76ca01da6752cf82c461410071ba19ab8849f7bbf96ff064431f881e28fd7674eea742896fb3e058fbb0c8eec882ed6950fc640760e103e66c93b301051c7cb39a2a24770be34332d5d7a777e6b309409fc7de7a95f992d840fda36e88e663fb981dee270c329f4ccd6e48d4be5106e587f3dcf252881b7eca0840542aace17efd4123358ae32e10f19328865248cdb29216b4d48a241deb697fc5d662c0cc770444af16f551c9f9fdb9f51c986a5c652aecf58f1b99b1b4f04dbf1b49ff5ca66b5c746442e9a7ec09c79b56bf674030b8861e0289b0398976ae7788b9fd27249e3f32250fd381cb2bea9284849bed6a444bcebf82da66b91649cb1aa5124b49401882f09d8f4d594a0bdcb5933f261fde9ef109947be8c3c90e11b5e3b08961df377a11b5cca56913884de2846e709b79a79420a01f081d1d3eaa7af0d52aacd8c62b1487eca7d817e213f02b1b508dcf9d7a9b6c058f4891f9bdfa17afae3fac3449f51c4fac3ec352e1d01593aee61080a6c34cbf80bd00423faa90f18b8b4900065eb016ca78066dddc38aa000567cabe8368846ae763aeba90c440f9aa339eb5d7e339e5043c84f4ba923d92439afdda28528f8694203cdef868a298a0235be4fac4d455b5ee5ffa6182a133523cb5e873072a84fd67646a10def4ff420740753387cd20c5399a0b407e18b8512986be4cbe2bd538d4d16fe8e156e1c28a2123c62b1f56ed57927e724946535cb425203dd1ec5a948dc0d74cd660f2e9453175974812dc1595c32e1832eb110415898b5dc17da183e62a7befe549e14d1ba7a0c54980e1c9fe6243e5ce0d8e5b4c885eb7b4a9be6f41a3efe27a21397ca8ed6cb8bfc07ddbd23f2d99ffe00757a47e1c486f132f30a140ff5bffd14eaeec74a26274185a89ff2f34a6d1c0bfe2076f0ef7fdc87385f9cddb606bbf8a33988e3d7e7e6449142c9e104f125d2b4fa62c2e567693f13c38e635eaa44e7cf841d86b3c877b76a3ee53bf542bd5d15b6c3b2b68cd58febf1eb92c8514308179e9a5ce872a05564fcf4ab0ec46650164bdeb90c8cc3c1bee82c4eeef82e04ef94d1582b1bb3156ed1863aaeb17dc65533ee90974bcf29b354dde408fedf06856de401a421db5d62efdac88a4a47bc97bd80570e96b4e99065d4e04a776464b85a175c58734486102f1cc5e73c709a1b94062bc3b7f3eba2ebfff30a096e7838c985f90904589f83e32fb2a810a92d43d4daea3e87d6fd8f8ffc6c331f4de087274111feb5490ace1f97f9e2ed6f77f70f2ffa7f8b0fdf0fb79cab72361bb5a37c3aee7f83747485a11e0db7e5eaab44f77403a439e3b600cac894124cb7b411a133b8b26cc5e9824b66e1aa7a694136a99c9738c317a514090bfca9343cc96c02c54d2ac6cdd813ae73d1722a5dd497e6b870b7a7bb4b53ba9723d9631f7a8c4d14a6a39bfa808beb5bdddcd5a618013ec3caf2052ff706b4ce17611e649b315c6da9edeae7c6cb65a8fe2f2b2e564010d3e8d2bb700cc26bc7e98ee0435fc113d62b859e9a3f09de1b8abb3ad3483c6e6c8adcb210f4825e4a1012df401a4cb8c48dc156fb5f0504534b309f40bf498384f5e876017c85d95a8f882d874e2808ddb180650a4e19f231ee79b8981c9511300fbeddebef5c4cf59ca48d8fb206a01d74d5edde221c815c10452de7a8057b2d2853eae9daa8394c0298a264945524d62221ec6aa8fdce526ff7099410f36f945651d6cfc9df932d71b8c86dd239cfc6a39e770913b76c857aa23a08e6f706161e88816dfc5c096cf43bef94cab155400d6ce587b3a51bf8ef8c4103267d6e8d9cddca95d260550e955d9a6c342dd0b8704b84edce4d8b40bf93d20769df94656b45d011b730129f6f1b9cc8eef55e4e48427a1536515a04592a5c0303960f4577d898ab324c0a9f1d351cfeab0f4c4439008831d02ba4246735fe2e0aa739e0d2a572604261f4aa159137d8a3308d9683fea759ea11eb1b70921e69250ff45fa3e96af3df2b878beff7c970b0f095cc5f952480170e27650631b1022a4f4392ffbeaa94ef01a1134a8f4e789a22c35fffa6fabc7f9dea3baf6bed127507ab9d28978bf70e5d513be3f47cbfce1b269f5f45ee3eff945e1b703fa0bfe4d1c6ffb57763f6d452dfcf05ee9784a97fa1c632d5f27e60cfedc48202f421582bf38a8607c821b82ef38c8606e821b82ef38c8606e821b82ef38c1ef412200fc17ad976f67936f09758249ef41e336354f3b5433d89b682f27f2af694073d7dfea9c860630053aa584e7ea0cfc8548081efbf58238c4a6599a0676998f7a6830c8ca04c73877457ee4d30be7d6794350c517eaa25d11908d6148f3a93df7f3268c998cc337c879e14b9add235252d0c0eed8c03d326822792007a895ef60a5943dfe70602572cf070deb5a262e84c55720c7c47735bb2ff671259cdb974cc3d012c08edf400c4330cc30f26653ae1d7411cfce82b3fc1cf617e2460c792c6fae29a1defe33cae4e9865f0cce2ea6fc897250d9869855645ec50c79d814451a24ccba5752221c16ff5315e0a3670f9e5d19608065dc59f81544d77b6108a9ab9fcf391142c8a6595c452d91fd978c355d2c229fffc1ed19c34cf817e86eec6aee7edf9de30fbb822cb0def7a2b0d18f5e025b177f2afb72473347a954c5140f9c2d38fa8caca1d55f8d81999dd2f792a51b6ed20f7a4301daf2f95d8499212b93757364d6e354ac317b26f54b612b48d9243d91cfe25995a21203db99910d3a87678e79ded2182e57093d1c71cbac035acde264ef78467417412160e11e89c9f0f64baf372c4e1989be64cec9b9f7a292fde2193943e456502238a4877fa273b468265956acc5d4a941711b438bb77942aac9885e2b3fea066e1586fb62439b7e2c8b62b3357479728915f47c94063a26b441a90b41a26e5141baba5949c7c2c59517aee95a8a275f262dc808552bf079e98f0f085da752e25ede478d9d4d7e82e047c8400490f354f6de122147ce18339467834d34a453b8d109964b51a2a625bfc79cd29fc462a0d269f5afe556a9f75e33c4b09dcf1f51a1f44cd8716eb95086ad1b1263533fcd130422e7c5b70882497bb782778dc6d99f7d533ff4309cb9473a03b303a733b6c9909bd929325da0346cbe38bb475fd4a93b888ab65673943fda2ff81cbd0a9f76c60ba6fec6809886466fcbb737a2e9762bfba19a86fe690c67ecc1634d583a26722920059a9fa30fb99705984b65e19b76fc3590d98b0ccd22a7c0e60db92cd8221bd243eb998777ddc63e6be1ec0033d56e3fc1fe102c9b0804509dd6506c5c2900632ee379dfe538d6abfabe11e44b89093402811b0c2a93fc1293d9ca691e8359daa560417a7cb3da7d37855b4f1c56368f190dbd195a31019be75d87384428b1b3dd73c39e02b39918e059affa544bf030a19fcc159b9aec36f0bc5a21395fadb54f1852b61fa933f8311f4ac872e1bacc6a3a886ec21c493f6788c13ba801083222d85e3aecaa2a092c22c365994b83ea1a2d701027fedc93626a506f56f3ab87301dfd4c2dc64a54d7fb6133507db69895ee0dff8925e644ff10cf38e8781d2a0a91f12b994b5f41994a2db0787728b3d0256556f9d008133609f03c7235905184e85a5b3c4f257a3afca41ec9e1a748bb2911429ee3ca714a79ea174b50c98fc167ef0d7c2416bf1c1390b47d753a9327df6ac8a95ea8306f968b5dc5d5edeff0e95e24b64b89314173b8ddf8628554aa1ab9f8198eb5659475db6d59031fab8b6c8902a4fc9343c4d8411cfa0d71c7927eac46217053f322f9e9a5dd75b96023d518bb5e673b7942f503ed94258f8c6b6ce9ca7c2440ac3777663d949580302203326792e5ad3fe6fc8c7ab612f1feced835f084d31bcd517c4a655dcdac3e0084b1fcf651fc5a64f5254179079216ceb14d64cbc82edb5ba14716fb551c0bddde2311ebad461164737237a303578648e3caff54b13c7b9fffc21de5082132bcdb55e78c108ca3b3b384771797425c8391d1abeec24a0c04e77c398a31f478e37cae4e31b243acaa179a3fea6db9ef3c7080307efd9fdc04d781c14610257327b74ba223233237be2e9ff2fe4d24e47924e18f3215bedbe5e6099d0f8255886369f92f359acc0e8c185b22854dc423d9b9685ee643c30dc61bd6d681086e97a09e0e02607e95cdde5fc2a39d6e6d2fc4add9b0c81925663bf11122b1d640b2d71bd5ae1e2857a18e93aa4fcc66aeb63b2ebfe50bd9bb5bb1316d1920b4ff4a3f583d891cd704520e8caf22916757f45ffbe3b2eeb755d18092a69528b38a024428dd9060e20dee0d6ab4e3119234c93061494e8a9e8b7110967d74b0bbd4cb208d6f430e589df8782190836a2771c823439d56e09650d4bbe7504b41c227c0332021b5ff445fa8bdba12f8372a5340569624b9a5b62064a1f268f06651547cc32490c48023dc36dff703ac8854d9ccb4a80a8aaf302e70097e267fbcaf8c8c62e099669c4741f7a8d56bd6c90f33f284454ad7a87aeaf055c765abf9a97d52f5cf66963c11ba1e40a18a6e5e29e2b54f1803d0a4161ca43a93cd5be7bf7d1290af54f1440a2b43c11f0eab5e85ea80fb969e5f89db0faf01f74db8d286a5ca3dae65d1901ac0a36450975dc639b49d7c89224fc187bfc22a485edc512d346dde5bd53599b79afa632f5f0bd90bf987684021e8eba99dc8de4e51fa1d82a5e474794e66bf32fd0d1f07a4f8a3012eaa53a03eabed51081fb41a8323e2971588ffc382535b93f5bcf3f827dfee5b785743afe32f6bd5448072aa2579a79680efd34aeadeedacc90865f382ecd393caf50d8c13f0af58737fcbaa6fc941280b280fc397a68c69f27be67dbc8c63f6c111307c63b6181250d89ecaecc1744437b4741aecd3d2d4a231cff71417a92dccf9d084351683cb5d3a1edcb49fa0bf66bdc998e1f0c4bfd01f8749b631b720040d8831e03650fa28f81b1d6d5785d2368b37bee619e52030c6ef0394fca86201e7b50422df196e85f0217096753aae8f5372c14da7ccf447892dd2b1382050b462bb344fa014a67e2f4cac275cbc475bde0024f8830be0b45713488cc4b79d463ba1735f3540e7d63fec5f78dfca7d8aa72d93a47bf83a3b0980aad19a187963e390ec4e05098d6058a73cd3bdb284ea66b671103155321441652575d5af8627a95cb9b6685748632843bf6ec9053bff60865abd5d0f400bddfe3b91f046cd6f385d717c08b89f8028338078f783755da5e610feef1554486f9b2dc972e3e03f85bd0eecbef52c7cd9fdedbddf6fb27762eea552f1ecac088958fbf2593e4da5d12cc11962067839987405ee2cd651045a7ff08b020b0e5953dcec10627b38160b1c490c8e80919ae7eeb925cbdd22926596f007359104de868c4dab8cf8cea13134c0a0c43908c386fc790ec3f95f3773d64f7b501070e7b726f5f742692da2b3c4aee51a60bb8ca6d5779cb7af11f7f7e06c82955d88c5bc5fdb08219f0322e92b50bc4661a953933063ed611265258222e15c75524b23397d8f87b5ae443244ef3c94b7bea9e8e6dbb25d61f0f74a75edcda65aee5eb6c9049bc581c28523c0139f9c998b0c8f825234e4dc3849025809452dc7214fe03006450218d84c7bcba6ea2035d53c46b24e09cc592cc9c189248aee46af958a52441696ef0d142f2f3829f4666612bd95d99cfbe60d67f919eb0a7582ba9679019cbf6f2c20a9d7bf1cc29ec92efaa1e05cc23c86e503792325aa11d9bd54fa66b204901905ce521ca9f65c405653788648619d83f11dcae42d91cc3ac1f501fdd80b6eded079316a26b8aa099e2b3639102371478087aa50a222901a25b67346a6c6951c1967c2c81652b6f1ffa5af80c761233ddb66d747fb0c5ce0d6b1a20e55a525867358f2f4a01de5b5b49de5a9199d8d264cf6321c726520d09d5a732153d01b28c5e3230c045d86c29643807463fc74c55afafd3dd5ab92d9bd12ef059a39b4073a7a8ecd573d9145b63d063bf17835894da6eecb753189fb1ebed5d2b302bdecfab84b3b3a99a0536ca04e76a82140fc7eefe1f97d17046350735d75db8bbedac8d5e69a230198232a57a4c62d04eaa7dcd1d4fa47e480ea17e10ff502aff4df0d8814e62755143a6816a75fe5347a14a5df8620e65a1a444685a8f3d705e21957eb623b4b13b42547dd495e00b3beeaae51bd2e7bf80d476cbba8fc929e5fb104c1745a2ab9181e21378e737129062dac5abdda63992b2e7cf4d842c1353fb39729b9d2c273a5a41884b66fad18ec49f78464a7a62bd503dbfd34af07e42160c6fc034bdf1a75eb5e49ca7e9902b21cd23612cf8b3d590ffc4db3b07210e962e267a6adce230aded3c977119efce5bd356c0231b8d3831b24f1f7175b4d0cd24b92c9723ed47b46360d573442d035b8be82eb1f471efb5b9a406602b17d0b029a30fce50b2ba25bd05d0cd750ace9ae2a572020d8f501a7104d887721eb703ae99f155c95f63399c1d3b0220484723cf76010ef707d6392b06ca529017715f96bd01447db7b2bb4a97ca1e5c7aee0004c2863c00c48e0a23163ec5e393d9d38153ff38b735bf875d9d904222c47da20982d6fcd858e2e18b4edd82bb85e0fdd003d7836486dd3af8ade1fd55ed6e900fb5c4d303110a84f03a3d42b5ca0a8e7b98319d59e5b15778b07909fbe1500eb2873a9a74558051a5bb8df25f230cc9bd240083a9a2955c05c71ac0b4b2a04cd45b8cbc0545c094def6d80108152282a9ee4c3f5344093447bc6677902e7805281b5c6f031c3bc032dd27a5190de4d253b6302fbdb222a377671deaf997277bc6c9470ed7e037efb7920d517b40390195add7259c32a446db9c2e0cf322a9f5790bfa96b80ee282ee34a42db7772420ff29f8806bef1a1f5c77e62c561a6373f8fed949dbc3e1d81da3ac5d9c981ef8f5b57f342e1325f9faefa8c608f03629ffb22783b8a30c99a47cd7e6fb357c51eb4904fcc7ba9cd4c3bd9ca535f7770637e06192031b350fb0b493f9acf4ff65049a930e7117282bc89ecce8c59c7ef2010e3e964adcae55a9d3be8de83b6fbc2c6997845d77b48cfb97fc0355b53a0392e0b298f7ad354c1f900ba76d1e7757eeb929e1e701bdaa436d1aabd7f53d68ab2516c067f955f83a605e33dac3ebc8d82c56bc26c02ad0a70e4513414007174c7867a727079a4da5827010308f2e3cb2d90b1d732e178003f9008ec529d32dad8e38b068e5f2cb65e49451eb997bbbca2c7b956dbd6bd785adc2c531d666e11a326d053833098210950477ec079b2ba037bc42b97625369ee54c5bbe289beb9c17dc0cb824824331c61cbcb310f786abb8b84ce3bea14f7f590fb40f0ea069f1fcddf6a86721c5b169e0da9570bb60c8def577d7d0534ef33ac23f93c679f0d1d05b8c20deb3e0fe97d2aa2cbbb7ace0f8c53e5ac520a2c80f6868b433862298536e59af61d2d4833ccd2f98655efc55e5d1338c8422b9f765629d3147b7f02fcf5095cb4716dfa5365280ebebd8a3fd80f0917b959a612c9d0d95ca811a7b75c9826765e80515d263827b857e7b5ddaa4c780abe59f027426793e6682ef8195af528935b464e0dfb8bfdeb7323aeaa84ed5ad689d61d31855960d34b2929dc8c5363eb5b01aeca89809b19390d5724a6c9c537fd8115ed82cde14c01f37a182dcbb328dc5145219a037c1a6244b517b8188bc12ddc0a6392bb9d6822a6356dd069ff1e960a205455bc9c73dd6bf57997807410456c6fdaacd1e872b384e25b6628ebbdf01a4fba287ee08afb134de7e00f9c06abe3595fa398df17a0a178d4a1ad3ca5d1bb6f84c10734bdcff08f8f7f6aef79e8c8d10e99d74caebe2ddc6939e6c60e8a958723c14e9a32f99de88276ac6e87a8df35dcfcc6c6ce3524bab8602fc714733c093f1a8f0dbc53dd6cc5e73b7c45e049d71e362f947c0f4cb176a72b821f8f19ef910f277665f954270cc10f72a3bd6ae7b37fe9e5cb009cc49d683b9abb4cbea6fc0f62f64a2e3dc90b252ddcdfc9c29d32bbc83e75560f463aaac7538fc1191e0a8067dc90b9343960e5378fb95deae0e63c5901181f06845b8f7169a021671d36b45d25d3b86407ede780af34bbba4de722ed666b39db04bc6067c10f2ca1fe41d4213d6bce1605286b22f05681ad522d11de54dbaf2399dca346a80682651cdcb4cc12c7ecc69ea21b4654368082b5ced2885881819e8fbc00843e89701161d0798dcae303deb6bf66efc9d95e35c27a7e978e2b1b418efb9cd142cbdca6d883c16a41aef3115f30cf87d5ad4c3420f9ec7e119eea0bd24b186b280969ea5e0990cb77baf2a68331a17ae01d7240c0d2fd546bd60448bbb1fd5d430107f87e42d4cf6e622c5044886e5ed6d11de9cc3e9732e9a6efe31ac00e9470830b4eb4d29b3abcba5e07b46a4659cf0d239e58de4ccae4b8f1ca27a5301325be43b4dbe006808dcb0c06f34348e46e5e215548e9e2ddc472c0947c72439418563463ea894682474dc66a4d80bea3291e3b528b70d60563d2c182fabbaca0a0c96aa5804523bb739a9ebf116c203cbeffce0667f9d987ee5fcf5226d75dfdd3da207f5024fbdd26e5e14e4d2cd92f3036bfeb5c593f446d3383463f4fce5c945be5fbdf726dbcd34d228a0784ed5c80740ebd0d3f71743426148269553f8a4fee349602c4ed8cf029ea9f0f6bc5f43d995861ceac159d3ac80fa6e6d40380e9ba730bc2ce9e4df929db5da00cbe8b4ca517d1366376828742f7f1aaa9956a4ca025096a629e004147ed4b1ec6c5a0496558ccb8c107d166c9eaea7ed093aff19baef24d6261d603df13d4e18d5e1deb5df99f0e7edb2ac58563b852bc2039982fa8af99211600d519385617edab89b4e8c6df1eeae030cb0d6463ffef23a7e4810c37e91b57a67cbaa1b8c9a54d1650b02b66cb3af159a2be67a833058d9d2309ee546dd2df7038cbc86582ed2e42c0cb31324a51959b6930103e7f8e795d8ceeccadf08b46087d6db56dd2a8cce632010d182bd05e2a4533b97e7cce9a48844a687d043a7d5df4eb901232ed481c9870765393e12f7e3ef92fad5feb282ddf9cbe9e977adbd788f0980e02fc3f5cf6f54f5aa5dec6d4c7cafdc6da7210ecde5cb6858655d77ef0dde0a09c20346a61e713632b193acc3d8edac4247b875eaf0563aad96d855788722d0be13cabd7cde927bfc9ed2992da658abc3a1537bc4b991ddb7710a7f36a385ac69cfaac701d188f5e418390c99980ac1f29a372d7bb0a41fccb65cbacfcc8b3fa27baee451541019e0abc281b85deed9b03ad372ab4fe361b166490ac2b06b0e148468273dc9675ff8d4f8206ac6ce6521ff637d3c30d478ac3d6528da7214ebfb662b7fcc6b13637942147f7cd414f9f52145d771c31ab24138aa5ef521472e5da3f184a45867cdd6e200dd0f10608a0fe47991fed2ba9800acbf7514e411b67280562ee8b9a0d1cadbf65912f3f41ee0094ccf8a38edf75fd6bf5d4133c150b044fde160835c97f233440e3053a80d74986239dba19a3ad727265553e73d0daa191495c29de6015ec6ab8ccc7cb86574214762c80b5be9f0a3e6c48eb1113997d1b4d0dafafef00a5137a7bfa50803057727ec37b35e65cdcab581919b0bed77d09673bb0bb2b5bb24c978678eddade4eb844d50474c772e773a4684e0812519437339b55d320fe7b4c280addb94cb03fe8f60db9a191a9b911df7378f5d26b9fc01ad478e211aa3b06bc2811f36a082b491c04d732cada6ea1ef9b8e226fe96c3283dc335352e0e909b92350c021858bb3023c266cdff9a0a1b6a7f0359780dea2359b5098e150370090b59acf6fc80cbf6d8c273a509c369e2cca9587ef0f34308ee9bd1310150e55c5770d8ffdf5ec17305257ddebe396ef4f22dd1399bd3d28e6ef9eb96d91fffb15f330bd860095b030f361d0863768248116622872e186e7dcc98727e11d04f289e5aac2ec0023f11a812659877bd25441ca9de8fc8315f0d8419488be480b776916b21f1c17d20aaa31245dad0bd86104de080f9ffec8ac5c3c52ab224348f56da163eda4aa8b3b5a0a16b0190a788978fea424c9908f26068d07b8ec257affc0333cb3b41d3ae4bb87db5f3a9d226aa3f4cc163181cb13f90da5bc14fa18c5aef9bb74aa38bcc266f9ab75ec02d35bc36b49efa9b3a34335848b265b293c7fd0a483f1a2063d9cf5dbb900c9f8afd933f43bcea1452c51195c34262e07aeb790451e63c2f718a02847a968a40a18bbb9b73506c2b098d7b79737185afb5c9528ba8dc50e40e1961a5db340078f09352db5b9d9b752c9bdbb5073bda7ab9437ce9205f2290ef489cee3034622311959fb4feef9bc9279f12f1f6fcaa99d43e55f438eedf298968af6e5bdea9dd9312bdc8ac028e01883e762867e6d1ed0d1adc4f6570408e4e4cac8824b817f2f4d57623d0d87830e336203aa3820ad95194039d635cc8cde6eaa0f8f2a399355b055a926f7300f718c641af6a6274a579424379226dcc8462086f204d2803b30f22a5755b272fad395c10cd282f5ec0ed3cd7ec5cd58b615accf297756098bc134b89555cb71075235b49a3a1dc8fec72cbde65512d571f5e518e028903c9968492fc32393cc9a5ac7858665f6a8a292849189cd99677a39ee34c753dddd46262810465b727f904de44dcd8739f06499793c0f03c529ccf64333471493d2b0919491929209dd8db4b1e195dd83c14bf9b62aabedd4e128874bce750652978078557ff4933c0e0dabd3e846a132d33a0bed2e490c80024910085233eb2bf39e11c25b24e9e8a1f71381c88079eee65f278aafa72c4ce5aa71722fcc60d94540bc434c1ae9b6b3ae329cd2cf34c1996679e1dfa2cf447b5000fa22ae205262d428975101d366fb12c183aef869b160294a716d31d64dfa123f3605557f4cf018db77cacdb416f0a35c0d90d50a9ee207644d9cfd3122aa5bbcdcc5496021f2db2ddaab213fabfcd81b890f77a384e406eea2495f96f7c7f2b668ad0da5510d55c370b13c22fb68748718e6426b68e930528e1e1780c2f7e90baaa326ef504989317e1a1ff6093bfb204c876c944b9eaa68b217c32e4d9e2b275fca96315165f2cfdbaae42a8d8decb9410bcfebdc3b2ac54bcdfc888d21d85f49fb713d978ac813b389e466e60defece9130dc55647bf7498e6bf5e7305424f96aeffc4a2407d6fd0197b8be1cc602ed26d7897808a5a54cbcfe18e49142c331b70cf3db00ea5976383f8206aa110a3d0435a141e22540bbd32ec614b72165d8fe6e99e5908abc6f51414e4180fa377001153a60db8db31c94a77f3dc52872c09bddb730c0097bd4555d7b6bb43af464c7c1fc654a7ad2294ca0f7b3a6ae94576d88bac0f26c18b822f96ba0018002abc60c299561ecd83044c0b008e8d0f1de6cf1b661c4d0d73bc22a4d47233a77ecb935ff99ad64122d09636bc226ae0c87db9238308cbb83d599be7f989e63ff88fdb190521e546fcb9f934579a79f61e18e199af0c4cd0c7bb81922940735431d447981c5712327c716a09d6ccde76d6525f12c7208fc0776b9a4abbae25c671d39a0a42406962aaed383bf1f433462b9a0642c02d1a1cc74aa805a5f7e3912ce3caaa7c0195393864986af451fbce9cc70e6d0df21570f848f09fc06800fbec686fdd7c559fcefa896bb9cbbc2a04591f4cd5bde17ccf7319867f42aba9ac27f3a5e7fc20ea046ddf00d22538712851d7008d72d81316ad0b739974c3d963ca8e8299b48610bbd65dfe0f5f391d0ad003bd005209c80a0aff97017d2d42a209beab8caafe6f89728755d6b9b86d89e817988eb3de6c7e8eb9edd9b5c8bf8a7ef785816c7fac944d02e609a4ab0a907f4616f34681456f5b9c80e7c1d9b1c49b6f49904147a0f4981af89b9b1fc535f6b7ca165428896e9e82fc7deca99b8897cd793791b6b4f8db363d8c938520748f7a83f2a9d697e9588c27180a02cd1c43e2fe2c3ab565cc3e870eebf754ff6800b15d3abbbda36cff9ea910df27c778241dfb7e811a42626815225fa6d1068ae21cb2e7d0dcd2e29b96bcc46e371a852c75b1c2ffcfd30a000716028822357ecaa66d4a9160f53a564976a335561c6b7ff54291e5b3d2aa43c1b2976c358c4840a0a2816d2cdc3e24a793be592325ddb26471de0ac3a3fb582c95f5254cb372f0910cecc487e75f663350f9143498c166f19dab59ddb23ea61a134b9377153cd76a9a7bd39b2d6a58fbef16786eb191e5238e7d6d4f3d7ccd6c414114213ba363f59150906d509fc3d179b9cde2236d65782fc4b7d95fae3741a32369008271c7c835ea0e6b6d427ef1ca2cc6ade97e9c03447d4af29253d6a59c7c01c27495ce11cce358b150a7c43688ea0d37b490f1e72368b5569f79dbf09dfa002a7b7731270455b69e06e8861c06d743fc6091ce6dfa2fc0e975528984cc6e462d1d705fedd390b07d1c88449751f00e5fa9d3efae8cd0e78a87bf11aa2327010101e27b3e668e49ebfbbdc25dbc0eb25e9432c330b3d4859f65cfbd35cf859261c93d3b0a0b1bdd409e94873b6b72071e31a9e940e20e03eaab1187015fb1f01778dbd0722c0f7fdec67202625d771d97cf7df55b66ee5aa20b009530b40b8e777196e2b4ff6062d8e02f5fe1f8906dd00bbe41c99b737ab50eb28e81b3bd469098d4634ad68b5b17030d156f7226ab027a6686115cad97046e77e7514fac4442c9046108822a01b97e0813b8f50522e8f31bf10ee2d5265810e1df5d429432a0c0fd061ace752c70deabc28cd28131b0892901b03edaf5cbba4d28d38d2df110b02e8bf9aa16eef6580186c8f59db72366f387d01ba8da46b7a05fad4017db6c45910cd83e2477fc046a644e4aadec170e6e2c0a3603de5a8a82f24014d89edb1b56da418d97f97b39acb0c6993f1368a01125b1afc6520dc273dc7de5cb66b45f992c99fc6dc8f09855ef42f22ea8fdcb1ce15ea9f47135654b14cb6e9eab9a733163761516184f06ce6f03e24b2083c36cd1efd8334f14ac27efc542265b6e01216e5338bb5cb7f2e1b1fc05056985fce8d5a2b31a55671019c282c49990de758be38de7180b3948f0a5d089adcdd06e3321999a3130d9d64274dc2dce4f5181cb4a2ad0e3e92a4ea19ef82bd512c0d89a84a41a8353443540c75cb9faecd2133cf03edf5bfee36644257713a033b8ba8bf3176cb89ac2472367eb9f3ac4b2603594416bc15ac4ffb283a0a93c87370fe9eb639f708ec8d670cdf521aab75513bdadf321e4a6d0789bb301ee84df465ca206c7d3f1463d95f780cc7a61c620f7eac52aa8424a890145a62cc90110adb91ad8f3ac6d4107f79d946f430f40040609b5ff12ab35bf7e4b068abd0aab09ab52b0d6cc0a17c8e3c246f8ffa2fbec99f4ac8d1e9905c11609d6609cd0d65d44bd63fff3182d7df073704e9b427528b96fea64978ff4926e825817b7f66006f2b70e34a4aad5ca2f07e3deac062cc7b9aedf8575d6e0c5b4e74011aff618ebad67437e616b5624b48808917aecf71f4cd9f6816afcb4cc79a83b262534729433c055abaf6aa7fcc92ce49249b1c247b1ada53e87b04349c4bc11ccc271a617b6cdafbb917abdc215652601dd157133e26489d8872f73a8b1f3a6688fd4c593645e7f897632219b71aa20db141a6626cebadf72eb46c40009a1f07b01b79de9f31f7f2f9b6b3c46c7ceb786cae4d02275671609c1a0cc0526d3d62117e34b6f50f182a972fdc026e1a7e209e65209d1c2df484e4130e26ad84b533376c80ab141d10c2532028f6da76beecd8c6553e3907648a2f720ffc3f9868a50f08f4990d5c0cb6d66cb68dec1f511039f24417ea8b76dce9bd3790b290939291108db295a6198595622d7301d72bbc856e74f619268e25d64c0c4b6c1a2d4c5c00c67141406ab099cb2b069775cccfc2bf7190453b11181010e7cb23f0510c7f36bef7d20504ae67cda30c6a1fc50f412cbe002435403063d0aa0fc07465d25c35880ac53e9f401e415089784a89479c9cd899fa93cdc9f67bd0b7d7f7990bb783fcee782530c283da29cc0f8addcc14bd5fdaccc461c459ed399f822eb47059b3b4373faca5f499f5653702f422038a0b179c55201e555107b96918e506631dda351d8a1d4313f549c619d6e66db8e2803095881966df83ac54d2121ccc4314c34c52b4b927ded58821bdae757f12d38ce7facff5f97a4b68602a5f9f6c5d4bf66eb2f7de52a624659e09a009f7095f4f3e4940341210c988092605792420d28f46990d1fcae48f461939c25f2e4f2372e4d15b7cd63990f675dcdaf0a16c6d09be8fc2f64f987c1326fd08d7fc237f7f7e424e406ddef2f4e32f399c8a9c8048e5e927c9c9f479dded4afd1b914f1a512a432a47a348023a4fb8ec2f2201653f4f3f2d1a76a363699676e293e98a938ea837fa2be111d65abbfb48ae4235ba4ca9cc3b50a6f4e94806f27424f7deaeebbea15cdff32e19c893fcbe4fb43c29d08572fd4b0651f8e52ad691fe38ca6859e140daf2298a444aa156a47146cd214992a552c934ca6c984619a594526a69d759dad1d7716affd2b7a578329d4ae4e9743a9d50469e4fae65d7efb2dee86ae9090b783e9e0f1642ca727a40b93f5cb5f9fda9367f3cd63bf4c763e0d777208f7d5f6be9939b7a3f3c5e8fbf9a7e08e2baf36117f29e196973e4f67ab2975d080abaf3610ff7a0feea771c59bffbba506717ba47c8faa70be57ecf6fdcf62542ca7286b3dc1f52279289b0198716d7ec46daeca0363b09f2f420a06bad87cb9d3d5c9bfdf7887716fbd59f033950c38c74ccfbe1e13c5ca53dea3e9c591384b4da5a5b03ddf6bba6a96fb17bb5b56fbf4ed77c6bc7210c472018a64e4a23d00405e5ae5a759d8a451257dd7822ad5d71c178a586e6e5826752c4dcab62c5f7a980b958567c2d30a6c994f382058b1c1a33f20b16326aece0793cf8f0cf030060b4f0e15dc4d07979e93ceffb40101489c23014ad5d71c1786586e6e5826b52c4dcab62c5f7a980b958567c2d30a6c9240a43511cc7713422914824e924d99592d6aeb860bc3243f372c1352962ee1d4723128924c952c964329d4e7e3a75a5a75abbddddda7b6f672d89244b2593c9743aa150a892122f29e94a4bdc45e28834a14c4652e994422a9d5aa5537962697111c30fcd51367af325da90e0bf4659687252fa1046f471f430618c6843a63e6694a17c0cea4319199791e94a65dc5142d5285bbdaafb945fad54441f57af32ce60b158ad96b75a5d69cbda950f554699cbabb0441b123f6b94ad7cd8126dc8996f8d329a6fbd3e7cd147977ffc21166dc89ac7a32cc5e3980fcbd2cbb22b2deffd108fb2158f4bd186fcbe5c116d48d8afacb0883eae7896efc396961617177771e94a5d4ca616d186ccf9d06594bd7817976843b278d728cbf9d0146d48196f8eb2196f8e336ace1fbe441f5ffc8bc5872fa20d49e35f46598d7fd9e14318188781e94a6160441b32f661cc28bbf91819995116fb7046b4210bf033a3ec003f33cea899001fd2883ede3ccdce8735a20d9980af19650af81a213e4c91c253a4e84a53b45aafd3df4e91d89f0d74f3671b11c09f1d44803f5ba8007f369203fcd94342fcd94912f0a7f328e04fc7c1803f1d0b40fcd950e4f70100fd4343c61852a99a19148a245326a218c49f4e0bc09f5e1bc09f6e33c09f7e43c09f8e5bc09f9ef3d90e0316a080042040880318a000041880000270130b02886f3d007c68c9a0c15299a959a598a444950ad1ddad1d653c7c78451bd287bf9787d6879ee77d2f2f2b44ea316fa779f0f067db7a891efeec1bce873ffb472e488f901fba067d83cc22e745963163071a355efeecda1856ab540a8522c906ea20a121e7f11cfdd3fa01c77817aff330de876ff13dfc3f0f2cfdc24276532346636707173362c8809175705abcf89c568a0a25459e745de779a3acf5e127da902efe1b65305ab4fe65e745a32c86ce87a1e7e920528f753b475a7f56a1ffb322c1f9b30eb5f8b3795cfcd93860fcd93974fe6c2576feec598c3f5b87eccfa68d21f6672d43fe56c0feac41ffab540a85baf9b36d2cfeec5bce9f8d7bf167e7f29fddb3c39fedd3b517d90e3176b20e8c172e5ae4e0b0f8d64dec7a1f6cc58949090a0582a048b421557c188a36e48a0f4315de87e3388e463e1a75a523930926528fdd1d1f2afeacb81f367fd65ccf8a3fab101f223f4574a83fa841c878c5e53543135393c2f4ab552a2534d43c9da3674dab419e0e18e65bb0f9ae5ff12c6f833fac82e53361994fd1aa798969a1819971bdcc92c5e55bf150a7944914c5711c65de8723d1866cf9d12873fd689c51338bf7995e9e1c65304f9a1f964a5e2a75a5a57b633b34090f8ef2cf9a83e5cf3a6bf9b3ea70fd5969e69f75477ea93c64feacb6d69f340e993e07febb150b67455768fd89f9b302d59b492606e6e565ba5c5a58564afc79df2ac3dbb156a6d58a442291e428537d58126dc8d5974a26d328ebde34ce389d4e2894a3505d29cada1b917acc77589290e967da4385a4fc497d68110a448dd0207a44050c47259313540ae579c81ca9d9d3cc5ab50d5d157cc2232631e9572f7e0aeeb00ab38c2cf89527fdc9fd289812c96d2acdb13ca990dc7f82694f6e5279d220b9bf44736279deacc8fde1fdbfb9b961c10247b421c7c71965a4c71967e48836e4fd9c51d67d4e0bd186347d8b5176fa161fba70e12e5c74a52ebe110d9a33b0ca45ac5888d463bd43a2449425533353f2cf1b90e6b0549af6a71415d3667f09a6fe429567cd29c684699ba592c4a4119f26689a2c35e74569fa61a88c4bee2f8d32c0ec258b104724c954eadf346b4e93fe474d2ad3afd3b708593fc411a9871f088eba074547d41bdfca02508494eaa84ef325da6f997ebbcd69ee3b327d771d38ffe13e7c0664a448112024434240468a24f9a9b5480321f1a14cdf8580ac914cdf16714fd279d775a5ddcf8f1caebb7978db7ed85ca66f716dbbbd44a67f9b474f10cf3daf2bf5ba2684e4c8112121244782dc8f1cb1428de3768e4cff368f508724d3ef8ef4cc6b1d99bed74a7cfe7d5de9174469f58113e2d3e3fec3878f8b13527d7a703faa8f9faf48a6ff11011d04bb52b0de8c30c2086b87ee1d67d46c048f8e4ae3a93832fdcf885aab3cea0e918b445da96888d2daed3bd8aedbc1ebc90b00caf43ffa936980427668930247327d5104420fc3ae34fc469952b14d4a76bb8bd65e7a3d4f2459c07146cda251261a67d42cda9035a38c7e38cea839a64d17c5ae54fc46df37a22b7ac26a8d3069ecfbf3f313e5b563949cca27d645c726b924d386b9bf6c495139ec44c7a71c7aa263530e3fd1712987a0e898cca148744ccaa138caa1283a1e73388a8ec51c8e44c7610e49a263510e4ba207e6b0243afe7268121d7b393c898ebb1ca244776c7368223af6dc3944111dd71ca6a854a3ee65ee9d2adb98f7e7575f2489ea184bbffadbeccf5da61fda91bfcb5cafb9ddcd21a41e63c2631d7698090d74be42e460020337790833dfd8e93ea4638def3150a3c85e761847f7493081819b2cba96a6f0eead3ef6706a4c80cac3c3c3f36d80d6f8726d4c00dbbdcd57879b6b48ef082c35d04cd8f17aff7920fb57cfd6c323109e9d897bbf882fdb977559a9fba8bbc480e7dbb6b4d66e210c6d48ef3acfeb3c120d37efe0968c5b5ce0161f2a13cbef7f7a42f8fea79ef05d7c814b1858c7662b69fef2f14d6ce39bd8c637b18d6f621b3366e9e8d76cd602447152bcbf4a952b691e9379d68c357b1a6fc6c5a4797394d1982fd33467fe35e322bee099bff8e66330cdc360ef75308b878171fe05cef91a0c7b1738c59bd8e69335b3794c953be1694173ec75f8b366c59fa6cd9f7fa6485b6e51f690f9c9e0e7e0720807972cf00a8e36fd6f7089abb5c923e77fc2e3311dcab346f42bcad3b429cf57519ea9577992a237cbf393294bdacb7cfb295e268d37e3a27973e6cd5136f32fd33469fe4563be8835d87beb3906f790791813208fb166264226481a46f3fe26430d9b314d806870b9630697351e33b334471936af4106bf9bb85b5a4cd1c4d5c585467b8926a63906ab82c060558f6b94bd94aadc4c959be9e858b9c345a399af97d79b5894cd97695df82c73647f177c7a135a54b9d35c51e5583e780cd9ff4b1a8b35f326ac5439d68c3594fd51587fc29ad594b066b80ba4895693cf9276d2a17c96ac59492b875aa690fd45342a94cf92766bd67544bdd12509f020b4214b40da6603896b65c17f5efa68edbd6437ca2aaa7390b5768a56efb6b66dbf97dee779e148732192d64a2b4d9cad5cc082eb93f38fbffa76de078623592a9528c5194f14a7e4a3ffe10df8eb02fe52831069ec51794213b193381129c511d99f8c86548fb65441ba50f651c69889a8e26c04ee292b115c80103f117800900d0ab4d91fb45211ed4780207c5458967ea5e3cdcd0ea85127e7439bfda791469bfd210bfcf0c50ec7fee77f5224abfbdd0541d0a47bac69b3df3bbfd69a749faf6994d94e825c8df6070932c6feb4194471c2ef9ac7bc6fd1fed81f7087adb5dc6faa34fb638b74ece361fa6a5fedffafbd5df8a7ff78b3af96e28c0d22ff50045a4ab1285618e4ef2b8dd2f4d3ebd77ed816e997570fbfbfdafaa9cbb27c1449964a2b2b2ba5d87d3ab1b08c321b2ca3acfb303c6badb57aa5d42badf7adff0d59467186839c15c140f996f6e7abfde4feaff6f1e898a8a35ffdfd25a3cc4b7126542249e3a87b9227d395ebad17d8a046cc5ca8042e842428ec14926eebfe5524a4a55ebfbbbb6b5b5bdbb6079d362a6dfac9ba5940f6eb38499190970cf4bb170009d999500984b2a5df6712220159b37d8b06d38806a11108095921fa0909017981053a28cb690a656fd166d8891bb8e5da392ab6e1f978af03eb8186cb6c794b6d2a7fda19cb81fce5f8eddb1a8d95f7df9ea8bc7a89adc058babf6953a72b756834ccfbe981c23c283c20d1c72acf4f0528fb874a7892ecef792bfce5b09320bf9f868d823c28286c54066afa8f3ca0ec2f12f29889472424123a43259a3a6d1fb9bb60fb2becf9b62cd1af165bc95049928e611eb8d6eef65a6bdda248d2ededeebd2eb8fb15bed9855982804a25534fcd5a5b92a45f5e32b4f24fa9b4d84e5f326455682bf8f52bdc65212d3c43285409d0cfe39b898909ed18ae3d7d208aa3f2feaad4abd04657e585cd51067e1219e6e97ea81b8378a15a27a1427bc1ddbb6095c7b8f5257e79f9e21fe231f1a0a0bc890785a4611efd5229714d049dca77abd7aab5eaba3100bdc33234643b1731ed60096201c22c3f54c67f94f2fe23f1cf91500e77f10bfd90bf702db4e3afe0161b6eb97da0080441151e2ab6363b09f2fbb08695fa48a5a6b243a5eb56ab6ea5a2b24385870aad4d4f823c712db7d81a89161b4b10c5618d1ed73cd622fd89f293fd698badc546c14f74c12adfd2fa55910cfe69da91fd59b04aad4dff15ac42f3d7593212cafe261e8fb5d8fce524c9c433cba23f4b8630c6f8316eb12db6ecad12d758d60873d589700dd7ecedbcaf52f01ba9fc6a655f65d5a5e0d3b3aa3c59b08073a8d05c483e4d3b700eb3bca16408fb9c2c5d20c2925a6cb866f35932847b700dffa8f050a1994299aef811f7068a679280d2ef1704cd813cc95a6b14e499ea7677d2869329fab743f0b64691a3ce81a4afe3165bcfd22ccd56cf88a5b5a5e5fadded58087db27b90d3fa0541d9ba7b3779d61cf4a980743f55d943317c20fb7bd80ba232fe4d23eb576f77f7200f0a7d3c0c37ffbe0fa742efbc5028f750ee8f27e8cbe142413e5e907f2228d62848d18b74684f8f1b7fe15a240ac1eec52ff526f509c25f5e0e020a9ff0eb56d484a138cafc7b90628f40f8df0ef4624c531cbd91da9a1794bbbd207fb51734226f088adf17966238bc4fe277bc9209f7b73f1a6bf8d7d2b6e97faff5a7b97bd13b17f9b52551047ee2a87bf1362a83cf32fdd03a415227ac135f0b854e24fb4fa6f4a3209bccf4ab5190e7976df77d5f58cb3002c1d0e4a4340253282857adee55b1c86e754f24f15b71c1786586e6e5826b52c458152b4c261530d35b618271b17cadddee6eedbdf7e545a775efb5a2ab56f7aa5864b7ba2792f8adb860bc3243f372c13529626cadddee6eedbdd792ae5addab6291ddea9e48e2576bb7bb5b7befb527afb5dbddadbdf7765f8995b997ec582c56cb962693e9b5b4b4b858989d980b173b31173645cbbbd889bdb4745ac480d12283c68d0b062c40010940801007304001623ba2dcf202001f86c47668b8880143864e8bd64bcb10cf941a4f241352ecae150d796969d1f9cbc34b8cf7bc9b6f7969e9b48801e3c6e4992c30aef16f478a2792fd44435a620f23460b1d1f5adfc3cb4b8b28df0c89edb8880143a745eba5e5a5d5d2f2f2d26ab1e1f82d2f2d9367b2c0b8c66f91ed106327ebc078e1a2f522caa309b66288671a4f2452ecae150d318da3f9a10ad338dad1ff68f24c1618d7df8e144f24fb89868cad77c1b0982b3c6c63328da2fc435a2f2d302e93c5338d433cd37822916277ad6888c91b4793c91b6d091c4ddeed48f144b25fbd8d3231302f2fd3e5d2e29944d95ed66a88671a4f2452ecae150db9d692dd7dd3e974427d6f6f478a27d2271a62bd279d4472d5e1147caf15e56f88671a4f2452ecae150db12e40d1101a443444344474573642c083f2bcee3d922453a9f77c28452281b47f92a8d3ea10b387247f7f5a1d5f0e9e1cdf97c3023e44a67cc88744da3c6e4770e27363691d02f2bcb96931be8ecefdd3854efef4a194d5dbb739680c25f5f6579886bf9cf42798fa8bfc8ba9bf4cce551ea94c2754c9db4f5158c9d2b2f7dd87fe1dbe0101e9b9d3f098fd4ccaf323c9f2244925edf11f478bf3d8f77d246971d93fb4f5a2b0c99f30d959c827048c823c4940619d037992805498f428f87e0a9fbc09935efe6cdf47927ea6f2f9d9b2bf58824940fe2a3f40d69070cde4934827a4b7a3b72f52f9e5911d6a9ece844923dc05c9221210aabd3100e0cec9f497bf37f6787ff977b7b74aa5dadd411449ba48241292cf4f4891ec1f7a2ec1a71f7f759ee78d32f265265190a748cbfe61389280e24002058e1b1aa26968c1124da0c066e40c32a03d4b4c8127090fa4c083c48efff5d1a67fc59ec395a8c235f728bb2dfcd852b39f8eb7244286cd183022dfeff198e616a63954115517776ff706de5849a8c10d2f108a4112206cb8b102153ce10362e0f1a9dd088002361c99810db8700338bc40c115d6607b01146b00b2e3cfc27e93b449f398db7b73dcbd619a5368769cc7e84e3e5d0a34f764f75c769cc7465bff7932da3c56faced2ec249fa30d833fa032fe2a180442a5f15f61d00795f14fc1a010a80cd8447d8150e8b1845a76d008b4ec3f845aa6b9277ab20e99e69e08cae08de274f6077f4071fabb0455b7766b3cfa05e80309d2f2945eab409ee06dc847c3c0200dfb91fd2cdd465b2efb68b3f9749ce31a56ffc6ec0c28e8810e8204e10c5fd8f12fd13c269a81086e40c30bdec0630d3b3764e005320051a24711e600851dff92cd631298c10daac084190fa670841dff8afdd6a6a3e0a766fd145e51b3fe094e51b3be094651b37e09ea642a757759527f75833ec4fe4c73363092cfeed27f1c65d40cde4e4076f3384fbe79ac16e997ff2a9fb5e7de8ee42a92ec7fde9ece01d9c3fbc6017982b70ce2c01f0dab44bcb74fe4727f9829901d3e007df40bbc350ff0f6dddb924c81ec10baa51d2778bad276d9a24dff1b12b9d3848f71b4e589eaf25ce52692eade40daaf3de38cead3a6bfe8c70f70fca1e6aeb936e94e133f1ad6b5dc69a2e787b7d3c40f1f9e8f86f90071d97f342e17a4cd5a6b2da2779ac8ed34d183f3d18de0cd63f7e62fbfdd5b76dc08d71bae3f702e48f6b7d5078fc72a8f7ef9d79eef23c954ea7ebfcc7cd69ed40cfaf2c734a6e32f3f7f00d4f447a1b020daf49f41bb903d0103a0b0234134ac02f406e4597f641f17c8fdf5fc72111677b3652f42ccb5da1097b84011ddb9276ed926d31c4e0eb94719ada51d27686dd2fca34c81c82006e38cce18a851d45aebd766a53254a659daa43f394c89b4b4e3444f9b345bf0d6b0ced64ab5af28de87698ee803b8e62cf6fc612668ae0848e43acaa899da80fc9ac76e8d96661eb3387a7ea55997b534cbbeca14480c78e4f3d6b21351bf773c4c776cc82bb3f65299c6f5b2d03b3a6fe412d5117ec33ca26fd85bc510e4db742090a60e2405a203473e6991ec3537399064ff160ba859d1b057ce7238e2df4e615e7863dd9f285f69fc4b2ae37fbac91018f4755222d9ff522864ffa64bc8fe25c5794165fcfdbfe6b4a834fefe37a4b3a484ec37b90545abc5ca028ba582051595d5cf6a95728594149515542a942aa0a0a488a45227434e4e4ca860625232859212940f0a7592c2e9648a82c9548242a9440a2149d21348a4911346a3b109e328f68862c88430142d4124029500825f90eff392e0791d12baee02b9373b95a91606c571f7ecff82e274bba038f5a605c569d4584b92dad28e13b52aca426843d6fe50f44598aa278b69664a6bfcc6ce4966233bb51d7f4a9e27248a63ff7efd4b71baafdf511cefeb7b14f6c1688b19c410704003a84c7f871980174065fa2d56009599007da16a8e10140d09a03299f69912c26395c7a33f2620eda75068ecc671299fa92c7a0450997e4a653a01350d540a64c85d5f27f542eeffba467e9d794c085a79fa3dfa93ba95a794291018ecc84d03929429902ed8209fb566abdbd17944f692de325db921aa1e0c42ba436dd83200c13f7510fdc9e2540163c5cc0c8c8ccbc59222f79f2eff2a2fb9ff6ca1aca4504c327dda245349ee3f455f18fe6949d4ec17e103d0573f8807f099e47e57c9fdde5591fb2d8bdc7f001a2488f2d4219fdff7654b85b0b6db4b639e8751fd320294533205624409998e407cb987f73a4d7acc2b3bccfdb57af544b6f3dced519caebf6b8ad314a7fba6b7ef105ed051587b6fd70d8116aa4e2655989212ae4ea655c80a5b412e60010b5820caa10515d6d593440209245ce00217b460051d453fedae21206bf9d5ddda7bf18cecce31071fc29fe7894694ca74421ee441ede2cc5e8b047b6dcee63e9bcd5ea76ec8c067cbe5c22ef4c22f1485611886a552e914a2e850ae240cca3eca083311359f9fedb3a5a0a65042d5c9a40a5352c2d5c9b40a59614b059694741948d56873350e399bebe917d99c1c6c10db637336c762b15af7b3f9ab6d2df15ba25f6dbdd4ffcf4291240963fc62a92cef97a3ec7eb5ef1e7eb6137cb625be5bee3fd9927cb610fcba0fc5592d214318865eabd75e0159abbb7bad5e6badee5eabbb7bad5e6badeeb5babbd7eab5d6eaeeb57aadb5ba7bad5e6badeeb5babbd7eab5d6eaee5eabd75aabbb532f29f5b2b082147433f2fbeec6fbbeeffb3c4f241289a2288e2808c11169348aa108e441586b6ddbfd99a029a59492f6deaefb3c59addf2792e2388eb25a5638905da24895564a92a59235555a294ac90d2eb5fed166684dd0b91a3dcc80c964e41d1a3bb8787b010a7bf1f68ba031186fdf080ad3791a4481e80ff549414f9a9b69f3754363df6c4561353533c64b0ccc2d08381fa02fc0c4a0efc763a4dccc63a41af353648af22469ca9f3c33cbaf9a1dede730bb1c837bdc87c16f22d4b0ce6ebef74f61601c303e89df815132616f0d236941a0303206359c8fc74e38a0869d7abe4061a727e07cb2ffc92728fb93721e33b10d79cc04a8a6c634ff532992b4e53269289b18954350247378cc24477ef161d7759e088a429148248ae38836ea1847dacb34cd978e1db3d188245624248da48344a2bd4cd37ce9d831a391aadce9435234714a0e51a24a88244332318a0c69626282ca69f13538e6030b980e2b6c54bc7c6891c32206d361858d2a97ca2a5e5ec9289b2931f1490e5326a7c6712ac931b8dc517b294b5a2b81c248647808050ba18c41e0201d34d2d391354b116558e69477c0250f1e72a1502e195c7764bc64cc9789d2222412894c353016c2411897ac1e568e6563d55834d68c9523c5c5256525a596c223650915904a084b8b2ac7c2c2c2c2c2d2d203c8ca41715eafca794c8666becc1b95495d892fa59c28a6d4c02d403f54e65f84bbe15aad56ab4566f365ae6834951d2aa38c5563d1583a582cdacb34cd978e1db3176cdfc4a44c0363a120a01f2ae32a2a2c96100b092b09ebc6dad13ab166ad56abd56a9d4e709ce46853884795cba98274acdc4153698d720f3566f050e248e2776a7c143b3cbc3f19039296fa93acdd6c34763006faf1c1a1a0bc2ae808ab0666cd5a079b0e5b0a102925252525258564b1aa27882af73ae159fd79c2a3caa972af991b8b467bc1afa781657ec698773016c2411888ca380a55c262b570b489e3c1d2118359339313928989898989c9094995abe13c76c2539281cb9c0c97b81d7059e3e1a19933e68b55d2b09863708f9b77815b80a88cdf984cefe5509904070fd1272412894422d1975f33f365defccac4a14f2657785670ac28b1449b3bda2c098d4aa552a9f42759faf31bd156c00cc38a61bdb0b068e22fbff8b7dccb301ff32fcf3202615ff5e27fac61dffcd0c42ffe6594bd7881c71ad6f4f0c53162c468797d0c4ccd97895de20b9e09afa2e6d090e62eafc035bfba6fee5eb8bf6d5e21aaa839ae4f5198f8f55714d6f2f54b688ec5551aff935cbf44612a5f5f4461abafef1496f2f547d5ab60d7f2f6cd35e5afc8aeaba3d2f85fdadd01da2a8dffebbb52559ad5db3fbeaaa62c8118bf755c5acdb13e7747cdb1446c35c7febc74749766cb1dd96b39becb75b8be069f3772cdd35156f33a0d13ff8c69f9b306e6d65e2a7fb6acfe64a5fc79529a95b9b280b74ae37fa6bcfe44e59a052a53cbd3beeb5fd8fe541a2ba4d278cdb13e95a6e6582295c6bf2b59fe966797dfe5d95d0ff7bb9f71bffb1bf9733debbbc6d7dfc816d3fcc245741e4b1595c6f52aace33538e65d18e643985751736886f9184cf36894c1942a48d791e908f33a6d858cde9f8ebe545a21d93f65854123545309141482c12c983ee0fb5b22adf7b73fe683cfe293bdf5654924fb9f3772d3baf7faae3feb9bafd7f17a7620d7b734d7ab749596d666cdaca73b5898e633cc277803916012e1e5b3342bcda084830c83697ec134575caeda4c15813c2d4d6646a61c6d97661ff2240d7b2733050283388caaa7630d54567dc5174065150d62e66bd31cd227d787f17703795e2017e1790898af6fcb93f5c3ccdb9877f92eefad1ce202e510ddbbbefb767d370261fe6dfbf744657ba280a06d8e33babfa53dbb17fdfd0ebfd47285dd727d980f6910f6657299e6ed873dbcbceb2bbef769f77a177efdc51dbee60b9f36b28b8e40982e1bdd831f7a1d7ef9b08e00a899c64bd9b581bca50dd4f8a1d78d3cc4bcf9e12776d83e1d015073cc79df96fde3d3b146ccdb72889837cb2164deda409db7b481ca31a595e98146a6a4acb208cf33ef52da9ab208cf30df305f7f9829cf9a61fe3c22d78bbbb187b37b1b81ecfa6efcc17f388bf00c539e1ec8e30ffd43995de5d95f847fe31a564b2fc27fdc7c5cdcccc71a2319343f53e3c9333faa21caf7ed08c40d6fac517fe675bce28bedd8039847a78d6c69cb3c6d188cf9f2b45f32e5f9654bbe9016fcd1df7288d1cf7c8f3dd0fcccd7b1c6e8671edf6f7cda18e1ceb47f87b83ff3615847205cfff2757cbdc5dd3786294f1b2f3ca37bfb263e6bee2e9e317fe675fa05bb3a373e6da0f2ccb75c4fdb345db72bcf23f2cc8bccf2b481a2f9469e2947ef34dfc2e70e36b2e8efadaadeaaac066aa6f97e9aaff8b491eb3711c82ecfaff814229fa2b781ca345fc71f6aa6793af680ca34afd31f7444c8c8053b776083e08d06d3fc38c8c81121236d7a5f22d93f6c7c6d68c0e6141e6a9729347297293cdcc8f56f0c61cb5b5e9f219044d81b0d3e6ba6f9c64df37da599f7687b824ca9613f053472442805047f8ab479dec834a84c833bd75156a2e1c813bcf5d4d202a85c5ff4358ba8e8bd2b7b78df8da81d26ca941af553ae91234229d72703360ff193fd16c9fe37f28d4c4719886b1c797d1a4669be3fb25f21d945233e6fe4f1eb281bbf45a28a691611e127532299e6888023e78680245b9aebbd875abfb1fd1f3a1b2db22dcffa1aa8d93ef9b7f1ca5bbcf295b6b95271bfc5b8e2ce1bd99efd4574be40dcafb8ffe2eaa33651696a79cb14b0a761aed2e6dd3eb529c1db57965fcf408a7ef4d1304ad21093d8731e7e0c4a2086b4a6c046a4789bfede6ff4e54ff32739f3672ad7b45ab9c6c55ddcc5e5431b5ca2d14aa98f4a331b4c77da0952c5b3de59df7a3acaba0992754dd0c73541dc689a7fefb709c8fb3a6e9adfc3e6e968dabc10c896e7f641b6ccb77fde9b6dd3e2c05bc3ccf79507711eb3b9dfd9069774f4cbcbd2ec4d5c834660620a94bbb7c1a5d9697133f36de70d13532ad3a0cd062ed1b01f1ddb2142deefb7b497f2559ea95c67d0b9d71290321f6369e58b346d8a3f41a0afb73cbfcf7a79a6b28f444ce687f93adef69857fa85197d0a4c7764301196756b274824fbb7307892e0ed3a718240b23f0c3e8bb02a708a79031de02007cf4e8dbffc3b08e4599aa1640a04062108df53847f6b18e9a3612e26680e68d301857539a8391667830abb34a039b716030ab35f73eeadd2c080e2f8203bce6d0d766ad9dfdaec2dfb9b1f8636e4bd40202fee06de720d0383340cf6bae1a5a1f73b4178178ef9f44b8a433efd158a739f9afe5df7c44f2b53205bc0e57bf31808fec0dd480a040aa0fc7382371f10c9750f480a040a1b981bc205fd00c9024eacb866d0567328a5790268cb4e50d33ffb5fd0084a1092fd2dae8248a8559008190814076c82cadc6a0ee8a3d2f807018b90cf5b6b0d69b5585460b154a6a0a2b2f259ad52a49092a28a824a8502051494949054eae4092727264e30312969424909aa07853a31e174322dc1642a29a15422839024290924d20809a3d108641cc59c288647084391114422b00820f8fdf83e8f089ed70da1ebee13f9e6fae0adad7582e2b87b1314a747a38c6e21509c7a47cfa1c5a5ecc234f70349267197616010400fd31d4a712a18845e41f69f51976b24baf0f571834065fc497c81503613e409deba27df8569ae086cc824f632fd4c40927fff2ccd280eccfbdf1fd0fbc405925f1e66b4790c04427d39d884f10848c8feaf21647fb3ec1a09ba2c0eb374a428e6b0a49fa0e0ede69a40c12964ff5e823cc1db1361bf6badb5d65a6bad96beea83b852ae28b9fe794d4872bc4f6da9be509b34370fc873b479c9965d066580910fbbef7affb0249ac61f689b2718a477409ee0adeb488c5d6267129de2bc695ca24d1ff2242d2cae55c55e0e6dc85bbb3cccf2439648c78abbea639796667734ec77b860e8fb48f2f6e0cda2a44e4c4a5027d39ff5415ce9cfdb0e2e04e901716d7a8bf166d301798e361f6d8eb7d136dec0db78b3efb5fe686b73bcd93f479b2d47077dfc35dac69b0c9000bf7b7bb3e6267bdffd798364ffb691aed2c97b33dddedb4d7dfffb9d7f3baefb9f6a4bf3cfcff527992d88036ffdfd5d09fab0d5559e64aeb6164101710dbb2578c3200ebc95448040faf4b4d9309a55aff2a76321fbe950f8bb908e81b7d389647fcf35ecd6dafcd1b06fbd0fc52e8f0e627d80c4dacc90fd291872b534d742f61616cb63794ccbe3433ee4495c2eeb7299ac9667adacbc4e7b92b63cdd3dbc5f8d2b1fda90e563233279ded4c09c4fbf2ce3aaa1f9fa5dcdb15fcbd3468b5cdf055f1bbeb51e64fdf3d6f2e5d1a683b7174c79a63ca63c3fe63bf056436331d844a5f19fc1a00c067fb419539e264c79fe4bd95d9a75832920cdeffeec59108ad3a36d08c97a57597ee3223a5f1ef65d7e94b9b8609b57de872db1e29b55f8862cb18eb21e47b4d861a26b0bcb4adbaf6f6b12902881853694c1e70a6978a0129c4009212620811076c6254a8c316db3ef8c3cc1dbd9b3db78bc8db636471b3dc1db509e5ea8b4a95c8b82a064a866460000008317002028100c07846192648920f914001472be5e5e4e180874184541104629640c2186000008010020232334630200e2be49081a17149bf9b977d42c95346061a08395c868b7440a6acfdb077563f534a416d5e6e3dcfc5ef89780f9c847d177a6059e8b5ccf2c71060e63b1c126e55572426b893563aca535238e53666e03047f93e23b42e3eaad378327da4c87f73d769f54aa7042671e122422dc9e6c8ffe8ae82a9857a0909edfbab23ef8a9b66f29464be1231780e7f459819e3d3a0be2ef2c7928753cf1f7938f0c4e527875511feebdb86a6d69433e058636dfd35f905b91af79bf44adb6d44dc154193bb6ba74a476b701fe8241c0ec8bb9d59945b500062f1488f6b00d1ee2a4c9a3346c8a488ec589e7b5e5fa53e3d4b94c84802393214ea9937eae22620c322eb6c8658ba8bd9f4318fb23ffe25a67279896da2776b6bb02d73e13770adfbbecc1c887a64ce462fde5e2c6cd7c4a48ca05a28e414b37c67e56d3a31ece39bda6f7440c4ec787cf3b44a6eef184f4ffc772e971dd7ee45839544ee623ddc7624711bd2114a3b871895f9f28d9caec7940586d87af3858990a8e4fd64dba61c75781112205b666dd2758669101050481183cf6db1b53afaa70094ecab594c2041fade5a7487b540a9014ce33ca2679d1c84f00379388aa7b77bda2b2c2d9d4dcbaa1e7d0dba10c13039002b984dcfed0d5ef4dd3f2156b798b7dc50860e408f2be260af78cc48063a909015273cf1757f7c82ccc36288e4d476b1d7761740922bf4c662f8aaf8e66e7cfda00843c7980ddfa20c24c292c5bfb44a879b2a7e2210998655ecdd2c826031b7510fcc885718948454d51c40d75cfde34f06b2b9e1625ba56e1abe47e1c31a82cb348319dff0da691eb14a2e6c1f9418c33e14cdf7b1928e556e4f1293957955d443dd5015f2cc25e792e4a5a9de1cc1490208ad23bd956702af8e676656ef55c69e12a8b2616390bad83cf09a1a8b92f4e61d1586fd09ebfd2e5d94220f7ffbe03705cdd8d83c4070fb01e65371abbebc22ebeb22f4e82f1164f2e1c70e3d1ac2bb4b1580e39e3719bb6da0487e3d2bceb147a89227388b434a90eddb3772407311ea508c16cb592e47e61a55a1e540c79a32d52bd0a40127b2898eab5243185d35e46c03d7bc8161327c7763377631cbee06b812dc4cd946c09768554622ec2d9918d256daf6dd260171c390a431c00a08d5486eb5edaac8e096f813d1bcd9c21f3b3ed347254ad2d07c8fe3d8cc9135e76655ece18590c0299b9833f8b378e7b41629c22c221606f8784058fcaf993ff82c8e13e289710acc0fa8a9cfd156201b29085b115e414198160ea6e2c0ed2748177edf774827436364f252e41583486c0409bc65350ae87be201fb614729127a9b9d2e7373b964a8e159c10549cb915590da2a570b1d76bc5eb7049ac3e989d4d917a61c9000cd9429529c9b948885e413506974758a7973082e2725c39ca3499e4a30879d2848040cbd79694fd7121b1fbfa8d7f8d72363fb90817f948dac04b5ac39e522fbec7aa748a9421deee95cdc3907743be6d8b5882a48f886109f8b6a9ce4910d3be87e3a43506b31044c0fa7c15371d15c2e4420c10cca274c102cf447949102b14534096397fa2f7f713770c00e2eee911c482dc4ba2abcdb3b6b2a5e2a65a603fc1c2e76bd41bc7b24830cc61c916dfb678e253a69a47377a26cf4a798456c530c55c9708e0058e195d54443f34e95d73e542b173036efa52264f7b2a21303d1e92d338008d71643f8b286d428def02f20c15fd02f196c6f14f8bd689b17a851fbe06d9074549b34c9badfd32697b3911ff10ba74b2d67c006dff5de3039707c65182391498caa0bcaf32a75ad4e80668bfeaa1881dfbb51fb986b403839adf832cc7339362fc632aed5b19ba0eade76fdc1e03bd9198a770ecaf0a47184e9d51c4119185a93094736e31c14cb5205e2b87f4392809c45d0ed7a29484a5341716dada8379116ded46ca52dee31a005357c9a9bbec387913a04e3549517abd8037257fb46f808dc4bb08e4db2f479c2e401926cbd9d577feb0a00f77e925d84ab917e8244c4863ae579c8c0cf4801b22227dd6a9193793d2fd15487843e8686481e41b946e94ea918eaa36ee003efda94a139aa0a606396e4888c148c04c4810b5f2c8769dbfb6f70e9f89b26861d2d482b8c8882ca9be8aa118c93b983afba4a3bd9d20b8d851ffc0adade76f991b8242d666094329e7ee727f7cb1c21909afd9932c1dadb02176a92396dc30661c6f3c8923b8cb38ef6e5233a7d07bdd1562391e53108c7fc78f4c4d25c1bf480cac4131ab4058da74425721943c2e3084b48403f6b6da0bcf55282520eb3c0d4e810c0a3024c2cfb54769a5ec0cb5814291840d12a2cc095b1f2696026a41d0fcc8ff783ec31a5f24783307dea8baa0a1df0bf217e6f1080371cd20cf8dd8734692c0736d9bfdbb7873e6fd8c6633eb777b2cdde0c10f6a9e53af6e42df59881c592543fde89eb12f78bcab78c3d8139e3bc7eecdb1607760500d7c8aa91369a7c9f66ab8a11c012ba15d51f54a035d97089e85dbe447a063b42efd60aec43d4ad0f8ea6d9dcb52491721dbdc6a3fbe4e2f1e1f3c0bfa3a60c1a4cdbc32d2edc431d63e2e4b495aa53876f86170c0fb4b0dd35f0083f5eecbf0bf4571f402e644738f5648a804e5ea01fe18743f81d89fcd48d1272e0a3f68d9a22aaf2e61bc53edba86b008374f72b611a3b0ce6ea514602dbafdbfcfc61191cc340be65ef02914ee61cb9ff6e7b398481a455de3f3ec89dacb1c43c255fc19a184177b76e0796ad01583ae1684a2c072c9057b0f194d569d7892b478299613eb4ce33679752361452e0ecc3408c924923a7470b8d0375ad67c06496fa52a8b5e130d8bf7c6323d56afe1b69c99908c40bb09647ffafc98be5ce572f3815c2022f81ee92550c999829d85760ecb23309d0ee9ec2d2893a3e368ba80115d4d94deb397b49a39913c5baf298b1d59bf917d9765e0150454b1402a6aa87d8a6c9b4fe16c2aa335403fe43255cc408ad38bec8e5c04f43609b5802370f40a3baea19908a7debec67d6b022c71bef38a313ef20954c54f869e05e57b01f51356534f5a5d3681bd528abad2bb1966560f6e777f5a5ba5d12db28f2a05ffe5ff5505bff7b96648b1800c407d544a4ae3577ba0e802fd9dbda122ee4774701190a7035030600e8b485d4ee3e2b17c4e81ce731bcf9031b7e79b01e333cc4e1c02f31e0ea20f13e864d0cfb67e0e69c54451bc81b8a4579c014b177f456bc8d9d84e7919b1f5d5bf2dc9c3f31b96116e2f00bc92b6fb3285247ffd604e5db99ef92b6f6d1bf0d2f19a38d4e6b373a084218b16174489b366ae5e0b6a25dfe980ed9546f128c5fac5b4397a060f8bd84dab765a166d6fe47e1f3a5f48fb2d26fd2796d4bd84b35045d926e6b41fb5fda7067e12975e1dc1e3f564df9c6539ba1aa6193b5acbd949e4e3c7b9a0ebe2c748dacea6e2199edb057e6d5762529fa70eab42ae930d39115fcbc63455cbcaf9b3058f637e5a1dee02786736e4a076691b72aa3d0af7d7163c87c19544a5494522222e6d954b9aea00d8ba669cdad3db2ca5ac2655b17d88db2e5e4bbc64ad34f3b945b8b519172be698b99a2adcb34000f46a2d948cc2644e137d004c84bb3f0a8d5121249ee72ccd809f9298a187f8fc3bdd36872ec4c359a6c9eafb0ffde1c70fa1f75e9683ece70eb53a18601ba6d32a936ece38a9530c9a3634f7a449df5e8335880996f7b762d4e5858339029ad26cb7235e3eb7c6ed97270188c4c6abcda10a301f3f1244af05c6f41fbaff9c1e6cc2f1b415edf0b6ecef703b5cd28b945f7d02d7f2c6e9b9eca596e35098b7392d6f36ce3b03819c2002b36ecbc8660ecdf69e8be7e8204c71c575dacf26446716a3f6451d37c2a42dbdb92888d10e7c49a676274769968d32b2e18842888895f4a65ea5d5db06f48ead5bfe56f1aad94ab6e9cd6b075c272578a250d69d4b00a4440329f620d64dab6d31a4407744198970c08ec0224bac204ea8efc8d55f91ca6df5a88b47869b827279ba71f76cce10dcb02600d81bd9c35969e18d70bbe9eaa96c06944fea8b5a8d71b684166dcffde5d95faceba683958dcf520e0f89c06ada0c3b07ab8279d03a453b21ba9b87174312dc4c8ae2edb0917cbfcc7d3ebcf093be5f62d541cacd917b48e404b30e01de613a99c10a8ab5b13524f42b2c44d8ac10a06e6e47d88da355cc6a74e89474ee9a167b430e05a84cd854c2c118fea367f6772124ef4aee8f67fb7d8d16aad29ea93e73769084162c59ed09c561d3d3c9c5cf32361b92df24a81c61e08251971c3d349470fc8cdc3173fc08dca165e240c0076ab3376523bc58b83324180f91a1959659866c8467a09da36677790a2b217c5f0ee06f1589ad96776d058b49e22af13a8114ddec9d59a67730e604284127c8ce7a6122c7f86a1030cb4aef0244395961427c533bece3556435fc89275e18ab93108d63cc975b97b583aae0cda1a930fd34808c1ccdf2a344991b9338144d626045adf174a0191d7da065316476053577bfc8322ed2cbb568cea93e8b696934adf7ae59df4cffaed4cc524b2a05579a3009e320858ab9ba4a981b62a8f5254215605f83226851da8a2e45e64852dd41105d6ffff3ed31db3617c660033b8f84e8ba28196dbae7bc689f17d8fe857b2970d49870da54e8e401d254ce4d3bb0884fb7f9d976eb19d694c24f0a1ca314e6683eb15a53fd08aed397814ff896244ac0d0c2cfc9bac889f0d50147e2c957cc9bc37bee50426a1b6a1786b8958895f978a557ee66d3708b2ecbf9a3b8903ec25536bfdc2d9f64a82f477b86cf54bd23556fbce4bde900658012a63d184009cb9cdfd07cf1a770ef0980fb6263950842f3e92bf9cc08220923489ca9510151cd36b4b8c4d6d93a1a5b6c17a0da3b4a553cc88f1440eee21310027eb7a47c0e5336471d9f295680d25ea0e0f663f540a1eaf99cfb19bbf62a3b556e75e050cb1e777b413f48da4a8f7b578c50beb0a898d0c3cb0103299ce01c238b11411a7d06e8c1f25d56ce6f3bebd3f5e2fd4204e93587acbf7ba251cbefade6534794b5c627ac9182069b9ba57f7fb7f3797b8973ba429c155e72301ba3361245cf6f1f9f33b131a3ff1c5c2c31b611fc483a7612bf995ef1d0f17d96319c849e7ee95ad9a4613b4486037dd7a0c4e506dd0ec9162d30f7d013d0cb6b14134788ed32204a68cb4000af6ad6caf81adacac68a3fc00c9ef874332a2ddc669a2ee5b2ed35d82ed74b0013d223c875a5dd6544e9b4e0150cd8c2df23ec36a1e9563fa415e0c88698801f0deb6e20e756dc8b43c929f14fb45ed5e8d4a7cd307e151e2c4969927e80a153607b8937b39d3f147b0123886b2f52360288893bba91ef50e4e9dac770c3f927d700248dbf92d2eff4aa5585085b6ee6a55d2186f8d77675de92aab92b9e29df4f88d1fd9964506829eeb8090ac0d02eeab520be43941c65466a9c553e5d76e1b08d4db446d671154387c5dcd6e53647dfc0e6fe0df337920dcadfb0eb66245ab9a8a8bff32398029a718fa856af5414cc7d8434fab2318caa6ba709c25a9b6b11fb4dfa8edd864e9832a11561f0cbc60a6caa386f0844313ef4aeb31557fb62bbae093a929fc2d25cfc5623614fc465e80a7b9bcd3f1ddfb56aa4a7855588930a36b860f0825aa93b35215e50ce86d749674972393629b1e1463927a09f36907fc24bdac0baff43ebb5d200d44ac46c1c92085b26cdfb7e5da5212bcb81a5343e8003b8b8c234932bcc1f385a5c747410f7f30ad17d748bf96554ad97afc129df5f1564751cb96586f296a218dcad6a38b1886e7905d4134b4ef2a787b8daf5867660ff0fae41dbdd245320eba0e014805efa09b925d0a6ab579ed9294fa7e7d575282e154b9b81e6d93a8a6429fb540655393a4db8a881d33e8abdb65019778703071fbba9e627d1081fcbcb1a1a32969f09257b5ed9dac589da4c8cb5f7fef93e50bf77043092e4799ac172a0749c26b1354f0c31ea3ef5f690eb256deb1b6a537b17f7d2c14b23432ece0d6b9ff96f16a44005a9b3a1a20c44daa33690f8979f5e32aafedd98ad69c5425f3ab4a761ef52beab386470ccbbe66013812136fb09ff20700f26c1c131de8a385343f5137b18c27323d109ec3c8deacc163684b247bd1662caad3000f6a88efa0361e95c57a0f03b6f60a7916b0a0a0603985f71706fb49a4e8dd355927661ea4096a5337fa365e94e0d4f4a35eb2e49a6a4cd0964c359fcf9ee6b646f454ef417ecf390c81f588ce1f522197cfc8b63660feaff30ca842a0ff5a1b492e85173a7e2e7e661977f15b8d6107f060cd1add887b65cf75c69ef7b8fad10140179bfd35ce9e198f025665b0ab8ea9514289b45a2b12d7f4988e04ae263107d676eba5294603bec57b0543d616cab6ce74d2978a48b6dad13ef2d54ccc82ff179ffcd5c2352f5e9430e4927450f416eb0d8363a5eb2a9462bf01c1ba697e4b0edcc9a91c4b06d4a0af68794374c4d85c9e99bf9e6fa34e0622fd70463649952db89ec69a4769a31aee8b3c3be3b9029e8756dad4a49524158fa1862df5e10cff544108bcbd7f138ed4f45e037c61605716bb6f0364a0471d3ed3088532141b3d71dbbc69d22ea1815496998d9ac9c154d4595a489eb86914d8fc5ffd6c2be03cf4a6c6b9ecb78db9b4745d181b5f10ef35b7040ba9c275635e7912e2793fbe63ccf512bdbb305334a73c2dd4ccf7ddb5492a563956b39779ab1a86d2e815a6433ac8b5087f9a4fed8fd34f5535ebd5c817791747af8c778ae7d3b7c62535b236ae666c07e8248bc4d631b8d957d60a74e5e4ea5fc55b35aabfc6a82f45a45b767fbacc572e12f99a59282c9f66a794e7b100376becdf71ab206c82817c9250688af8dd2651a6156f4e5194111669366ac0d8add23c4223316c7318055b0d18f5e89de6d2eceb712aba0b8580bd34f8a42b41ae4e6de7e75565eec95cffef465fc399e868cb5db1447d435d826970783f08d7823ba8e3c9992d99acaf200122132187bd00a154e0466e4ba05cf4f780233d17016147cfef24258a1139f5b041223b35cfc0bb14394919f3f1c34bd10aea658eacc84e295d15e97292a05ea1f9e254399fdd45a0525b859e723f62546e8d81f3fd86adcbaa796ae819bfe8b39ac4e703a4e38f1867975eeba042841a8bc85564924494d2911e7d1b9f9341216dc1950a86c12090e88e2e859a8fff2fef49f28615b5473e12574c845c973b3acb9dda8f6619a5ad0d354327176686949eb11eee1f75e60e223eccec9b488fc1662f5bacea3b42fa2ddf55253b7bfa0efea77129dbf82ac6ef7313c9bc53c7a7f22fa3bd7747cbf467ab2d711f4b7b636265b9975edc7b43c779bbde587244f41d3b1c3f4e2ccad95f8783c8dd58363ad5bd0c091c2ef673201fec71f88101714adb6a418b7063e13918c84d8b0b87d12ad24682b3d5e820fa25c5a36a4dc1cb9e62d1ebdd2cd3bf3d34529b57cb4ca5ba2e94d5aef319c8975c099fc55773364d62ca51e5fa38330f1e8d3c110046cf4fd30f9418ed0fd03531d0e8cc6681dccaa9702a3907c82497482bd6c2dae3646da78088f379828042b02082b0b16bc3405f9395ddda995dd55fe6662eac66c0dbcec86ad8a3ad41cdaf9a63ab7965b65814645bd73fce6e73647138b70469771a4b739419d4c0cf4466e2bdc6855ff159a928a8ebeae6e2261b6154010c773b7472f1390cbda018fea425213941c2cc38088ffc2add409791cb25788a59d594fa14828509b97beb5fe6b09e1e596aa6abbe5904b5bcd7d3ac584f2aea7c929db96339f6892aacbe2d2e104ff4f7d404e7fb57444e464d95f47f31f0944f87c374d7b1ed817154e7fd2169c3af1448f3eaaad074fb60f59f45eb307c2bf8406dec41b50586e7c5b190c639aed21c423d2d1c9d2f72311aadbc7b14efd7640ad61749b52ae6300555eb8ed66aae199acc87dc78c9f08985a2b1b873cfb94c6190a561bdf61c756fb59b6096755fe8b20dd06742c66bbb49b6cf4c7e667644f63937b2855bc4dae91edbadcbab1cf07836cb75a6ca370208f8d3c975fa20b1101e85bece5ccb0907bdbad7d269bb171bf82344f5a5aebee838d7cdf363e075f3b33f33382e03aabfc7118d6f15ead3a816209d3ea0276085173c32dbb3cd3ce1025c800166cbb75dfc386fcdac17350fc4c3de7681ab4854b24f7704537dfced30227da9a46929c79df33870115a75756b3aa384708cfe5d08b2848cb120ec537df24186cb4b9fc1d6203354e4e4034993cd26056ca242066ef9759965c40599bd0034a1d5811a658e146622be991795f7c05ae88858b218fb2e7f70387920e7669ec2d0d3e14a5082e52e526ebbbf8b37f7e45324bbb2f3e7ee1ab4758a96e347128766183494bf79af8dca7944014a9d9d3332b10002597843df86fe8e5c6f7ec0b6dbda14fef20027bbfa782ddd495b3e0c8aa46848735d0d1eefff9cf7d3d7bedd9ebca5e3a3195f1be405713bb3f56951867eab8a9cd7c33b095a139d2c46f681bf7379b2a4c9c76dc237b3dd39be1b5ef4ff8915c155c66fdc01854ce91655390dee14eb5763f8544af6bfcc813b0793c036efed7d4caccfc8dbb7b072ce1487bccb176e87927edd3d62073d06038b4e451c9aeca2d383aea58780b9e3f0164ac85960e16307d764c42c569fb721eea9ec76c1425ccb62f3fa4643a4911cd88c6962f08e6f17ad3efec79d8f78945ed0e74eee0f37292d50261756ee861e05374078b2b7c1e76ce8de785570332c3c07dbba8ad25fa0f9199b00bd7f75e4cc0ea331edf378b0bba51d3ea63b517cac1ea7b957f0f3a45f4afd339927f0537e9a87ec4fa2afffd37bdd04cd3ea5b1f85336238f4e32bae04a62fc10d5277cfd5a4058a5e7f2864d960fa2adaff986bf1b9c5ec019733169c3ed20faea60cfb9c6418b14733bf61075922103ebc83eb3d21186fba397e5bc52bacbbdfdfafa9c81245b5ec122011b7af3aa0c14ce30cbc06cf33af03e0b0148c7f97dece0eb02cb12ae13a3563c6d1c21a07f3488633e60a1ea2c40787a5ec01a0e810f54102060cbd2b2692e5a3b6b8e86a8296f15a2ac7222752acf05cff260f0a7fa79edaf0d5698a3c93660307fdebf6a2da2f94b39059bfb57e96632f53d229fa2ed3a432d6124b404c28f95e5489c17d1cb69d92162c677b03f33ca17d0273ced5b6cca3448d1fd6dd63d36eeaaa6d684ee60bfb1ae3f666a6d3aea7dea7e4bea4cecc36ccf8998324fe9142eb4b4f9207dc3c0bbba6fc2ef672d2e92bea1dc145d59a6aa882b092a1a49daf04c6dbf53b367d35b8953b4c6daef04fedd96050ed95cc136e552da1ceaa06caa06b40dd0af792290a2de49ee90da173a09714f41e4d6af7b294c63905e1734caa66a1990dcf2ba21a15986752fb038268aed1d29b124e04255ed4fee85f8c115188f7d7da4b894c4f53cae4ebbd26cbf3377dafa25d86c6e2bdd8d9adfe8065f0a9442c8145b83532224b522cf5d3c31107bc925184c68fb159daa06d531d0e83e4bb77eb23c0579cb565bf7db03d913c63cb5590bab849209e7405a716d40d8358e99e91bf845896874cbb70bff190f32bd1caaed03a230b8dcba8dcec7f39614c0ab5b11e1ff1d70e2cff8b3e2989f98d8608e3c08acb98f27fb3590b60b930b1af49a53c62b65ed13be8509e52c8fca11252518dbe8a1c4ec4a3b4e33b53c5dcd85dcf3c4d0a4adbdce7d558f755cccca9b3b74d11c8a338c39a0c16599ae443a02661fb6a128fe89aba3d109f95561796d191f1e820afff658a000b1900c914c926a5aa9326d57c8d7df94cb72ef5b1cd19a05f49da4ae898522edc6d0ba6740ab526fc1e8ee298dddd48114d918d52c1b06a24eedd8ca5174fa598d21de647dfeb3573af4e2a86c9a206d74935ea836255ef6e4cbd6647c1986d1594b0ea00b2a7f1f3b7bd9b3ab92e6e9791dfbd234c4b31a4dd51bdb74d564cea2da7739019416cf25a677ed3c005701db800a6b5d6cdff9b5ed12689674aa1c1a73194deb2d85fc05cd50fd5205bb171f0a85c0b4c43b436283061fe4d6051dd27b3ed139c6615c02ae28275e69aa0e1b5cc2da8c744f12b34f817071a537d31ae872cd3b77aafe5ccdf00b51995c80b2c28a22e393ae84bf57cae478de7fe78aafd95a022caf795ec90b33dd1ab224a717ca6d24d0cc2c9fc8aa87dfc0a8f7b5026ee3a5ed8607278e3868d9b6eda07dec3f6a8ca773daff7fabf29770d610d3d22507aeefad63323ee0078e7c170951aae3de94d5058afe7423359c89f5836ddd0683745bd8d8e5cb7b046ade014f913c4e9ebfe1c7bce4674f0fb257fe2daeab7ba6098247eb015a0b22c92384328d2a1f50778feb88f1fa3fed60f21cf586bcf25bffb870a5292a8b286f8829e46ac793c56a8a898a64ac19fbb5752d2f5df8ed330e8d54aaad46b034d335ac4d111dd32dc3bfdb1d86ff38ce69ecdf32bd7b3e5711772f03ecb783ad9c7a5c32da9f2236daa30e47d8a364f5d2274ef9c15851fe1e9c4b7690469316e4c348c45d2abf22d44f0ee37e9b3bfc694932a5def450e00796df2bd101547e22c7f8f70192b94eb1e13055222e66c72dfd65848753df32551390bcfaf39732687ad00780f3c798e02023afceba776fd6255d5e7e2050f5584476e0124f05609771d4491ce9472a9dfcbbedcd3c9a212aca66fd34726ef451621819d7cb527dabf81c52417259ec9ff478e99c98e65801a84bd79e15b6564a0798593770675a595d77bb54cf5bec1746027b2bb0dfb029eb4eea4c1c1e4b9ad9f6210e35b1df3b5c5c79d75522a2d1b226cdc4cdddd314d4a3e287851e561dd8e75ead054100cda02ab716340b40b21469bb9c09bc1f723378c2f91ceb23f89a317b7f9eeec593bf9eb461ac8c0fbf3621193b2382545234aac3e09527a4f91ba84e41ceb4950c47c84f154a97188109b47a6bef945311d9dc5ffd71c33f8f3d6276d74a633d1259f30c1213a32e552b283fdc4902f0afb62c25ed04d5d105d329ba9fa66e50aebef68bdd953983bb54aed0278ba8c81cea2c2cd0580c7a8a83b5860d2bbf96d21d74cde3f73e205a65ed888e968e4e1d52039b7389cad095eb8573e04fe572add6f525dfd5859f4afaeeb34daf9238fafa374a52ac5ffb142f503de3cbf98eb200f0c62567b2743146439c2208b8ad16521939c5345b8944c39230f45f0cea13b1b97d2c216318310561daa76839137b6b37f272ea54cd0c4c7729dc9bc9ea7d2e74fbf974f41f9402b768ba9345284534e29408248a7f499017c50ae89ff39554d56d41b140fc11dc7e505ccee89d500bd36ce090cc89ae03d8864114dd9ad295b51f55d2c38375864a7109b1294981a908b2280a2acfd15e6a12a0fc7eb4789e977dce23cc636661314eacbe1e0a26a3812836025c23adaed11693f3dc9def0adcf235baa6d39e788fb2d0b72d60a4b452c446c405ca66a08585e7f75cb62fa4a4f62648fc8d9498a7a194aceb0d3837f0150b004b5d8d8b8fc8c0da428f56c52f4bc4a6e9126113c4b4a905284d95d8d4e960947c3bd681aca8a9b590acd0c8a3119b79337b5ef1d635ab0ffac9e691ce665be3b95b2a17872401bd474f45dc04c83d4f918849565cc75174264fafa37c6877dafb263802661ac447fd44b182a615c677ef144afbacfea7d30439f3391d62052a265043503a486f0a93063375e8a937cc08b6485eefc813b177e52c63d69e1bc2b85f613b9a4cb91d121a0012793374dc4ab3e179b9cd92fe553c33df00bc98b24c7299bade13c29af8af93082823ffbe1ff06b9725defaa51bfb6c2b0e67931b492bd7b441a8cd0a42412cb561f26c9e8724452c4c92cd9037997a9cac245a45436a43c47a48ffce21ca807674157336ae38834b8488415c9b02a3db290f4e04a15e94902cc28ca8fe5915e3aaf8d893191263fd253387c53003e3322250a8e48d9122b7a4b9e1f8c485132dfdd14f2247ef22eba0ccc54f932652dbb4d3965b8910ffc2c6b616a33136ce958dbe672180d973a3105e64015e3458c3effe124442f39e8b013c1c19a3675d26e3517d4a2fda4db200a6182da5864173209e5819720b213924450713363e3820e09001d82e597d6d179b22b5006f1008e9765c4cd2b9823a4216929ca8969847e140a50e78e32e49d60eecc006292fe156a3982709413ef3c38e916f521b14ba50715e059325a71122b66fda4a6c54b44431759b34004bcd104760f3747be6aa1e776a3e126a18199c46150e00d44d0717cfeddcccb345ad9b39697577ff11227089db9f517ce9f3c99918681b370603474870dc0b18dc90b1fcd8fc970a727015acc75cbc06f70b05cdcd71762c4e54d02e540ca99fb12137c6e8155e3bd54993ea221ef16f5a3f026b1290b11de7bf7fb243c1282836e5f002671276015292522d9f94df38a87d0d23df81da13f8a05ea79c0fb5effcd8c57e9d8d8f784b8b20d63adf0292c6a8827672cc8bfe0ab50d5edeababa45dc5a34de6a0250663c0ec4bcfb4387ba6591a431ea67222c84bdab148a3b5698f1caeeaa16bb81ba0ece9e786ead99439450ef00b7e9131eeb524183c0043e30c17363fafa822ae4c5447849371083774f311825a4cacfcf3c0ade5b6fd0847a4d31356b43b201e6d5cf442394730ce8ef7babc448a89ac345ada3639030ef881e5373ba0a5c3f3a2b5e8026d93e27c28f8f6a1df81726432104c3a71a5fc9c47691e867e6b48a94047b8573742f9a1a50b589d955f88cb4435c5c7607a7acea67a225aa12b329851b4140eeabc04642a0a2357e247c14a533a602e0698600de401dc63f80a9f768b01cd592192e0e930566d4eb67d889a1241d6353d304198a3ae12f31c8098bc80f8d385000dd1eea205998a5c7f637e3cdef5aa423f3ac57e3522230281d2948af566519f6ca7a50a39959335150c9216fb4a4286cf3014440ac1281f8761e299c4eb8c63d7ea08406857ee8c394cfa894640f093811c0be7d5c7ea12b042eaff1bc9a82531253eb09d00265e21034bf1cee3c64c6b2db98a80d78dd5021e96d138bcea0bdf7d15c47069f614247ba84641808ca4a2444dd49011244630797a6459128298e82e0c064be9326915e44cae319b380216dfe6d2dacc6e9e47367b74740a3798454682518faf64050b1ab63e1491b715bbc9963b4c795116d1348d9c0008aa59a1947cdc9a072b678fade3ff663e093ac707f2120a26932f2accaa1ec2764dbb1636dc87bc59c919f01a2bae4a221ac1ef6d6a0bdd009d392f383495aaa5d48620d2cb38248ca0c2cc422df635af3908224fb9f1878b0854c34e4b2b2786d24874e3953c7d2df1cd45f8111eb746871285b432b4aef3bd3984487bc110d7c8528ea84f7b124284c202ad17ba5ca8317dbeb821c55b0f8aa0d673b0db42b022a24ba5fb346fc5de49ad66e0e43d4b4b33bffb2a50d7e4f6254dd99f221ea9548658916dc5214b203f5a51d29e59759b34e24a5557548945942ee0e90371d3d7941be8715ad7de1c85648d2f70435573e6008e3a1ee05d2711b9229150200a3fd374b9ea3e2948f13e57c24ccea1b81ec4abc650aad1c92118923edddae77a327ba687f91fe03fd072de85e6b0d1c89cd3f794dce0f60d77adee284bd5719565d69500afc75a7938455e3121e1a60f1c6bdaca315e13cebd15aa6dad8868f56bee008b4be96a4d6b72659ad3d4ede242c3b1ab16efd8dd87a9aebd682bdc2de6e1d3e1297aa6e3c9e560961400c32fc8832fa1ebdcd5a17fa9f5cbf1b81d06143052f7f580aa6ce19ce38c23e78cb2058f80c9435935e834c8fafdb0a2cc847e18c0a298e715fbe4d15d2f0e301f9dc7add2dd8b9dab2ad1f2126bedd5da72301d9c7735bd9a87315655df2e83c592ee38e9b7c4c9447df32e39e0fcd417c93773caf01b771bdcf527c6834903360e71f60a7b53c8b660b8e71f6e724837950620ba1aaa9d5e5a05c72255c0fe32ef04a37abbd5dc43cb440fff253d62cc8765c065307a72078abcad8648be2432eeea3b82ee3f5a16a9910e6a4a7596dc9915b1318b50042bd5fae4527e9fecf19e837f9a3f0053eae611be02f04b843af99572c974685168d155f15b0e3ae55eb9fe9158b95ab714e0ea915bd2f3643189b510bb455cb72c816b9104b2839e4eebfd22967d9e192e424bf57446d219fd1057e9c3e8509aa6e438ce20d1ccc2b0a7a000ab147238767ca2fa20cee3c893987228ae6a76f4a082c471cd82c05552fc5af655ee9c4ee4b418ae8de2048a8a4e963defdcb1bca78bcc84163b1d0a702dd9634adf44d564df6153aff172f5b103c8e0035145c71be007ab61858ead72be458be238220563e672eb57fe7e5865958f56b8f7360a745807e551aa96511c319a71ce2ec559afba56f6eeae03a90b2625f6912614db9a234de0b01758181ed045dd2e2edc9c615c549e1eeafe4ba1b58dc41d6d85f88ad53a8041cd920a51426d1819b3ff6f7b179325a83cb089b03f3fef58c7a5d0dfb8d905475cf7caae640c9cf811dc704d1a357c3710375705f191dd652dd84cf01d1852576af21d8766e66fbdaf548d1612d18b84b23f738bdb6d2e775194a7d1cab13f81c4ffcee7bbaaaba5297caa7738a4f47ead369666c4e1ae3a71344e73cb72bc1238d28def03f4c1787247f5a0b62cfadf49f5ebc805ad25526ec46ee39b14c20f20fd43fc35613355a95227acf6522573657101c6123893c034df5ca09e7f5706abf2a8d07c3c9a07f4d52c00815e94f0b86839a04fb973bf63e27f891dbba28fa85940a118448dfb83a2328b448deb65e80c8c23e9159f352e528457bc311a48d8679958a22ed76e57add54a13311f6e6640e3628b8e655ad5f773e7990d100e62655ced290949cd5c8b9df2ca91fc024b43989a612677197aa109e0eb2e11c86b7e10d88206742c39ce5ad5b1f4687e493eef280b2fa6c790306407608db23843d89ecb67803d5f3f2f5ec7fb0c91ba8449b790309d6ec8c73295560d44cdc3bfa365e2eaa71be768ff997c34cbb967c55e26b158d8b85f28a19c7935e40bedb2de7e075e507d0c2d6436316c5b5721adb03a1c68bf218141c94453940c3bf167e22bdb47a0671595049806072b2daffc646301202fb6141fbc694d8c665214b4b564b05de72b929265fab2f51d7f434f08209ab920dba5faf51bd2a2c2b117c28a0aa40516ad6696db65bb5d46a4c5660885ed276ee79b54a067fa8b8d5981d1547144a79dc0ec29375a17563e9a6da8f3a98ab56aaf422c6fbf4757ba2c43ae5521270fb43d1d0e4c010e7ce0de6f1c15880ec527acc8b6b502bae83ffb8a9c6ad361e36606e1eabf8ef8a42e76a304d8c0305e9c6e83e51881f1a4c0135bee5b111a6e00007bc5e561c5fe8eb79d28e1478e9d48ea68c10d569e361e1242d6ab8878169a4636ea13b9ed40819c3628174eda31468dbd471c74552cdb66a2235bf5adbf44ff3e46b1b7c5b8d35ebb3426212519d8fe3564771b972fb1aec77b07765c6bd7d8e07da24293fa456a4e91c01907b72523572eea1c6eab7cf30c6a0b4b83e88073d99306ae05b32b747345c36532c7f5baa878a65ebb20aedbb8666007144263601cefceb17252ec9af8a3f8097cddfccd374c8c1d3c30da0ceaec22e9842d3a8fa3c3da28e8cc23cad28c3523b6f224b6dd1b020af213f8b9eef149ff90faa6d8662a79991bf1ccc88df2446fb35357ec83768fdca50263382b42f46ef612e64558c32a576256b9b1b4a356288ec4bf89ecc87b8c1c3dd26790fd6e92cce696c8010f589e650b91447518a59b95324569d2d0f55d096151d091b57d8bc11d5b7507e7032d93e59b88ce5547be379dd26769977deff0895c92fca2f7b2bf8b410032482ea90aed3c7f45059e7623e02f08a3282abe6b6779fd832d7d1d88ba6ad449a54cf162a538e66f456717ffd48bd6b466e5077f0a5ec1e496a5b24bb83e2c14a9204d250b4bb4363623f992a29ba15c7fb145c25f978c300d196e228c22431df80166923b8984fbe4d8a4e97f13e05b5493e42534ed2521c659e24fdac40606d2ca8139e384e800a251f0c677337331c3e667c2594c4322be10e5a29fbd847fa06d0fbcf203dc4327fb1874809bdbe3bd556cc610c3e8a07aa0ad67e332da610bf880ffe41bca914efd31bc2d8748f8d7241037562df237983813ab1f4fabe9b7a693d3d2184d1f92d941774239addfbc949094727c5750eef086793c2ffaa49e92d79208c0ea52bc70e9d93f447e50688f180fb28fb43f17933b9e3a07b28fca1545f75f592b0fc9430887a32958c91c4b8c741aab5bbc29758b073bfa5d6bc3ca240bc58ef91bc51604eacef49de5860a26ec33d92bfb23af629edc16736a55de97403383a5016cb2cc21d1f7ad156f98a715895d0fb5e415fe70f01ceb87ea83a5cbb47b598427c119ff7256e2c705fac35009fd414e6f3d3d9cc7c2e8bd495e8458d9f5669e028e1618dcecd8ec1b8eae7d75bab0473001f5223d0287ad44866135089353ef080fb28fb43a95e71f5e218cf9530c22a1e58da2f6a4dc69420d47a5de16b0cecbcdf52695e8f48205e6c6fc538a929cee7d3d9ccfc5c16292bf14bfbdaaea7bc5246ae24478468b7e60040492ae9c321e724fca7b4ea8a5c2c8d6e02bf3beb54dc21c010194c95a1da62a0c504e297f1898ff845b53c3d9fcd4ca73369a4ee890a1c1ff1a46329c5c4d2fb6063b1d3263dc7d2a688d7fc6e1a13a2478fca6c02801817126b9469e94e49bfd196343e8889828966a0863b422de36107bfbdaa7939821b5157e9696958e0e7b7766180a650ca6f69a951c455aaebca0b97cccd1e655b05a186bfa8bd3c5ab203f8719e2aac267ef1563945610a83302b4a4b4bf2711f282813900fc61647bfad1ac043e52c17acd2f2632163a34170dabed607383ceb022b788ad022ba645535ba689ca6ea11995b446944af896093ff2f37112797a192e6613c4074dde509d1a392888e7018bf9d53086d44bfa824fae158897ee80f647766389970aeead6db64900498d4c93ed674ca8dbf429acd7d9ab2e83462ab6f76447de1a2eafbf9c73a82471723500464ca9f9c7d10288f1403cda757228660a06b41e6a160b3a94904dd3cc31e06d609b1ce417b91f9048bcfc39b6b8a9b16ab2e268459630032f38b958498fb4e348d150ae15825b02dc677a4c5710e5ef1c6c92bca899748f889de6d957734897af16ee33f986d1e8fb8907b22ea635ca7fbaae171055a8d478aa1bfc6fb73a73a41f2256021a432850758c5729c02049ad73e28ad6565c893f35583911116a8c0d4d55c84188cbc913aaa67654d88cdc2451f47e08bc130c21cc0074822d75280baf7307a4adc161838b27f9b496820edb6f661629c1c01b1aa2ecb209b56df5469a2a656d5cb3ac858f176cae6652c818e57aa3bce95d43df4c0b14d6f29b9ae69e8bb94ea48935bc6ec63995a245342ea7ed7d5d7b2e2619db03d622f638c0ba960d6e0640620477931d8997e4d848f2484771dc89478b849e7c8d7b7eb0f47f08a07a6addb5079a15a6f0ab851f73e3d66d74590a34ad4960ff1250a0c5dc653142f6edb8d8354c095c3c9caa9192baeec9582b7dcccd84dfdca42520f528f8f4c3c469f4734e6595dd95580131ed82346b94ae1b6456da9bd2f8a130051bfdb6232c847c0016f0b0edd626b5589658ace0a10b023e514479d256c7307b73ee0750f23c8f24a5d45e31dee9242b15e211c693ed3e5deffc8219ab007dc062dc9fa2fcf193b326b884991e476f25ac292d02b1c936a5d0f029e283192d2feec251cdf427eae5495c0017ba1c6b53ecd730b7a48a74e3c1a8e5629ecff18fd79f355018530a8016a01c2fe95b0c1a2378a994b948a0ad2af31a169fa8f3d07105f17e009651f2e2efc0712dc1011fab5a756439836c55b5c3e752dad6375a45b7c89e5c15c748491a117dc54f2e38e474c1bf5fab93d5baac3607bc698e9253ece78d6062580437b56fdd6fc6cbf19e75ecf6651a7011a29544aa283257743f4f3aa10c0acb7155455a370439be3f4480d0521d152636c910e2beb9512266f67611266f408755b91cb1c2292fdd30937eebb6849fd20adb5ab19a56b64d123db0c204429059a898d7e607c18302e842093163524d1b86677ba256fa89feb0a6f5f6bee15d6389611d512337debf26460b7669372e2b7eaabb1d4220eb5f3e12c402836f36ed52f51737048c59a005717ba16caba573620d0732c5c68e437972ee2ed3e2fd142b171d81a2528c06c4cfc180990a0de30e15b4a903abc2353591d85c47e9a1db7d09593865e44d9b72dde8b1f4fd5922e0847cac3dd9d70f77ea1f3e4da8e69ad7f04140b4b6c84a85ac716a7e50959e04917f5fba6590e3dad8366cb40565193a0f2a545e02c2dc27eb51abb9e6212feb9cae31389e78009988d995a7dce6a440e5f314d84c41ced9463324463ffbe4d400a93984b7582a68f115b1cafb00324bbfc1cc4ae509c90ff1e6da10c4ec72b25cc2d9531f70936fdd7f641b8a7a667ca0b0129deab856d177edd3b478e907062c71c8497ae8897266d868c07e1a51102ddc4c18cf8b0dd55b72afab5ce1b27ba3b6667faba3cd392b2e337217fabef61386d6916fe89f93f647f0e05fd676822fa2b100dd4e1d7c29a3ca3a62bd784cd7591ec03c8cd9f7c71cf9349ca97cf2ea329891374c8f591ae26692564f403a50f20363cc17cccfce9e3d89196b4cd5fdc7b149da6c3977da8ca79cf01a67f8fa2e576792f1a2a6f67ed1c0ff7c5f8fe296c9bbe58d5d410b786d070bee67bbc562a4d0f089e54fb558af24f7837674028feef51f9e56b7780a104a37909a1710dc0f958dd820fddf50bc1d1a10c60ea3cdc95102b6c6f9b03cb0f90caaeb90ed27ec5ae1fc18db5bf6544432ccf581b8df35ff7508e262148bc3904b676a580989f56327f801f2d26d8acb5420d859df4e2d751f54f38aa8afe5b2c406430026962572fc37e7ffb0b43668b78fd581c1804d3f0d21080a2ee6fff88ab2f3a789f83f17d8255aac4deb56191ba9af475ebb37e8eaac675272a4a1e831d4c3e524fd41481b43087087e887d442655e5b13aba8c31b1800ff8b0f620db41bc37fa0fcd14989d985b768f5d280efeafa0adb9d3184790f98dacfa8e137b7f21f25494c825e05794eaeec4eb2fda0a43d6f15a6a51d4dd89d65fb05586a8c36da9a2acb313adbf5a9d25be4c463fe24005c2c3ab9aa9a4505b1562940ca740f485a4e54228781f69f659a6426c2924467fcb34b37d70598893c8e08b42f83adf5bdbcd5329e7c3b0662096b789ad12d67e68a60cfd418116b8476650a1d459c4f95de13e327b37adf067ea549bdff1dc8856d73315867764b04d223dccfb3c6c2349a77aad176b287f5c7de8b4a903fa60e3eedb64eb4b8cf1e7f61d30dbd0e7e4e1ee53ac00c27109fac9afd36b2901ac61c61a9870dd20d3bdc1a5de0670d86d3a4d0994f934fecba7d9c184444242c8997ebfb5bf3728525f4797b656bdfa42c3b4f64da95b2925b78fd29564e6e6d98bc142bcda6e9acdb42d87afc22eb2e625c30aada1163487ee3b698aec2986d1cc74c7aba6e3b9f5ffc05c1520be40f8855d77e1d889828b871861e360d2d7f5b11524830b85e99502c758195f19f42d74ed5ae6a5cc2585af3031a8bf35c9e6b8d136be42c5a036ac678a41e9bf35727b0a32a4858fa549c80bbb42c3e08030e9d239a29dc8b27a6019db32c64318eb6d50d319d9667f8a5bab3e679a9b9fcbe013c3ec867ae35d8c122663e4e87927127b189bb8a94cf5150fdac54d9fbac01f305ed7cc43bfa610fc6fed78d0c67f820a2e5ec4261607933e40896a2dd32868925503aa25c45af1c6507a66396de23460e78819a5c5a68acca196853205fba5c1e8d24ce832b851606fc510526e4c6ab109c6eddc1073ddf599427cd8b5b9459c996ff806de3fc6d6172460ee01b2c91856c5e076b7b00daa45db1b902587cf9330a4d3209171ad9c7727d8ef99694af61483916438c3b5261d0059ee9e44a0239d44d7809f126a9831fa3acabcc25dbd65987d4952c6c936eb1e453ea65b17fbc6785834ca459a502672877b7f90fa17ab9e151aab4e46a73b6d43937223accf32f5b586c18d666f19838aa4da0e9bb4df7d135866668f05228efa45605e0ce44efc39862c0bf958b1633687f75089b5508941bd10310db7c43bdf657968522eb9eca6e5f6fe0ee206c36e8f1c925e322ba5cb9bac74861391ab91d5e4df0f37b6213ae19d2a14af9f46b6fd3d24cd3e14535db35799bee0297d1259a9ddfc54f5e4799d64b4bc88079f4a594aadf74ad507374b32380dff106c8adeffa3e935ffe19ba567403ea3256662be2077071a13c77dd486a58ea11af6777e567cc1df2a2e60de05012e9a19f44cd0b76d06f3cf79fecd0d6b3207e2deb6c623e2adf154371f13fdc561590b08558832c95753ad9e59b9bc2711d8cc24f85cdf467656a8e82af3fbef323c4f959c67670f867d6eddd60190a5f7e5bb12314a2497fc5acecac8b7257eb7ecbeedfb67618385cda6a09fadfa8eb33dcf88bc724aa311cd79fbacafedc90a48186262f05504db556cfa726cde5c4ec5c02b8bf3068236e3266513d15a947678292fe715d74ee56c401b166d5536f5d7693221fd77b3bc7675c4437f187481fa8f07e782169d5881fef7da70b20a7790bcd9a09427623977625638a8de65f28676aa617b3c387f7a88238e41e17f379857f2c775aaf7b78523a5f75785be04d6a60fddf36e907f7934fe5fbcf23f7226a063ed144da1d18882b041fc052ec014ce650f2c2fad57f741a8d3d0082522bc8e7b7ef0c2a4112fa69308db53fe223c4325a32199dd3f563a59014c8fa23a6622aadba61485e8a3c58dba39d319a5e00e16be5efe1b1968252ba57f27ba219496fb6cda7966da88c31e0e645079f6c6029d74c51ed9a8c4c9dd6ec8ce39b77ab45b7f4439811d57ee4504788778cf1ebc38eec1ae9cd90669d15f5ff59d2b3fa9a9c4ad70421f950d2a5388095723737733146ec0c1afdc139ab36f8f3c9d09ca55bb95146524622bc2c840ce8d10037e093cd20e65dd6041a88eed4d49c7a505ff23d1596edfb50072f120d507e33f7a3632e45f2a6bdd1dac9a8ad43cdbf0c995c665a0e148dc8506327088a6ae7943862c337893e939296bac38c43836842277b11e8c11885c41c3035d4e8628c8d1d91b651af2865b29e4aabd0366064d2a86792a16b49a284d2a26823c86c30b3613c8391ae07d15d8f56f7eafcd430dd280b1c0a53eae51102d2b216c41c6191cce1229905e38c62fff714d841675fb1c1ad185b8696b5754c486cf1ee61b2b439593145e1ece03d10b6ae6d9229f66345eb6deb88b655a60dd1da8c69d3bf5a170c8813331614832649e65dc57b28f9d4a4cea3f1a2e09e468816b6abba66b842b2f69ca73d9b7e5d9986af12a5b5562f34f15f98ef5e2d706fbfbbe947eff53f87c1aa88f550b5ba94837ac487cc3229749535facf3a6e0529a2f2f7dcabdd0f7d7d68fd86c07993ffc5638fd8bbb396c61ee6f01cf5da24dc8975b70aced81da6e02305c6201b2c570c54a1206553c7fdb2c209b56c87ded65f53929e38ad4a1b8a8a46e0c2afcf7e9932c6300f453b82f58c53d50d87d9c5c7dddcb98251282f0dc08b27880d886e6dfbd26d60295a05e623c06057247d9c7c2050eff60416db8cd599e65fd2ed1312904a2fb22d493350ac68b4bd8fb3432276b58499217be25f99c50f5fdf0014d87236caa4b2f10794960dfbe4f7875b49b96dbd47b3d8eeddd603a9012c6dc325c2b32497346383d46fbab0db3be79084664ec8c5fd9a96d90d4a3b544f8c239415e737380796e561ede23ebd0b3ecbe4f83d23025e9255ad80ed905d3bdc0748e57e6228ae822c9afa7d280b6688474c36f4d00be98f7bdb6104c8e60309d9863ebe521a6469a4f4d305d938ad71b9995d55c4344ab047ae827c156209c59a5034d38b21d9e4879183f4703cb71aaced55f0ea86852c569589a983e76de5c05ae071411fc581b8151810020ea80cb4f9c2f30ddfb9269998391fb8f9029b06c2bdc803615dfdf24217b6f42f69652ca9452ec0b890b4c0c56ba4b2155e9d6eea4905255e27594424a25f560199963585bc14fe4d4920dd90df4259b9ed1c8ccb29b274a2dc678038d3c79218aad24bd11f4a352ce39b108b3ccb8ab60543033345e171667dc55302a9819ca1c7d68bace677c8697969b17a234d660da88fe028dae45ae1f656e23628c6d448c31d296e8b2f1bc9012060c09e3459cd327f7c1c03873df75e4437f524bb0458386d0128da804a1873092883e09f483417d604b4648bb0b77c109e1c29ba33d3bd07713d2ee8213220726443bed59576c45403f1a441046d6ccdb82fa32f0718706517b3da9a51687918484ff388987cedcc21798174f2db51713fda341a34a7b1f8ca4870e6910b5277f8b824af05a120d25e834966860814e638b6eb948a23145e9a1d33882adb8037dca0823a93d2bedd928b5b75d596a0f4b7b353c741859606cd15e0d3af0a5c22c156e0123890651aa49522a2825f430fa0c09a5aca490dacbb81d164c910a82addda972e2be541413f7a5801e3a562312e8478348a9535252477f80dd894feaee6cd2da0c893f1cd65e8c5f1701fd2e25a5f632c95246b902201391d1cc7f97522492c1d20aa2d8f9aec8348ce20f527b5f049af98f0691ece20f6c01c12cd0e18a7627337d896a30f4d16526c743fff44be9bad2de16b1e0e7a1634f5ef840bfb0b4b7452a107a488388c9c36b09fb79c84b54aca007ff61400f4f3e34d822163cf90f83022fa59da1a1ba13c594ba13c55177fb3488681cd190d25ecd43a76184e33f1a531efe4783cac39d2128e052e7b07503681b827be26c85215b9aaf868f14e8f59ec1dfb5b97ca1b3842e6bdc8de7450a19267ac59ec123fa6547cf47d12746ee88be0cf1c8fc74c7bec382291a89243320f732b4361c59ca4021238484e8b33bfad95f82da23625b2f4370c5fec2c44b9417277eb02bf60f7bf94126c3c3c3870ea3b1600b4616aef0d981ce575dc80bfba4dd404f534a79f229fdc45836ae7772e8d87a2687aead27bd75396402b1f48a1d2ec12954668af684a09019a2d93c992a84578c1ad42636e1e570e92f5e1b1a434d5885b3ca30d15d2a4acc07ab4c50772c13f43ce7758492118a3e7c297d900c13b14e30d0ebe8bfe873da361e663efaf480b2cb3c69ef7b9142e6e74507db824bf045a6c9b30cd0f391ebe8937102c3b02b667f9142420ac3103d0097602bfaecaa03db8abd8a20d8dd21f3d0b3ebe833de86725e11339d36146f28c8eddaf0d27714f465e8886f8f3e3e5b7c198a3e6bd31e87e82723f4dd3242b1c64b1239f8f9f95982620bb4b3d05930921cbbbbbb7178e8a851edf08eacbbab87ec7bc48d822cc6a2b2e84b7035ae4d03e8161d09fda7adb6929399428790ad0128f40fdba21858f918638cd1972642c0e83fcc8901e8164141e83fec8b3260f2d0b16e8c2540f41d9390016b1013f90830d9756d3c2fa042b4873991ee80a8827ebb84c49f08dc2524ed61ee268facddc18f325d11e6b7480923f4125f92fa4c8eb9d72f7aa8af7f497739997fa7d3d2e599a39cb53b2eee5a723992eefaba5cc992f6ae2597a33cbbea1254458204b67827fafc40d21ecadb952c696f1fc5e5a0d7a5f9a98528f5c3e5323d608e72146744663ffa7e63573b916d347f50d2831f49cb205eacee5a47778dd35d7b0c0eb49d7ddafbf60849bb926626dfbe80f67a8984b4b7fa76960e255b7c23692aa80f1faec811e9aedb757668cf1f7b24be3dc0e2bff88be574077e7da9c316925db5b320b74bdfac47319522bb816ec718b6bbbb2d97837a051cfb2074083dc69ce99fdc5fe8132a59b8bc32c2e616835a644c613ed50ccdc7adaa66aa2f33333333b30fde493a8957b2f5807d735f1198eda09b2add6ebb8ddbc1c89099bdd99999378e44cd7d3cbedd442184ce70b77b45551f23c7b831cab5f954df3054c3e2c6085732332f33f3aef73233af942c659432420eeb0e639392c94dcee3b1e81f005efc5bc1f857df857f319e39a6509be6e2170c86bd50ce79fcf4cca5f27b731fe627df7a4cd7695a5315436d5a85e9aee7338f1b4f264d7eaa5f8ce7f1d2759a023cb0dd08b9863da821b8a7637cc283070f19f6b5f2d19726a359f7ee96caef1a0d595fc00a69cf875de78d3cba5b9cee7659edadafeb68cf7f9dc296ef6a7d5500b457dce03fdfd642b6341f1f3d7bb6b1253a6cf113e8514aa11f3fa9827efc2463eea0f6bee8fc84a170100775c7de0b51973f811cd4defe74b90411bc753831b94acfbe321f5b40a5f7e9b708ca0b8ab4c7cf4ea4bd8b93e1013ee698f773c6cb8e55f8d90dd489ee9885baeb983ef39467266287e1295188dafb78484a7bab67e7284ef46750c378bbab392e44edda5c3c278ea174c7bfdbc5f193ee9887da63e729daf39f51380aff3ce49e2841036e01f51fd8e2284b608b9fec8a9d370b940efcc7519ebd5740e76fd11550fee3279cf1c908e99c7535ac3582383e7cd8a1bdafc3e1d1d5746b77bcbd4ffafadc7e488f1c8dfbd27407a5841e69fc6968e051e65cd7bbf656b8487ebdb72a899867d8a5237adcddd91febbf23daf41a65d679e859e9753a5b2e8a0837b6b713422bd9d2ecba5475075da5a3d3d1d5a866bab53bbb1df4a306b292e3976807426f126a2f08f620946031ae847ec4627c1b5c6046ddc1294d24e586d33997faac45a267d3694d12638c5c92c935c7f8dd80e2a17ffc3dc543a129f0e889a49c601e7b8e3737aed3ed83dd7e8b5200069f953b2e2d15cf0ea53c7bc492f3393c7363a0ed37ea9cdd5fcf6ca7de75cab443270fa53cf558597c335dfec8c137d3f7757a4e9e1b4e9d3929d41d1b15d14f0ac92196526e77ed4c9d52cfba727733bbac2cbe89cd39f8a62995e1b8cf35740a27bffa25e281d243273d1f3bdf2de6e09b9e1eeb0def2a69fda217fbb9bd65e79d6750a3f58b2f87baeba49472480e5529d41decbace85da6b28dae3a12c6e3ef4fc119aef7c86db0845e9ce891aa7dcc7e3a9778c5d51afd8eaa903e0556b438554aaaef3d56ad5e1c0d175ae0357d506ef680d35b84d0d3fa0d46da8a1a686531cd08d4bb1381b2ee6b748892b32a8edd603e535ded51aac86c92aa09f4968b5720601fdb0efdce4385a80600d1cc7c92ac3721bbc06cf3e25f2e51443b589e86712dac170541c6e53bf9eb771de786c3c83da456fa0c11163f49ef7286fc8daf3ac44e2701cae036d2a0dc7516df8ac3c365a8066784dfd60be73564dc2cf6e4395e921f31a3cf31abc7f403b8fdd75ae03bb7aa2513fd3c97be3b9f1cd092841362a8e1a87ce36be9cdd405b805a9cb43c69016a0182595a9cb40f36aec15183f7a83ec3331cb55b7af474f06fa7e4e0df1e6d1178ac94a522c73f1d3e871c1cfc6b3d7f1e73f16beac75fe3d706247bc6b61e315e83c359dd0e9db487c3db4f1b4fb38b1ae32f6a7418150757d51c8f5179b80f5507ffa1e610abc4a468c5f4c48e006ccb9d43fdc175a83e388f1ac373aaca71a8303cd6171e535db8e71d9fac7c3720379cbdc7d7b17fc67bd5ddcdc999996e8e4a34492c9ef176ee87e9332bd3c6c3dc8dbaba2263c9a315cda3521cc3da5c59e21cf913c7d0577ee969f02cbbe12cce991c079ffcf7c3a5b3eb740eceb9e19225036486fbba7f42272f5b80288bc5dd902c7696ef36d312a5bd1d1c7a8b13edf138f41616aca7c3a1b728c15e8b436f21c17a322d23f0e150864b1597afffc5775f7c77c7d70f63856fa0c3fce06ef80ddf6d86eb41fa9461f9caa34b26515e47003fda99c539ec928b2cbf1e7ce51f74f2f1c7b8e48c489fc9c137d1a59301ece0485fdd7002f4c029e9bfe13fe40d1c3c6e40b46f5f02709f8e5f79eb07f7c55f49c7c1a3c7e82e369ec8e59055eecf6755fe39a7ac73b27e643fc3e5e09b15c731896fa212df40e7b0b036d07df8b0f8e686eb409dee51a3f3503de7a939f80e5507d75179784bcd71998a83c7c4d4d5e958857322161a5c06f7754ce29c98856fa05be19cb88566050bbb4372606d943847fa60e11cc9e49d062c0f3d6ec139b209dfc42c9c237f04b0c543971cd81d72046be3c33912886fa0eb386779d83b02b0b556ab981e80efd8f100e0f82683b581de5af95c750cd7c3e472f04dfbca75ba39191d7ec3a34b279c13a70c40063b389209b36cc2392d4023802e8178873f9300b8cecf6f2e80260f3d07e74cbf21bf71fcd3f1b175a8aa1c78d496edd873ea693b761c2adc8edd088bc36188735cece0be4605e0da1bf667dfb8af9fa7e3702daf6e77319e83737a3a0ed137ec4597acc9ebba1c73e9d9bcae1963ac49909c1c6e3f9c4e3fa29f78a04b4511462f81440f423e12c915d98d9d74d7279f279f6e04154618bdd4f6070d5fa4bbc63c3332fde45aec0ecddb33edda7e4c3ff9c749529f99b61fa69f4cd9a995ae98aa745277b005f4eb25252ced2d71cefe8fe8adc5ee4892fac632dd08731ff79269fb21fa0c720203fccc3113cd7123eaebf9205aa4bde9ddb94749a4bb1957c908fa6491452e66a13d04f7707797d06bd88cd7fcf63a0c4cec5667e9553b46a90a96e0db3f1a1ebacde620d99beec2b77f370f7da5f64bdf5b742b739a99afb39c3a4bc6359593c9cab7cf5e7d7de5dbafc6f2ad115be92cf247fa481fc98408f4bb808ca4cfb75f417d057dfb0524bdbfdd076c5ddca79ad80e8ccfebf425f45b6388d8337b3e21db9ae9597b3f81f69123ddcde93b5dab524ad98325731cc50a2a69ba6b24ca0e91b043ae484572a4bb8944baac48ba9b7ed5223dbfd949eb5396695a912348dadbdf3826f140d27154634433a77f304bf41d6c1581abe918f5eec3f9afe328ec4477d38b401c9c13f7b1585de74e957e7a3b817eaa9faa19a7694fe38d775b2847618f32af29af753789a6489aa72bb0a58f7261643d36c68e1d320d5e3d5b1b280d315e9963780dd1716c57fd53d17f3982abe82fbe5b0f780479896fa2c378e53e1575eea3321c106c492198e8bcc439311e39ee53552934c3fd9ae13cc3e70c9799318397766f66f80514e36ba159cf9dbdabe16d5d407015d95f361eaf130c5486cf8091214386c3c858da1dfbd13988caf01929193ec3bf0b88860d97aeaa9fcad9b39818671b598b0d1b36fcc60b1b364e366eb8c4c4d018ee9be1b286b7c8909142c9f430c36b788d199894f6388f8e19b5573d3a36c5058d19329ca8bbe8372a94d25d741b151a75176d547e1975468d2eb556e760d4ea43cf57df8d87631a2c1d03f1d993a49e3954ca751ac63de682d21e0d8f7e05b537c3a35f4cb427c3a3672e367cb71e366a7856c3db5134d06c95abc1c9f460c3ab571bdc8b86d56032270dafe1996ae3a95143db7ce8f919329c999341532b0dde5cf524a9afd5dd511b10e834d048df3620315e390cf073dcc72ee33a9079647c8d1e323823d2536e44c695f0c7b84ed7a8fb198cb6ec5c8543154ed15d7c51194be52cbc45fbd0a8cda47f64d46ec24154c663b4547997e830eac70fc337293cf50f1e5d406b133da65e49ac4d7478062ac341a4eea2c7a0d25df4cc45a31b5f401093e13eac0352c5e0e0d1c216adbb9367fec1a30b05ec4fe206043a035d4eae27bb637b80aec200bfca75a0aaeec3f817f57317f5d360eaa77aa9415c2ef5eb961a447c5a83e8ff2ea08f9eaa4ad8e4d351dc876ddc47b56e6beccec4612ad5ca89cd1822f6348451946d573682424cc5391a0de847658d7169a00978e959724d779c9a471e83d13ee558dd8d0709ca3f1c338982b20f93dda6779dd8c4362ddb6a938871dc598c312e9c291e4aba63ccbb6258479f28d7e986289467b389dca6a32684ce4cdad3aec8db278e7da8877a08ae4e9ef22c5591e88066aeb991ccfbe4df2e29e98efd34f4f1d033a4e2e2fce2ec9cc45658e90a43c13886d25d507b5af7c6b3411490cc99090eea8fa178de6feca5a2dc65bbbce572acfbfaf98bfb8e3876b53631c9db8f8be39fee7c9874473995cabdeb582c25ed510006497b29675fb2dde223b8624ff96e3ca9ab09ea1795f9d18e39061d7a73323ffa53fe0df1d1f77b5e6e3c7c9403faf15ef16126edb17393f632d79c81d8c97f0ce5850c692f955d3df0c6836558e657c56493c77cc35a0261327212eb06fa26fa4ceac0d921e09b20ed49ed080e8ff6bab53bd2e4ec4030d273956737355003bd5f550762df91c58a008d977bbbaa3b9ec1407ccc9de3ff7a624d7c55cf609d65ab61dd7d31623e1b68ce0fbb26e69777c5fcaadd846f1ac3b80e01df3417659889177d98b24328e887edced26f8d14a4e1888c999959b294bd35cca6642921171b5298a18f1b7bec58ddf0070c9dd923735d044c09bbdaff52bf4515bbdabfb80f9b3ba0307ebf25f0103e8c6ec201748bca80e52ff79726c9dcddddddeef3da1dba93be03136874e84184b4b7444cc0f2d287c0160d647bcb377753a1df26119135e849a1d432530fffcb296ced8ec67d58c67d34760b73291bf3df5c7e54db4c5262f425852be9df4ba01f355d30dd4917a2043a840bd249a02d2f236450db76c822d85dbab20914b2942c36126823d11ed1142f3df6a7cb65c9cc2b99a3cca09432c628f9591c5c01ffd1dc68ba2e533c395cc5ed4efa894342807d9d3e6d1e6374194d3146e7c83531b923a617715d73fa5504e645cccb8b98ae791198c98bd038155c71a73561d764f5c02294171532ffe830b286c8308ceeaeef73cd47e949380cc478ccbf75c96b9a2e7d9a4c26d334714930bf38ef4e5241a34fff58917be6964b72f9c726973c97b34b13c75c128c6b02fae5aef56abd3b18eca3b16244318b6c69be6d231a44c802daeb791953be017808808734948685383cf4e53efaf178184f29475df3d3e74928a5d42dd0f394b3c0c97df3ad5ffcd63588f2263047ed7216d8b6fb342ec9724d48ee08e945cc29a5cfcb8b90d38b909e79111747e12a3ae645649ccac461dc856193935ce4a2cbd8d3720a45cf16bb8840bf751f3abe437bd021c6c5e8ed52b029cffed1168f5db1efcc64e59962a0d059ce103af43d8223c83a08343acde0357f5e57162f87575f575f17b770f29452cebf2a4b72ee71071f7419f7e27677777777b70ad063dc6cc6685a6d0ec61f8edf9a4dc6efe59de698ef7a29ff60fcbe7841c37a598e36633d94afcb6c0ee528c45f9c11cd2f00fc0ddf4e311adbb5a36c7c7b66a343abe16bbcd81d33d666866fda615e6a0fa9e6b0f6685cfb073dd3b6fa1adc78807ed2c0ab98db0e73b8fde08c44139c1242e9d8c623e5e4201769523c99c8b6864eb971e1eece5d4e721ff6a97e9d8b90653c99cae3f04b0513501e734d8b928b3e3720d3650ece397974250d399a18e3c9cc28094d3386644efe70392a6a2ccec11cf2f8eb939f8ecb772f2edb80643fb91c5b0f93cfe99b033ab92acfe4a8cfa69d3ccb5cd6e8316aca5705c35f60cbe29c0bca199f315c623e51a8188c9a1b4a439d50280ee5a84d3bcd702c65b81e58621e89ccb88b2f7c71f1c30f7609647250ca78845b8f8972d3068488cfe03e958c249fe13eec73c8f04f0737e35b0f7623d365fcda8068cf73d32627833b796a26b20c8410c3aeeb42cdadc77493bbca690b8c5f95c537d26365d7813115c6a829095baee23eacc561701ffd7ec17d2a1d0e665d38caa17cfaeea665dc75692d73a397a3fc53d2580f284e93d15cb571449cc44e4e2186f22ff6c0dcc889db9c3720da63ce3ef044b9140adbe0665ae5bf2094ff50ab2b77db330e3a63ec13fb823e3113d783a469190c030bd9d6409887f174e2647e9c5c7a8f13d7f358fde2c78f39d4e99ec794f443d7813c70bd8d7ac6d9bccd9a39a093269ad79cf28a17fbe408f91be2796de2152f6e7d72230142c82c70e219ebd6c2c87d47601ec3b893cbbaadc9d6b8efc84b8f1b0f98679ef50624fa6e3f26ccb87559bfeed74f0b5b266f8871d742c9450a53c05d2f6cb7f62ed6bbcb7337c9fae6d667b60b355eeec8cb49f6dd80ec10dc7335dc010c0c0c0da6b560bad5d8c57d1885d978aeab5eaea3bd7d4c076d6fb7130f1dddf1c293b377acf6a64b29fdbaaeabcacb3923996b2ee3835f3abfe46408d0f397f7fc6544e33aef8e9b50124d7e399fb0bf2a0f7d3d17ac1feb4fdc77447b2c064f4e7e55291df34b5e13ab0c05df5c7e9297639cd6dd0c37e3974be9d952e89315d7b5d6c1c4adc7ee2e8e39aeeb8e610deb825817b2a5f96eec5a51b272e58238485159aef9494d491d3d4119832d17eebb0355402eaa4c7417be3b596402fd6850a134886010292ab122e07217dc06da2fe7faa5525184904140bbca55dc065c78fbeeb818e2824b4581591a66f1694fc50eb3b4f7e2e2d03da5e4225348a92add25fdb4c74e83284583a85b34887a053d955aa25ce94f10f5490505c1968c101742b8e05241b0674709b63675e5a5a6020fd89c206b855f6fc07a2f7c9d019b23846fda15b01e0c5f4fc02259f1bb4b5800dff432606d9aae107e9d67737474d89c1df8a6bdb5393ef8a63d0005c8e103ebd77fbd165fdfb19e8bef0d5dcd77e363bcf0a0ed9f4b0eabae97e5e8584ff375189b83135381e8551389319cf322ed55eee32ff2cdbc74986d2181ab760cd0c7fc832fa5f3e6602e3dae57c3a563bbad1897b516e9ae590a5afddba41ed5751a09cdc7cf79967535fe35a34b1bd323176dc4e9fb36bc65da98a9e9d7f663c2559d6ec3864b1b97bc84e8ae1ff14d1b1b55c375da460d3eebcaaf1aff46ddaf816d978cf2aa107b6931c1d0cd643299dc466de2076727c661b61f2618979376dab0d4e9743ac54bfa69fb113dbad878e67cd97ce8f911b919a952fd88df440cc74442d81d97070102a15fcaeeb8bc9d89ae996d3c9189606b77ba32a141ecbc460d22099fe16db812ec6f00c115d0b767b5091abca3c1676de247b513e3372a06b0b7c141b004dbe2172c0bc1176c8b73b042b0c54a3007db622dac126c75935e69e1eb26366aaf60bb761a6a37d1a4bda1b487797b07752bc69b86cfd0f00ffe8c6f7c19dd6486ef077f86c754c62a4b29837cf776cee22e73a1369e8962d911a216354d281a9f5f83a606dc782eaeee57a28fa3f0ed33ead751be5d46fd66ead723f87699181810ed518086489118b737e9a7183f6d313128401383a361300451c178e142c810062c800ad249e7c5a585a676f051001fa84831aa355d77ed2c2b1e0adfce42d0f808cc846f3f55d1f1b11270b8083aec0e0e82f3e01c96c237ed548cf0ed385246871532510bfdd650c91a51645f862b8dc3fed27c100cf46ba1f62e8750c02129edad771bf594264ae29ff63e5f6aef639fa7f2edcca4bd24b682859b6ceb47d8e84d5b87187ae8f563a3b7d2a37fd786c9337739ac47d8e8b375acbbde24e7249f9662f4ed0bc8965983d1613d42c3351196af8fbe89daa85b4a7ba155332df7d975ec4ab6ed7537f769df9ec420f6f07a23307ed2fb88b6717723dce852eec29d2bc3037f8d0eb8ddf6ee569aeee4f6765cef0e6641d7b9af206401c56f51164cbc8edf222b4b9fb5c6557e7cf8fdd99ca84897662b903131c277a24bf7d138b4c71aa471686f2d286cfd148ee06a9168924ac997ba296461f9c0d527fdc0d5fe51d07fd823b5fbf8f9761fda8bdf4740df8df33b4350706b1042395149f80cdf4b547e96f5949d288edadb22259fdfdf191a6a4f06fad050378c22cb6ea01896a5da05740ab39278c4632b8fac212f8c6bc3ad7d3b54a269b4c8b34a93d7941162cdd86226d4d7f3dca85d2bbf2e95325316f4970a25984085124880bf5428310536475411596b81b2af769918a90a159389c2682c1d35271310bb36d2a4e34ab635598c7ecd0823fc758cbb62fcfadcd5baf4862da5e4608c305e41f47337cf397dc619218c10eebc164e6e5d5e975c08a7dc7558674378c9d074d7edb09579c38c73d8a62dd0e841ba5bd6472fc2e6ce8912c208619c52ce29a544a2b0e717a317e1d53997c7eb82179c10c229328b1e84107e47b48f1ee79cf39a978c1e43e90e23cb295931ca18655ca29925969288c0162b8816625c9185c3d281d3852974ba2025898d4811204a004407812d1347c4081a44ba63161124304135a18e0fa21d7c3ce1ca13b0c80ec820916893a4b47490214742862c216df1a8892061063126ebf820dac187164270b5314e2c8810218104aa8819bdcfd15581e88e8b10012288109d2844458020d21de4928aaec0c27f7bb48435f5c117ae2d90a6c0d2329f405484267d88929011830c39123244670b9f1ac44022862491d10c32841b421406a35e6d0c025b3a473050fa683d9323b8da9712b682e460e9940109c220425433344d187d4bb49581c57a12e30e57f4c08b0c6694d825d432a18ecbe4814d0a2c80c212260bb6744cd84c91ea6ac59d8610c2092f245eb24841162db8585de39b817c7c7c8eb0e20583cec26129584ba48535fa198950419325a4d0c2480b2b5a4d9881167ea0129eaf204443575a1ce3f649af36088b42855443bdd2b90251aff6bfae0ae9aea5044a4b5720e267dc0166cefc0b210b5cedac4384b08830ccdb1bbf4447b8f2f1b7e80a2efcb744291ebca884bae0630513c430052a3bde7f34044a9012855191157a708422e090038b455aa445aab9a205a95fc77a194acb7604ac03318ca0d4041a2184f00a1dbf91c6142a38c1083bb08172608918d4221cc18a294df0a04b64c145cc8e2270600a76042ac609385418258231c1910992d0811644b002021d3b6ab052a5000c1b634430a25311b5ec8bd1176c86d0822190214b01086218e530da418e4a093250205d188a0116249f273ce10621dda251085128c1a19c00c5432e18019524e0c00b52c01c104208e10d14fa90cc317a8adc977143ba630fd21d6326eea318b713040e09c24f0e41008215425081ca937c7c7c8c803285960583101328f0200c467881040000820fbe3d621aedf69976a7e970b0babb7bc9c7c7c703b1bb7708b24ed041135ca044132e5cf140b3babbbb83c4f676e0313cb4e2a1634b7897874e61aac8e221141e1249217ae834ed75354020c1e3c0c1a0d8203379910a1b1401e3f14a180106a7bde8588f07b2e042134135c8220c2eb889310c384ba400b52e8aaad081b17c7c7c3a0065c905bff40ce8e7cffe03304821dac11381a8888900688df41903ac17633b76b846413e9887bf4390ab7aaf98e17ff4b9efc85241e3e89020422011df1eb12abef341169e8a1fd0e07b088fc19e0871ca107977bbaf296377ec8edd32767b9b9b646c4cc69e8f7d4df6281365227314a9a5e929d0e93ea0d191b5f2db9d7c018d1eb3ce414c7cf46c6e3ccbe5a0d8624cd02efac7fa701e932ea0df1af9681f82606ec25ca731e938bf2c8cf38ddedde2f0610f570a74592806215b9a4f0225e9d11ff4c9b02a454095fcb779f469ef72e991497b2d2e3dfeb43856fb33de30d7e91ab78a711af491d8e6b465337193d3811bc699362028c75adc949ad395f4d69ff2594da914c5360cc3b0944f0efb817acae4d9a6b54c6fc13e2c3efdb09f1cb699b61e97cfcdb50dc35c6bd2dec6834d6ce3d92276aa2617cf1ce5d8e6da0fe7d08dd380a663aed3da073807e5f3744a6d546bf213a5e44ab94e9feab6f7c2511ebbe5c251a8cd3f94a7b81e18757293b7b8b86c8e71df8fcd31c79c3720db74ed876bf14f6bf22d7e99b61ed331ce08e629c7361717d3a6daaeadc706c3e7a632b9b8565fea346d304c70e369e172506d8acfb02a4f959f52ff53cac4611ffbbc8bd3aafdbca89abba82f0e535bfcba745ab6cf4b4739b6f974f1e913634a29b7396a73978ceb0185b90bf7f16f322e7ef281a7eb29caaf16ed29ca53fec56ff1de7a503782e2a8cf7f4aaec7fce2b4971a7b854a51eeb340cf639eb5b4b4eccf77a9fc1ba5f2a963d4457ea6699a928b3ae69f92eb5b1cc34e59fe833e3e2f27c61dd1de08758d7acabf23dab76c1c10cda9eb744bfdf86993ff4e593582f293cf16dfeaa7e43747d5afc7e7514eb1fa69ef63dfc55b5a6a8f961f2d2e2d3e7d7a0b877587d1cd759aa638545df25b3da2bdc6691fc856fb4056f7596bf2270e9bbcd207255f34493a4a3a4200dd222c4c7eb7f82df2c2cf67a9ad3dbfb025c4f4e5240e92a3ed2dc5b63b3ea27a1e897dc4648f1c41169ffe591b761c448e916a5bc718638cb1bb61470ca354a5d2ba8b44147a8f1fd8c1ec0661777707f923ac6f181ff00455aa0bc68fdd41e660e2e6bb71310bcd228f8f351f3f9a192dc6184d31c6e833c62c0ad3b3d581622ee3f683865775377db539e79c2847b567f2e91426bb816addcd6972ac9e5c4a082184ac87ce30dd4d0ab02618e87784f5987f41fe927e5d521a61c7341853d56885e90ec21a533e5317b73f24e6d96e3f9c20c3cf8877275d09bf115577d267ba8bcbb0a344c17427dde498ebc06bfaa92a4971310b16bd5b3ee06a7e2b85878e9f3534188afb607ebae326dd4df7318d3e1c1c56d7b9ab54d8bc3823763f32b5f644c6447326999f7c693ed6eaee6475774eb57d765841417e9d63d48eb03e9a6aa8163e4b4bbf455ca8f2df2249ed547747abbc9355a1871ca5bb1883dae30df408ebd763ac4d9c3ceb248ac5c40cdb7862c4e21468dc456a290ce1a4f6302bacc46a6f17b684e09d85ab6b6ede5d6ca627c7ba8b11a6bb250257d0672db280ee361efa2337c40aca50baa582ab2747d03f9ea2928233050b0a66e2a1671c050a5b43bc6399540f9da10f81ad85dc878383711f8b75715fd77915da1eb5ee966598fbe83031c675087d79e96a9a946537d0ee76b3200f594758cfde6c8422945e7a11487ff2e83e31ee8e934b8fecb14bc7580bbebb63d7e6dbfe63a417fd4cc47a26f0d91d50890d9600ca6fdb8f25daa22ee27fa1f29e8273ba4ad6538573506580abf22558025b2aef7e92fdb75976a569b0a76824bebd4530451b21e50c4659a66871c43958f8f61fa22642ca188cbc30e5af1c4dc109d011df0ed31cf8f617327c3b56db5d5aaac03954f827ec0e964223710e57e19b2c9aca0f013c64a4f6bef81ff48f91802cd1438e91ba83eed24297e819a9c5751a426fa1d9a96e8ef5e69b4787cb3793b9203339392d5cc90216aea064852a6461850a585cf153904214a69cb0caee90de534e08e5e6bd01d97cf3f5b8fdd836dfd7fcb4691bb6b9dc7e6c9c2637fe2f3c6fc10adf3ec920062c5dd0c2b767e1db4fb00adf6e25080ca83e725ca5bb7625d8430e89cb7ce399f084049be54ad4e5ffd75c1f7afe02aa57a2fae93d21350dd13d2e60cd15d9d6b052129ca8aec02850a87168cff60c55c8c2ca3a14ea2e8a5b591ba1f696fbf8a1509b817e900a58ac4d3b5f21b4d00921869c0c0ffccb71d2f37719e807a3c028df5fa09fea7bc9da3494ef28ab8235899b52987875205b9a2a80200944d0c2ceb600e09f3f2bc70d8d0f0307aed8af0a6d5c5ca74df5eb79939665d0e79c734e2927849a63365cb39149e91042083597366c481b6a70b84e673515870e1e46bf990d2a48bb1d779ab39ebca69e2e79418735f5633df40cbaac9867529315fbdd605c7a19f4279f9784326e816f2edf7771f1abc625b8f1d86058a7cb2a671dd29de61906793b7d004208f44cf49963957ff806fa694877fc01be818ef5bc062b03b16eeabe1337dcc77fe3ec524a29e5119cbf719667acca51a640f9e7c609c79ea9ac0d3b637153f965ed4142690e8e02382f5937dd2c168be5bdf1b024ce0e0d647e489f3e8bfc6e3c60cfce58501d3870380ecf669558c472c266957eaaf10adf4cc7b02df00d0e3f71587738707032d37908c4b2aa551b75a6faa99ae212d7b805be7171592f7fb9352eb563355ba13e7647dc7c87218f39ac1fc5b213125cc59333ccc30b7aacdbc57d9a9278a4fbd40e3caad0940711ffc349f1684f6220fe8ccf30e5bcf548a55cab6c85ee0e24606bb178e24a0f8060042da8d060877d093bfb28218267fe699f3e74774178b94e5f2c1fbabbfc729dee2e974716e9729cee2e4ea59ac0263de48ca8bcc533556d7118357eb65b8f165739ac2a7ff1c2857f5bc5bff0df9c207bc3fee285d7f40b7fe13cdac3fc85ebec0e1afec267daa3c15fb8b757e32f9ca63d1bfc8577edddf88b0fe65fb8d62d00f80b7fe1b1bd197fe12f1c0900b8208f5519ce325cc6318715ae8dbbb384dac3fc844924b015717026c7819ab376b98b8b67dcc2d5053bccfd546d5c5697310eab3bc651846a4063fce32831ec04cd43974236f508eb63228490668706319ed950f96fb819ce36d4cd68a8fc3595ad501aafe19cd9a8373cc88bf34b0dbe42face717844ece49341ee5b241e2f6d749285f39295b90d36cef4c62fb7c17d588de3e03e9c2ce8b7416cf887791ff6330a5ce5a8fb59bcf11c959fb5473795add00f07002c67c769ef268703a02e5cb132d769cc86ba6bc34fa2401b38326ee17784c79f3c930eeb6a898f0da8f415a23dc85180c79f3c73e92bcfe229d650b7bbd5767b8a92831c4e7790861a5ca5d57da843d606fa8c2aa342192a3138558551f75f54eab0b6bba8479f5b79f6971a44bceaa772a9df6ef1ec1a0f7daa96fac106fa549d40f728cec8e6d06166b809455881154da860a28a13769808cfdd6fbedb0e210159189145e8055798c2187636c736252a0425458108289880c9ce66f4db1546bfa17edbba09d013ef20d87a29c20559c080084b2cf164a7bda3b4b742c0d1e40826b2e00112507061a7bd87dadb20c0560f122520a20b2be882154e6065a7bd8dda5b201c51416aef2aed9dbebd93ac746b77bcabb3ec0ef8ddde8d04ae386897da79687708f11659b0b4b77d3b2f29213941a90fed4376c70299f2032954014516a519ecb4141a466520a2045f78820992a8e21b094f041511851e7cc109293bedfcc340ed75ea79202188095c98c1123d88c24ed1bfa0c11542f085282c2185043b3d424a4f8a8092823338418a1d015c01657e3b8e01d693507e3a8864cd4b9a3ff58afdc35e524fbaf41da4079159102b8f6141dabb1a0b9a292dc1d656a1083f36288114aa90e2043bd0394b7b3a3cf4f6d9c2436f26ed9d1e7aff7413e8eda4bd27ddda231f1f4682dc69a887090b9288020faa982245163bd0d9a8bd1c84debb1e3a4731711fc642dee5d08710815ea43dd3434782f4d095b4877ae84b5a08e8cce447034250f0820b9e30918520ecc0a02a3c7466a23dec7dc0168e773d98410665d042951c3401cb4ece43d73142f6d055b0a5000e80810959b8e20a3bb8c2ce0eed2110ae78291dd328085e1e210856bcf41959248417bc749af666badfcda9e1352e07ede721d842b2434020facc637bf5b3acb5609964a474b484f4ccacf41c04b6d80c7f3a9dfcc44327ac484a0c1e63c263d8aec763c841e3e3c0160f0de19ce1af1afeba98a8bd26f2f1f94f569e423ac6f114444b3e3eff498e89b8a145b2545445117e4e5fa524a42323a2a167178ad241fda481faa77d9e3dcb92529223f1c7d200462d244ba44b57d21d33115c2d41d21d2f92eda09742e85ed2ba676608ee8126215dc036094cbe3d52181a4d768c2a557739987fd7453195aa3be6a80a862955c166ac178766a5d47437e74af9b9009a0cce7c4d77d3997637b34c42c87dec7c84c7438f55353908c3338d3323ab94996418618cdf111adf78920840127976583f7f0855bfdd7155221f762f1b425e8992f2080eb3d6de10420877f019233347e60821d4b0f63a46d831c61865c718e31563bc628cf18a31c618638c13cb628ccccc713233333373ebf85d9b0c42f60f4218047f33841042082184ac1be8c02c7a117633d7ef08646666ae818c03da1ebd08679c587bd3b1083f9c871cbb0eccb2e871c723381b8ff4f6628c9143f1d6ae7d4a1e72dd0e7b0cf4a3365478764fef7a84e6255322e8fe3eb4029511a2f9e61b2784e63d3b9b84b9acbf454bc420d502de2c29208272c406862bcebe26d19684bfbdb1cb532d804850fecd8288382e111de18a0c82c212ebc4542f75d2134f5c444f18dd608b474f386118d4040be051134cd88b2c1f78a7679f2ad01b28e6d90d5add3cd639bda5b65ba65a3a8289c9649a31a949d1114d6c684b473c0123e9670bd36493c78728ffdaa7cf125315a24d9c10ecd35e735f73ecb34b7c9aeb2ce378e8cfdce4eca43d262d2484d1bfc0d6ee303925114581025d228a429499e6ce10145f90bf9220f9cbd4a6ba9faa1bb7dbae990810458864396af10e27f949884f22c49b4c3a5897c7b879139afb5f1a2744af7aab9aeb40ad9eea92eed837b6ec481125768c1846378beafa2dfac9827f6c6cd378688c8356d06fb3b0a6eab728055462bb4ce478e88fd397b4079f5d88f6b6e809280c45b75e82b60807505e303f79ee2998080747ffb113cf71369122489c73da03f0df6239da9be917b7417c0457e07cf620220efbd79cef0eec798797b52bf67ef66dde690bf04fcfa6fccbb72809e837ce6cd67e664e42420408de81a523eb20ed7d46dab9203eab9076f8a3856c3da0ebd8b759368bb396b05c7e8b94e0c17f2c557b3df34b2effe06f0ea6d1765577ed1f8559025b44dae457cdb2b53155acbe8447681e2683353be896fccab7d3d4f4d1d3b05277cdcccccccccc6ca5bbfe38c9a79dbdd5fee3a4779d5e9a9593bab3c24a7ce5dbfb522204509ee6b708892c3fe33334ddb1131a7ed2ddddddddddaed3d0fb63a0232494befb2d4282e8b1a058d027947cf0bbf140ee63711f4ba77f7ee6b7080458f8205537e1a22642bdf4404f988967ef25d8a270658506a1847e43c10a5ad424e967fc163521fa6c21156893a567ef2adc4a7d656a340f9110fa1bbf4548fc7cc61b0f4f6f3d9adbb591d1bf468258c02ba8f47ec2043778ee2a9f7b3427bf79e8d123c66ff8104218639c729b62a0edd8d6a39b5b0f467dd0d79b8333959d6c3cd397da9b1c3bec004da23a0d3940d72111949ddfbf911aa997189584ff1aa997c3b428bdbf40bb9b070e42081ba90cf46b24a3effee8b0cae85b5547d86869c9ac3b051aa377c3daa3934cdf1e0df7e1113642d2dd6e57e94e055b9cc4aed851e02c78e618f0ee7007d14e6aa4eea013e8d7f3dc59e8421f1741c8b214d323c4a52b3ac71863e4d8d9888de0111f5b6996c25660942d601418858dd8888d36c258942d4d9499498b1088ee3a4697524a29a59452ca2334f506baad1877e1df2ac178a9a0a8d42c76a5494c992212001000005314002028140c0805a3b16040ac6aea1e14000d92ac5672521b88398e52c820630c01000000000010001098218100208371ff2fc93ac7f3f252315e71ac01cca79290e62a604498accf2dfbc80d742185055b94cfacc7ce1912aeb01ce0638b540ddc368d8739004ffe9ff0d7a44965093e8a9654ead7512d79cae817950721717a39d74e16cbbbc08e3b2583ebad0ef1daf19f853be30a24759d125b692ced0d0dffbf1f2ce565e3829caee38324e3e6c6c8cdfb26e968bcb41df399a82a37876a5252c0e005e9b4b3fae368c80620886bb8eeb100266bfab90fdb519e662c2a2856466f6e30a7b6886cdc4840ebf0abeb307fe2110401cb57b72fff849f137bdef823518e582543f7833bee29eceed08c1db37073fb995e4aa578b7a763cffc045bc1295a97c1144108a42366f560344d8d9c4405c1fa12a52082e437240059a5800bd6da1d3e50ce9c9b243ae3ffdd1ddd75b740d9c9b35e8b75a26a4f4fda238c0ba3705e93b47d59f85d7f607e1fdcfc07f683ffc07ef41f9a0ff6b1ffe03e721fbafeb83bf99098d3860ac8c60e9d5c3af186d823a507e6393ca8b85ba0eee6edcda894ffc062286a64afc4ff869ccf83d91a082a768c3500950799e6efd7e060320d6bce1a646f579b3de1d62dbb84f3da1235087f993b8150269983ac9c6fd9461158b2dbc4aeec490ca4ac248fa184be9864155c38f3c494253e45dcde8d033a5feae7e9ef0982d1b2e7f404fba55c5fb6a36d473d171e22761a069928470998074df3c4f4feccdc6c95ce951a9471afee219b32b92ababb9b93792f113ec8b6922e41a0cbe8e3dddcb6889344b6a648f3d308f5ac8e193f45f0730007dbf4225200c7c75cf801b013e5a43fa706dc1cfa5be9523b61fbaf2b9c1810e16f6d6b7b554388b0170611086cea34944d646fde50c6a4efbbb4b660930151ce61613a6530ce52079a2c0df913c654f0e663c9d91bb6dac56e417f4b821e4ec8055c16a379b889b77467137b60accc46ec5faed1fdf6141a38d601754faec01daf80a7bcf3df2718ceab20475217d7b60267dfadc4fa438749750ca672fdd586d3ca510a60d905cb3c5388cec77a5b388ace77c9daf892f15f6d76d62007029ee2112b246ea82bec213c4a007a9089bfaacc16077bdaee682823fda97477135575841051a435618dc67b224a37f70546ae25b9c21b21df10f796568892f050eac1a906fa6b61209cb9e9b9d29843ec69d1faa63f65d242f19a16befe62974c07bd22dc60f6fde6d001cf2fd4d245304d0fb53eb243fb4f18cedb62a2c3c2511186cc96f2027806651bfc0041470ea95735870eaf15e1f0c621860ea536263b6ba7485070051b6f065ecbeb2c17d472220428c79cd5a2350f95343277a078d87494ca88b6a8fe98153a50cb728f6b8b67e531e251793b3f9e6fb3d504164989726b840fb4c0139e0c68b01682011241b8b86913e548defeae5f3af5253c26ba5c0579205e89e66d129feb101c1acfc6e0ce0155ecfec1c00f228da819d51c0e14fd77c1984110464afd6e4d86febbb837b13c42b78e3f7143b72bafaa2ee6d05b3dca948426c999a5b2ffc15e17df1fc293a3389b9c9a99f1bc0e305faaef12a4058e78c66543944b3b811bd86ca1c7bf843f83fe78ad5d08d69723f75276da2fb771a0af1cde7d3fa3249fc210ebb66720fabc17a3948a96270cfab22601bb9872499b2c8751cc518af520b98206449461a17309f47e10174b6acc0473baa703730763c4d4109eeaa2ed926c760817bdb4f87a911c81d81b11cf51a355e01bfb3899e24fc83df97cba57de979f0c7a3a72251bdefab0868172330c1b776a3180604f33c2c1b7b1e88a62ba46be8df952779884548b991141c01267f1e05a38734824fdd8c7cb6291d62a333d037c1e33d7a6935886865453d697390309317aba68fe2d0a5c35d07481d636ad9f0071dbee5e79dde3143c944beba2ee0d623cb165f83a5d526969f84b9e6b6624e9d702c242eb668a103dea0c72f36aebbb7c92ed4ccdae5e7dab9010fcdee22717312ef68634a1036dc508b3e73d4e0850b35647c67c38059d99330620d68b3595c2ef0fb0fc4c057cedc631602a4cb8d6cf0e316bb3e664af2b60382383660bae0afb910b0806a2db243c567d78dc83a773d8881180268d5ed547744d9dbfff12637da1a895d00181687ca86ca29bb004f454f8811082d75eaa5db2e95593c832299ca895bdc975f3be995a242974c01e315f1b3aa199554e168dac697b6e47213a5de549ac743741b3f1559594b917b0611df478127e48c39f0d202f82e87b0836fbaa403e44d77d10d25a9fb668e5ec978e1bae16e63649d2b512ec06c5f4a9aa0903489f68c283f68370a613147913f56ff8caf5e21c8a3880381f4b4e3efdea9540eb5f9ee3911c644955ca3aff420ec694805b411387258454ae0edc9461a9ec90caf17020308ed3f67477e297ab687e2aba46e87ce3bd3ed4aa67163c8e580c44618211f401ea63e9468558deb4d127ad8bcb5d062f437eeb8b5b14e5f8d59ecc94c090ca9f792d031e4f54f021424450c7f922f145de02d52c5e65f309ecb2687dfe7aaa551191cc95f81247d339732428747f5cb35d0750745258283648ebc76190b51511d03f080bf0a1042be73f56b9a87e043cb8a0127937b677ddc72710421840b8c80766a09e6f2ad059b30c78e5504d67d692ab204045397ed6b8cfc8ebe05b0a045b639573ad19e5aa5005ec206656b348b72a86dcf6b23ec14408ed74bf3a366ee50b82f8724698571140a9b4669286d000f08e6e844e0453950d176039592ddd1850710a372ef04aaa92a3cff407feb8e5e9a8f9b576dc0e8552e01c0c5781d227039685c777a0989d1b58ad5f3d693dd8f37f353d0c9b679a4b11715904b30254765c02d836fb7bf3806cecd432c58be3c17be7b1ea9c3906da2163dc480476429ef0394009a0557fb2d03680a4bcba0bd67a921cd681d5de6c35e40103c312ae20d0deaba44dcf4cda28fb2ec4cc254119bf12a66fe38e93b4c7e58882a4920ed63e24347c5905a25367f419512845e6a368c2372f61a798b58ea4d0030b5f19c1d5474b0874525c53bdb5989c684d3e87a300303a4d61a9d7027020c78289b91b5120b895c67964fd1b6c91207cffc64c276097bea458f8f6b78e02af1a9499367d7c6c34d3c22df09af560914abdabc72cc70f3ab4857c88cb0fae356a05bdffc93e7106c628c45e0ed8f995594f7c2ad02402961a4d6b1be37c19ab31b80aa08880ebfd7edea9a2f3d912705bcc4c5e650712d32b0745ce8376f819df356750f161b6a5d4ac5dbd88a7bc24ab7d2fdad60da6aef19a7aec0492a9d64838229be3ee77dd5dd68ddbc7d8e9d294955847456a3a61239b0ec15218797c1377d9184aeefca31b43b525ca0d388a69990501a429e97c534092891b6646445613e84e2b153d82890fabe4e429739b7c82c347bb49a4b6d24fae3666e843d6a6a2c31585ef5ca20694d7e444233f450ffafe71e4ea15ea0d79f0fad9f6cf6b2e4fe8600904cad223225e62588289b3722895994e76d57f58f0673d961570229c99d1794adee7eb35c111c7ba5de778e7f6869206cf08b3a4b56913526c0ca48eaa23e3cfa5581468dc29184965a85bde7df49592ea06c5042bcc2ae484e90f925302e965d0ff3e340f72d4041cb3a7f115edee3797bc33fcaa43b0f1948ad4397f5ca99bc85feede58f515024c62833509d6d9ac0948688449b73953c7316a678b5a8cbdfb927e2160a01e3cd4cbb2c06493c96b73c8258583dc7724afd90809f9304731a91973ac987866eb51ac9936e23b346bd9858387230f3c26336ea335cfe276a99486d8d5a0846f423ae149a43e04f1b3e1f6bbabe32f1d57e3874ca046c10b71f6da33e224761e440b85d831c65ef60c09677b4705fd2dbec273e350c5f18c274d00ca9ce8142ac7839f507c18efe1ba415d99ff888492691b870ea8f06b735d287d3d007539d825ed8fcfee6a990dd308a5cb3e95a50b8d9257242334ef5dad3880bc58f76d34cf8e0b5d3634cc6f5c016a4bbbdd3d3ed92ded10e045f5fb1306199ac4976fab3d6e253bb5bebf43235e283733d86dbc9acc6d3a0988287154781dd2932e2ee1270d1a28c9340dd5415ac132dc600ed237ff4521555fee34d337815e718bc93737846ff64d6662b03023f9aafec3659262dea610abd5f28047f76f20a254430de20872a9e7d4ac44b93f63d30062d101f2e50a62a17da01f67ed3ff5200856fb064d148a3027ff915a5cfbfd408abf495c050fd8becfd49faa86ddeb08483b2eeb80e08f72b4087c651d09fbaa7c03a8f70fddc867e4c8e7769b4a9edf3b129c560b493095facc28ae9b87220e429965497dc6bbb37f9861761fdc438d3df896ba62f0b7bbcd5871741fd4deaa25a5951774535a71c13dfc85d415111189e432c3cdc05e5aec41bb62c56a963730615776380f6455563ecca96dc60317892612aac68a3a4dd3f010a853dff920605e26f2883d95860588db52229f14236453323e050ca6f73dd2e7bdabd9aeb09f8796f52f8d6bfc15e04f8c2615e1e00427253e3e1d220c4427751c6a3ca333a8ebccb13cd8b32c33b73a683a28230d2ba83577fe95daf6f484273a8ca613bf60b090c4cd0f4fd144fb1c327164bce200b91780070aa3664848917142bcb1f193928eee96224fb0cd123015f49b25c50cc5cbb3165622e1ffc4b04a4d0f994f44c522c74b8c52305a99df21d2c4f24544a89d69a9d66e6d58b5d1ed94566abe11c6f0e38e9e6e7022c253ade8ebb8a64f6b4cf0508c91e9421e70253d204c168122e37abc8b25c44474117d7dd6cd81f63d307809bd56673cfd3fe49d90b527ed85c352862fa91648687185dd1712c5f4829240bd16ce3ed72febf0fa17c08b4cd926d2f135d90012033d4a7502f5e59c16e3889556f0db2bf894d1ad8ea0ea62169c2d23219e46a6e2ce167789d8c4718166bea6d78ee08bd878a603965514441f0098a862f2ecd910d680fa3d233c385fbf6e9eddf8057b70e9bfcc3e50744060821498830c32d56cf540b3e3febfe2e7790ba3b8fdef31c600efe32f9293889b26ce3b0537074d89f956c501f14c6df2e2a3a3063d1037ac33bddf34fe6464c0256561fee02a6814355f2818b6eebd1f59780641816a157a112ab880318ed4509171df33e70db8bfc1a5531e1e492e691247503ab4051178456599186e60dd7e05e61c92b64d8b917358756ddfc6c77638651118ea24f2f77457015290eb8a751fad7f971116c6d9e80daae2a3337be303ccaf87f2150e8945648d0a5360ed572031998097b66fbb02f29ce8112490c9a111b7e8d342a99adbe46f81e9e6a84b84e0a53b4d18487b68321cae18b23747cfa4813d8487a89b057ca623e95e39cbd0692ad440e6f8b3fda31539732494511f3a2e69d23661f5bdbab46feaf199a7a890bb2ed06ba37ef640552eb179ac0bad08536fd01845ec2184e1537d02df40632b642d485fda7d6395a0dde6e214064f5099ef1a4bbc737ab8c099eb978101d729699578065e3e9577015603a485584787603dd8357b36447233abeee6e7c8446671521ac2d6bcfb6f93aaf5c3e1f60da4cb021f0dc0d4be53d8c3a020f8b2537aed1d734a7099bed9c4e4133143c04794d5cec514815915b928ffe66b4c3122121607d1d8c22561bd604b54511b88b24ae501ab6009cc4761767e1bcd7d1aea9a84d50c2009888f363a4bbe92b109c12e53719a0ac0201fa93164ab57383469567d9cc0460ad5d5a0922c158e1784d0c8681371fa24591f45ee11bf6ac34f139e2e93fc8fe36c263699c7535b8b4ea52649a195c3b4c70e0598f4ed5ba82e7ce080889252955c3fdeb44c7f6e75ba5810e130d22c35013ab54450a412f4c81209eb0fdcbc87810d0401ebe97abce7839155c74839b95db9d7727723c9f53c4f0009f34f3cf0cdd48cba5524a6bece72b3000549b5196f0e79f293d25438fe899aae6077e4c9977ff1475390f23ceeaff6e71f38651fb796ea3f324a860d1c51f097291112c4b0ca39006b54e1fd7c131f298f3fdb2c583e42844521bfbf10068da56fdf7187f70d1310fb0eba68d1f3f732f340e6a2492c6de93500425a2e8b4d3f218f987c020fe8314ad3cf0f56a0c762e7947cfc0370413d231ab42ca9e25125bd2e53119d08e7ff190d59ea8176b50a1b88d4d23381023f42ff8cedb83f24f094a2ba6d429b2d13b56e0e9f35e100080189a24e3911aa71247038236e8fd2fd7d4a823bc0ca9c749589a4e73070573943c9c964cee691bf4e650ba4a0e9435adcb164e702de00fc1f9b2da19e18f8b684b600faaf3c00b6636840f223e9ba0929186ab1f2e5df02c8f9fef6c58a910be0b260e0a4b8123f6e964527000c2fc7a7f849b37e5b0d3cdc9dd2dc39c6a132004da5b5004a32aefa49576f88be1214c454fb6145594e12ef546b5f9f332399fdd786d253be7dd1560598001e5b0d59bc14de524d3a70a824d9a61bd77ef7a74bf00f6ce48f735a09c21683dbbe1f6320638f31c1a7ee88b39cd21c766e0d260001b9293bf7527f9c8a7dd18b24b103a1f82f13cdc1bac749b79bd86c800bd5ad5406332529fa23b04fce613e131da3bf5b244f4610c948b40f42d87860a941dd4120c89f32394a76fed97add37049928f8663c2ab8a4499c23cfc2a38739a67398317ac67fe4e4d31dca9fb8b33a34b829a7ffd1dad9d14b26b38248cdfc8ca1cf6d6f9a6f84b3ae90014dc5014351ae69472a05fab684cb3d97d1537cfdc46f508a73185957ad1e7498a44a4183a59939536880b3ad5562099669e790bcbbef4483d8cafca900db6fde0373105fc869e3bf6945392538c547883a1d163e01ba4fa111efcfcdd3cdbc58bdf264d7804d8422466356a883942ac18f529d62110f0dd96795ee10423d8b1fe03e8820714cb9a348b5d87600b85aa9ee578faf94fca3e497c9db58d1c6d6a797d196c840cd2ed67a869ce49052a07f6ee236b2290723ad2314929d2f2890fc5ad0b1dcfc934b358cdf93fdf4216a39a02ad83322a2416cbb2a700ce45d71ec842b3407434a636b7e3c180f5dbc311f5bae996cd2af8d43995d2d42f586ba202a52a7ddfebee520a1b0bf2cc7ebd164a599eb6385083e0dcc40ee16276cc3e3e85d5c7291b5d6b41ffb09ef632efb8a958700c6531da06f5eaa77ef86b013caa3a8c4a790f52508d688231c07d2c54ad947b8195f2945fbce0ec7d84c13d754ac909854d5684ab4c4a67f03acaa46d4ed44d66e020b613721e23eb6e091260d77ffb2a2f09256626b27e86066429b764d8b0d5de72ef40b08269b3cbf93cd9ecd84dfb187fdf5e60f7847e47c77e99a369facbeef368f1e0ffe25f8b9ebd15d57174fb48fde55f5e24c9df5112301a0279de61a5a0a6adb9ebb2a031276ee36d73387e1b782502ac358455239182c1bd199a08f3a6e63d256a0c5e85b6501adee961af1408530584d25e4588b9e0bb320b9b99429bc39d944cd71f853cb34a25b0b0d2a0d0d36f0200c11a25d762571c89a41ab69983af23d17dd0e3c2769d9251842c52098bb4e27e39ca29e1e32f9629c2b5345da0d3f2658bf6881efe30d6c372d532247195119679462407e71a012e345d5d344cf33bfded5f96c83f4e6fb443793a762547c9547e5670aa13c30bbf2c285fcb31c972123e0dea503fb63d11cfd95db8d4759bfdc5e13bde05f0a359e72df53bbe07589484cb0a76e5e88af9121d96821d9c2d0b9f48456d9395f5509e9dce8477dd98fa23c3a97c4ccce057ca8d3257e6bd1ca2253496a512f740cebd6b79d0f43dfc8c20c04b3fdbb54a82a2b574abf0e1728698bf9765893c2e57580a635c7ac738b74ed4ab9741b1aee89835953b963fb5bcdca1ea397400d4c9561e9ef1709b552bd2bcc61e61c90a8afbb000905535932e2f1c431a88fb47596b28f2b77e2e5002599894e9ebfcc57ffd90cd66fd0046c89c54cdb4d5cba9455af5f75af6b4bd9fd40b2e611570b20e016fc696e79794257eca8490544cf69691fe7a91cd0c5766b58e41b09c7aac2efa6e04d4d8d507c345977a41f85139109db972cbc019c7884124dd8fac30dde3d4b04f20aa5f7dfe96d8bf1a689ca3038a268853efef2aa42a16c7144ea5bf52515d417ae7c25306ef4fb5331f2c0e6786a20235f502928c52cd137f3942705938149d778b9fe1ef332bac476bc79c30eaeb4798cd4c6cbeaed99412203aa680a3c32cb1aedaf21a80fc7576aefeb6364574851432b96f5c781dbcc075480827ff236b59b5d112b0e0aab609d5a3f56f33e5f37f44551020c098f46059a1f12600b62ac0969f0ceee551a1fa422fd024bbd73842b44df498103b3d3f6bd413f05db6a707843f3d529782515d35a89bb6371b14009222aa28955180287ca638378df71b40292c7c7975e47e863b607019b7bef6d8f8201fa340864a551783707c326a42b08013a6623738fe81b0df7154725ac842226a8a702b4c5b473b0f3b581653d3140f84a2e905085013943ec910704fa73dc50b856b84c7b7da5bf363aaa67c5a53ea99519696a064ce4868e62927d222606a982162a6db051ea2a0dda2006a98a36cca178225fddd61ac8a386b0cd27281b5e8ab53753ede6ba1fd7c226035bc4486438ab70c7e2f7db34f68155a0e9f72f5ee998a11a1e212280829b2a875b1ac695f5891184d66528ebe23c1f1bf7ca2331d5a6d7e852d394f59d30453821cd2779e19b27adb3913afe4d3eb49ac39a150322860e1302cd48188dab687824337b179d408ca5c62ec109a1164a67dafd702cf74c0fdc5b5a4d45951a7ea7e04712a721eef5a807240394117d85258c296019a1ab601395ef2dd4158c6dcab0272b0a75755fd548962c395656c0a0c768be20ec7b06d3ff04e8196355a46ce5b9876d78d0e1903629ae905e017f5750ef3329c4c91a676469850df96d0067a8b52186e6926384507b6431e31743357dbcbd2de476f00fc8e0280a8faba245c22708b12585643b3bfc4d831f14fc67d2ffe8d5f531471019b2594d79b86b51f720e1e3249fc628234f8784c3f27271cb23faa5b3de11ba8e4de4269d28c22b429c99761cdcf12c5af5b212e0bf07765128082545c84484d485f18df1a884f8667ff17088d171e8d146611ec288bf6935a48a68071ccb702f97f8c14c2b479b6cb966a8188f74101d106e5c46a4a58b90400519d5eccfee18a2724464ff0fe1cf64a46a4a6f036d4207aef125877ee0b5f77283454737cfb374bdf3778f2d6ec075ee5eaa30d3a6b532379fd9c87f1b15a354e8e747b4f32097abca7c804123c116efb877432a9142271a7321486986933f4ae98a4ca4182422d028dd950e6960f1d83545963b056a55c0ec94ed8f7ef9c3450133b87e4d62076ae85afecaa7eaf0466633fbe4300eba9d457eb2cc6a248caeb21d70267f0428646c4df35f6dcdec2dd57188f3dd2ab5828d9748689cab26cbd25ba5b68a8ccd2f9992714bc43c5e74cdae9a5876dc6896f5b2e3bf05a9c50e46039ff6ff74b7da68e8c8b1cf74cd30b2d086c1cf2a12277f78b951969b7b2d44c4cb90c95677941ad416a45a34eda491b9d1bcc3db7a7eda3f241161e6fe54e1c1182ba83e8b5d3ffbfa7300a8178e35e6ecec1a836115371eba3849a4159283812bcc19bc83983925a711bc0c74c24b9fcfdcc56ecea62ac767388edc0096db85603ff8dfd0e0f8eaa43bc07606ad438d571026e21412558b60f4c3cb8b39d35860c9897f302aeae8e845e74e0431b2de4a8396e02f61bb9686269cc18bbc2379de944abfab7a50ad2afa3711950a04d3a58fd253da6e29e02defc1595e2b4e56c31dd0738e64f7079e78a6f07a62f4c3ae8259568d06859d12e826ba13dd0cfa9f6bcdc269d61db438156a585ad373809d3d0ef207ac2e72a8fd28e711f83060b2ad177583e2878dbdcb00f4ade57bac4ac288268a5690173a4dda46f4fe1521dc9c70f6f6becd5ba191d4553b6f6d1b3cf32cb13be7908dcd21a99d833690c01d723791ae77ff9d5ea3fc9b3510b5bc8333a5fd854c7a6cf0b2ec5c3c9b18263ba59ee4e59d31b90548e67692efb8c75086259b30572fe1e736e2fce05c10213f9871a0ea9de9e6664df3b5e3329502dc416e08958c58f87bd150088ed60db305153cd0863f662b9c4e1f1bdeceec780fd6781b13205b2f54337c760861558ed1ba58486d643cf77b51b5dd1369482a245e22cee8c52152996b3da9fea36d898d494a6a75f58dc1b655c3e84c0493c565f4824ee666121796a1e5b7e48e0d6ffe57160cbb53d9059f3c7f9e7df29bf898588e0bc2035c4f044ce99230674f139c3f974e72c86db78811082d657724828228d5d4364b6c4ccbdd28d7d7f218b61a15063d00e41c3694df61c6a5493ebd99648cc30b871c1b6ef7af763be22b3464a3b2915c5f1590cb93ef82ba33e7df3d0c1f6d6caad4db069abc4ba2d8e75767b6b6b389dd9e9acc7bee2ce8f8252076c122125472355480e466c6478046b907e48396d52ff409bc2ecf23b0810da212fe0603d890416804e745cc9ac727d8082c3fe22f9a16a74132b4dbcd20f3cf64d7ac058dd1cdb205a71363370c7777beab14c67a76c72bf1f9c4a94ef2525120b951e09585c12b7aa617642475a4a0a125f02347e44196abedee1b796d6e25133fb387bc211331c3341f1b215b2cfd17f17aa840801eec7a81dabf5a24a5202b7ef11e5321a0470ae8160af25a2f839179ce58042b9bda0872b8f1120d4e2c525cc19c3e101542a23b55c5171ec27b11de4201a8b147c5caff5435e0e26e7c91aba3e467bf52fb84a40da31644b83464640200a87a8b56e7e7c66d2529bb091048c4156c47968028fe845d4a1f9ed85faf352a0708c557e478f3d25624f26ccf33a03122611b95733416ca80431e39a3c31686c14a9be92177d83d479fee0274c89158afebd197abf7b227bd9ae1344041a0986bae79a5c4794821a0a3fd24e396a6df105c474a8d80c5a380c215a602a0b581aa4ab563e8df2560a6cab580c917a651214ba661a3119835c6327b9a841a6226b14da0a1f46d0fe27992d4814fd3ec6d9abfa7585b130084876c6befc177fcdcac5837ee12faa080e02be856dcf0809ca5c4e8d4fe48e3d4f0009931e1f9f419215552e2e3738c51315282b2bcb26f0e44ed0cad2ab671022f39e3c3ac3490fb578a85d3eba7c3b8b5b29047bdcc95f7f3f5709714e9abb739fc23e987c16712d884e487d4b86055f79e34939bcb25cfd71ae8741ca29ac4b341251af4e95dc059d6d5719c019c92a5f69351ddda19d55e819971be757891d7ed5c51ea8e99d8f1d42064d535f68f35222e50de90a5d87cecb60ae584319c9562c0af4f8ecd10e3a70b4a5e4f117ea4ac27b9a0e8c66aebecf507850a4512d435a89eb84aea0f7f1c6341a3c2a01a11f285ab96894d806b50b1c9b0d054f257da2d5fe063c6636c496001ca07a6f962042d452a180e7e30a97402bade149994e162d66ddd89b92aa8436445585f7f0237de7d320662a65c55516330093f450b85013df5200a211ebbf2f317f094790bb394eba2cae64d0029a702242a6a6f4757384978647548416d10d720298dabea33af40d0c608416b358c054b86ea56b447a4f914ff987b2a631c037fb188320d8232b3c82a11856a85b9e0d0afb02e5b4293de50c6b4b48492be1d1867090d1552019368fb07d0ac29a1bab3e5d28c969450a74ffbe328a1a84292db50773f055d1b93d0deb696844e8fbced29092daca1adaeee7e056565241477e15e3eb32812ba0a198d446b250a7a60604c245456c841ba74db5350b20509ed8d0a48e8e88d7be74768c4723b911b55b86859e8b9e5bd2d5dbb13977bd4d75c743deb11ba5e1c1ea10b4d2f37233ed38125b4b93dd211ca9e63afe5b0952374b73b3842df4f12d539dd085db1f6aa052a5e6d6d50deaaffd8e9b9321621a7757b761ccfc5cf5b798174c751f287534ddedfc120e9ce34ae1724d214872cbbdca072454a6bdc877797e41ea04cb3b99cb42b7dbf874274edba3354c85439a95ef28677c93f53b1522ff6ac76d96b5ebe6b67ae5ee7b61bc8ca623e954d6c37c36bbd97236140156a139420c994de88636dcabd57bb2f57e4e0a01ff1f96039a4a3bddf33a774af7fbbfb4328bcdb69b724fb3e4f1031c8f63f3b8fdf15dc5f3ec1e275ac5149f928b84733cbb5fd2d6069fc78f11d1a26cdee3420351f5a55897768924236945d990d497b1642e80fb56a123ac3e0fad002847eb81ab2f0d88d7bc64cab8c411c01734aacc51acffc7e414604ab40be7dbc09778683234f2269896a9268a74b364608b4cac65e1e2103aa26695e822c2fb7ce91e056ea7e77635c34ef87636c361544a72fbc402e6e35372faf6601a29483fd143e31121d105f3b7514d69e58201a0ea0c7daeb6a759205c5116b7efed5031ffd2f5a6c2e7248b7286bde509c93fbfac50a6a12724e9ab265f661cde0639c916f8cad718cf54619e27046c0e50b636dc70818055e541a3959be128b11c858f32cf81e7506aaa46c18ba0c23209fe88ac476ed5266909c5c01887a4cdb7848d245f5cd999c2a71e6023d919faa3c2302b4477edaf172c73e605dbc56dd6137743c3e7449d9f6f37c4b94791b5580008d94ab168f5ba03cd4ab1809c42ac1c431c7fa8726f62caa2eef6ab518d910a113edbff55645c79b3a290c97d3a33518c9575a6ee9ffd4fcb0e4461b640c7c9af0ccdee03198ee9404f58bd8feacbb433af489059c833768b760d8da7c510b64e43c43b9c05a83965e1876d6f06cca0d428721f91ba5c05d0e25727c53e45a1efae588580d6a62808eec7a09c42e39e0d1720c621792e6e791496e4b698cfc12fa6d1ec05555a91705d0c70edafed69161c5b18bd423b4204be7efbcc660313d2f2923deac76b0270d54e1d5c639159f4b0dce7f65049f751ca48199e7facdef8eaa12d97dd5e37776d7b61c45555644e86a363daab0a3b242c2d42d555afcd0b708c2c450e2c6eb5b73c953b13cd4dcb6d6c625083bdff58c0b3816ddefe023bb13e2471ef8d592a4547ef161428a98fd4f9b8e0421175262f63a891e4515f5b2c30ed426ec511b628b9f67b613cfd18fbc07fe489eb1bd984fcbee3e827f9ead1324e981e47c78aaef5b12d1521bfc82a72bd053b2b3817102bc6b352e3811c857cfef8d699d0abce53f4d942e00b92fbe2607a8179f3c5e96ca9a95bff45461641187054b33486f99b1a2bd028c2382e9f6bcaa73514fac563cec8d76f9dbcd55f1b3d9cfdd40211bd393808653714748d7ea0cf62544540ef810c3356fe337968bcc691861cc87b6ae78edc74bc05fbb5da37dd70cdf5e47d7192b7fbd7784d752fe85d32c6c035ac28943959dd942ffad0d949aff8c0846f38d45642012f81f8152e4b7111b9f95b3035a1f7ec91c3f20e457d4787105c2079cd583d083b97e7cb7d1a1034322c70ebafa61d05d9d9c7273b8d2a1c0dc0432893c1987b0d37698ae4434073267b887b34cdaf8f94e43ecdc62ffcfa0c96023a1920f74d1e12defce9c8ce8dfe17d61ff1cfe535df28df0c76c33491aed7dd21d3bc893376667c38e91a91b975995dfffa37eae69dc1439c9db2d8f6924c8c0b12b9fdb55d2fd6ebd93cfd93cc04bef9174643d799b2f2a983892b9331e1ab47da28768004aa50f3475f8c34f282d6c1630e15b85c33229a37b512ce24c8a3a8e062caac3d1a948f20caeba89a6d9b63aa7dfa78061af8d6ecaed9729e7c9c849c19818c5af6b57649f5fa6682b13c0412486823703259b47d1aabef558507293f6bea334172221cb6d3e2bdd4c121c16b58ea107bcc672b0ad0db7959ad5ac03d8c4e5dcc5f5c9958d8efb40e87fa4c2980415233a5570afed5be23ac1398271643e0cf4fcfbb2d858ce728dc9e8ee535a8dbe9660543692ed7e9c44048485833a84d507cbb24dc21b135276d8b288d21678dfcc22222f0507a28e5539e3e374d226bcb87a659bf559ec6a1cf7ee98497b41a0d5154e86c840cd54579027accb0bb68d70887fdc38ab4710288de6c8d1038235101daa0c6d2037d62ef8a6bd79594e5ba16df796597c89736b2772822a37b8370da61d5d6eda9e520087ccec9eb7f2564a759387795efb6eadf606fc14ea3879d962756e664a11c0c83bed60f2c90dd3f9b4a17b517debed14d8fab6aaaafa417fddac7738ed09ba51c7159e00096822e6d518de3b4342319a8b1704479a4893bb2298f2e659dce0764ceaa84c88578998f2ad3365823d2b6daa9bcd1fb09d465b2956876b60931da9581e3919799a0cfc0daa0535fa7e50accb2972d60273d5e49c257a5d2a20c5370c99213b5a051719cedd194bb945d9e1df41c5562b08cc076a02e7317038ac1a195376df483bd56c2d846ef69c54fa051577bf8a937b1348d938bc959a9066ca2eec663d23108e54839d080c6f1f44d8fd79c9df92e218512b15500c621b0337bad62d661e379e07ea42ffc3886976ad3691b16c516caaec2b35d6dd2e4d0aa8ceacb796cb0f7a784b244e074fb3e638ad110ad39ef9aad73ab065fc26acc9e7b6f08556f75ccab843a47ae007806005c9462cb1a39d1b302f2655e8494d7e6de77b9d2968c2b42aa4da1ed6a20cf999a3b31274e671fd3322bbb3360239495c62fea4d3591c286641c17546345100b360ee5b630b3df11ce552fa777a33272ab8635bdee5f3720aaee5832ce0021656cdfc295c0dd8ffdbdca877147ccf3e7b1bcdac2c2c0b94665b3815b3aec9d8a93622f3e8858bdcac3793a864eaf05f8fc92521f823524c0872f7e963eb053b4597bc9bcb714e2b6830f91caaf7b2049f8cc51b24b96749c226a6a9e0fc734043db674aad4c83225ee57691ac97ec69c24c59d838d2479ec136c0bb6120771a59e0a04af5c2efdbe8c071466416ae2a1929ef18e6d0558a288b1294ea90183d3498dc56e0140ff91543439fc868e6e1248e46decb63c4f39b586fab4b218158c8d868013b67b0142242d0d74147fb10748b1c95ce001d58338433b7907435fe2578d2a0320a1482eedab7d62bf594e231bb2058e44055d06bee78b8c97e2e6a775a00dd25eae193c3f48e3b7049ead8727dc23ecb1d0ce12b657b56424b6ad71d246c01236aaa7694636e765379b261d918aa89a9b6b8ccf44fa29b889e2c2d7238df51a1675df84334242acba0b5a49de1a74ef81518c37a0e05e22979a08c465305b6e850701fdda840206e5c161af9fdd451291114213c4227ad4c7409b22f862571424ad67f5b6fd2e76e7dbb190883e1917e132a1119a99b5c60285382c34d7ca6ed1d7eed9f96704180110c199240b04293dd6ecf6306ed5c6e785e9befe0ef80be17f4ffcd8e1a9450777fd694cd1a7e70f14c54e96da2b6cf5c19cf11a84c1f789444fb8c23ab96b58709ed908e00f0c1c71ac91ffe5fb2bf5af5502a6b48fafb62314314b0ff4b76668d54d4ba356ebc93d6c33ee7e37aa5bf0c2e2805ee4cc53586d059c32fd66cc7e6ad62009657e85ee34f515a3098888d50952fe66f9cca949405eea3ddbd74d1880ed979d2deff11e6f7e1e0c89e3fd4ccbdd7d7033ea881d08375c8428c68418fe721e5c709a8e0ca7d2ee833c5bce236771b03dfe292d3018fa71e44049e43127ab0423eb4cdd6e2e02e5b98ebec56344b18c1401d6d503f671b8fe35394d0cf2970594e1a0e51ac5210ea11b87d178e77958d92462c7abbac0365964ed84a1e259e11211200520a80a410077c9a82fc0d053410557304212bef298d9d26a6c26c959ec60709ca09e0b97aa9ff82177a248176653c0d3614a8920624a9b93e68244f1e5007275b68c240e0b9a81f5deaa4a3343d39ed7a5c873502a5bcb2616b8f2dc593f5c96aa567ce079b81cc0e94a888e680c94d1640c8f4622ba0bd23da6a63b0e0779c10fb292eebd965fd7c7b3909435111d95a89b56cfe3bc364e7247b09c13b64d59a7edf8d6037bed26449427ebb70618524b8018f1ab5c92cc8c3bc1ba673506ecf2b6da122f5d56c5b9db80ec7481a128ccda0b3a489c1a08967128493c7f7b80eeb9ecb556be79e91a581862b38820968896f1830e73751233130c54493e150312efd40b5e5ec49dbad661d75045725709e8eaa91f114a310d324fe215dd8bb90e118cf6c6fcef970cb0902526d62f43524037c580f0bdeff2364286593c737c1193190645e514a39bbcdaef6e1a4b5d7508a42ee6c650382a3d75d5198fa57ca6438eac7cb9774c62cfd97029a84f0109bda17834eba62521e78c0e81141235478e7f9c8f4d7260b520b41c771f8185ad74e61c6777e506cb13aaf119d09f6cf1aa7308975791d320edb0990e9e9471b81703e57a8df4f4da1a88bfd530a03824b539f0a6debb4a5ca10781076dd73badc1aff31297bb04848bfcde4c015d04359d06dbdf1053042b7c3c0e8970e2c0eff86ea8dcb2bb67b8cc5531dfceda526d6e1b50842a0698e4c43acd8ab794df658d9550eb5438f64a1f4d89ac747167bb88d09f50e58a44c7ecf9a028522781b9b77fc666375da1f214513de7aca12284c752f4cb3b83b2d7a9827fb76ef76bac041b7f465997da424c813fa806df4de7803089be0f70f245a29e30a7a053457cff9bafb96511376794fdba0f90e9444a9a69ef34a70d3f4686e190001edf29a8a3616332e0f751e727e808986d38ae090ab5d4a1d89aa501ca5345b6d1dbfd28c13e8de57d3d2867b3e7a3a122fa916a142dcf2ca6d7fae033a66012b93abba2b14558e3a6af80af11d0af34c226bdfceae3bb90249edbea84fa4c21a7acd0c636963d26148df51e780bcaeae93ba42a631c7e21ccc2400aa545560ad474ebb26b8d0be7d44821a7c60cd5b3fdc8ccfe853842becf251e62bdbceb4f387de35225bf9ea574ef15fe675da958636e05d39381f7a720ca9ad405111fb337f135e64ce20cadf302463489802d927b954012b9e2f9a41457d4c4e60327daacd1f7233147b7bd2d245a3da81a8fdacec4548a4fea61c31f11ba030f199b6b82dc01bccb521079778cc63d5d1884288c064fec84f7596bd3296e9284a428ad7afe54724c516ca29d6e5e563e5b46fe99f57b2ea68840454f6be28ef43031098228d4517c568360ebf8e2be24bf12a2267f38dae377ac7ebd9b25391af46ed58d21398610f1e8360eb61fcc9c6368118c5ec6d82da7c32bd26fd2a296985933ddd08c15494ad2389aab51572dc91b5edb0d4544c83c253fc604cbaaf6223174e8c54abea6ad72d883c4907ee11136b41760f157620eaa9548600802821adf88c4c8632e207d293a0a418231d5a493e4098af707634ff82ee06ee5b44e0a6824492eef940b721f9a2bbbae517ec592f3696e80013dfdc35402e044ec6f95e0c2d196cda8cf1ccf4fa1ddc5c9fbbb8ce67c3d2859181cc6e81e4c64af9e0fdeb85c1c1779713a09151e6430dc1d29ba9f4b26e800b1edb8b3a18fe46a4ebed6d7f36d2fc4472872ea5de128dbdf8f77732ec603ac40b1843bce4d4de7843465e9a07d44bfd293930dd43558171a1ffb2673880c757572888f8ea2ec23c951b44805e5c3b3b0f8e3288a68c9787421d17e446b9e61f187a8be8ecfc7aa23d0cdadcc230e15ca9a26a70f14ef4b1469c60b7a13023c92f01452911a3a97c17e61b4df5ef4c0980d5abd8b8398eea5cb08238bce7b72be43a673fc5b2617e8b5327f05f1dae85a7ea1f7d0ec6826655ddc56230f550da93a018b163ee31d8ce8534a99478bc98348a66cb833d9b6eb5ec37115c585dfa86c8db801f48b1a46790dc01dde3f37f1e4ba062d58f72127c50ae229427e295a6fd8614ad4f7ccb526c403b73541eb9c4b201c72bab23ad9aa3134a132916491647ac3f1087764d2af00ceac4239667ca62a8483d1b7d1072707378370e1ff46a17aa71b740362c03024868c9e554b9f9d9f695d4a0a65dc1733baeec6b2f5d42305d6b1dddd296bfcb5a5ae7a1479626196f1b2b7948dced62e40a3664136e1d2b6b7cf0bc5e3c31f5ca0fd28ddba703a2109a1752ebae92c3d9ed6214e40782fc125cbf80aa4a158c9551508d4c99569d668c8d7b583c91c2e80e9b8523b71caf234719fe8ced941ae932750a140bee88d35e0d76a2d822952d370c7b503d95999071355c8ea238e1c87b71680a4c728666825b8a7829229af00d431b11cee8c4e67d532d74b8b4a647a09e25fc87dc53acdfec98f959e67bad77cddd35b9e2c02f68c80b9e1dce0224483ed5aaf863b8e0a1e6afc1585e5ec2109ebd86c2e0aa7e2a9785f09a97ab7991bb63c7d5d9c5dcc6966b0326946bbef3793e8175040bc4dcdfd08e7d287c184b8a091316558784ff126fe118fa12978105dbadd24a98c7f46a6c29caf5b0992e184f971a386773ba188dc77ccb2ec0bd99c8503a176d8535b6934b65fae3c1da73c5f6f087b76532beae45970de61532aefac91868783d5eac28e4e37822bf1e7dfb718db5144fba4a7f7340c35be09f9a466f735d545562fc2f58cd3bd8c7984eb31a0eea9af6f8f3b8d3918cbe30d0dd1ed046c11e1e6f05153b86ab6cab39d2de3f3a11bd7950ed2c55d87545eacd43a9cc787b5315f7eb7c7dbc2e38a2c7a9007a025ffb0c9e1a4511296e7431028ae8f5fa6d7d56093a295c33d2797fab2c3ce7b79a52b081597ac1f376c5af084f1125608eddddb06de53f2c5943f107f60b96101b776fd9cf7951dff8dc4e0ae482eb6aaa7e995d36c262916e2106710e84425fd44ba3e6dc40c3d695675086dddc02bf6015b817b91ef5f3ce9085cf57bcfec6ccabf04f6875e0978bb87b89c8740bf31284c1678d41ed985a27ed8bd056a6369e90b74dbcd331a0f9b485e5699d63aeb051beace7f99cb716f973462c4678f1c4827ecafe890b5084799386a44760ad037b041a863c52982be70bb4efb3a4d045b40e13b5f8d7d38c1b4237b7c8096e44549d79ceeeb259ca93d6ba4984219c0594dba8edcf8e0cc9177f2d638c1e5cece4a85a5b05af4091d982975937e201dd7119a59d03893107213c646ee662ef19baaeb645a7918da950b72ec968ed9063c84a6285c75bd6dd741c437fa8d83a633ee690b3249b0e08365d72c5ef5cde257368c991f7a0e7a62a5ad74c835bab17f1987f53589675d07ad771b2681970b4e82202143821e64fb42fc473dea581dc00f3907a065e871b63b36893429bd261ba1ecd946a5c6fd526cb309285c5519b928798b92c1f073a795799be1031bb0ae3b6ac7c26a61d1c2ab2c7a816a1200f6cae1cfa3601db92732421c28fa0b59da4168a06d3925308885707af2e405a0d5101e98b1451d26cdcfce8d8ae6c7cc47f84426af864282a4b9d34f140b6f6841f9cfc8b96eebf073a64d63019cc2a95f25a29c10597eacd0db3b5124697bb0fdc17e32db65be5a0686ac5c53b3f128db50f6773091094d7d2c46cbc812efc977908f94f4f592e3b612552b5f17a3c48363239a4b5481df1c19852816099d6614a3bc566c35303c040e44abf83a53dce99b18a006a9d85711b240de8a8867e371ccba8ee2fb7cc4232f0046ab09a825791328a28b28479f6e249fba1300afaef3c1896e0b55012da5cd955d50ecbfc691893932c6c1cb11c17500839d6f2de8d440de41f05baddf2b23332e97c8a5bb8e401030e4cbb46160347a7987fa399743f09ea1e6ed009174e6ce4e230f9d84fe9e37669a8ab7cc2c51533eaad960b74a05ed1d7f7d015eac711a0085af0859872c524775db8b5ab01a9b0b98ce89d732be0c151a71d0b4cd0c8237160507e316f3ac92db457267a05c84b6103953a595ead162a50cefc1a87321c18777e0e1dd16fd9091df01ff58e7d724fb89311c3176902ccf0ec7f3592abdb2183692523c494e483cc9921a4addc1afbd8cfe459af415f7cd5fd6136985c04732bbe81c41d9488dfba1fa3e8b93976bf397d975732a5d4ce846ddb29f423a39abbce4a8fad1c0009dda2a45b60855d9df0f95650670cf2d69b2d8a61f983de7588d6e0589d29eafed6ff59e25746084589dd3fa5102c2130a0440499dfbd844bb6920909742c038a0d303665756c98a4e01739d45103e30b0189299e1e48bc2c33053a1aeb2d86848e13f2a541821c097ef91373c827966f870658d4034c67fe532249801f3d65217d7475d3572984f31cee826989228eef5b22c5845491849b6660f7c92e2ed4ebe0c9c9fd7b95bd0bda38eb0ba8250d6a79b9b2ca0b37b1bb53a9e9d404155e1b8303e74ed547d6a02e1c48d6ce8dfebca0a8270c418864e081187e10ec25d0b97ef9976b8c8a4f53bc3a4b846d853b5938d109bd8a4cfb215941cdb9b31595362788d5edbc366af009cd4df28662ba1cba000b3a43d841b40356a8ac581c7e197dd436811f99206112ec7409465a7b5861a594b38193f3d24305368ee0652d263aa33902f28ed7e6ff48b70bcec2d602ea9028c28eb05a34f420a130744ab4bcbc89c6b8cca5bc0b64f8f05d08c80854d64810c9d9cee36cc9bc2b10f0604cb022c15d063686e8722450fdf8735833e706a96845b6d5b48e0b0c684f70d5e39b97cf1fd0c81e08ffddc46f595aac8ac4b8ee1d010be7a4791234a422f9f2e3df424ec4c46891ce9c0f2f116883f4ca1ab02a63cd68e3d7adc22457ecfca918fe5eb25ca3a38548e83b638524c08d0f22c91086b542012a9ff50dcd107cb8190c5e515968764a849529f84f3e550dab7a8365d7e63955af5671941e99e29a01f2b6a66eff0b157724779b27c7bd05252e9452000e3fc6dfe14cf44f4caa6408001a6f3920a8d90c411a4f6a081432f852a7b35f54cac322f991d2411a46ca98daf415b10612f45880a7747a8900721c43c9172162ba6f04b711bf589bb1ebd13a792f42244718c5349740e9efd2021122f1b9ef2389e9c0971e2299fafb3afe3f52be8f9a85561c3671de78cbb03b3248d9e8c56e03dfe4b8e430d6d2f99f95e50fda1dc69f08c6e820914101ae48cf3a05440803f98725d74c56492f88ecd90e07e409aa94cf67229bbcf9803737b7410fb61b0f6c91a94ec1bc6e1998431056ef4f40185170d30f810692ecc156a1451dd9ef252a8b6426aa195c18fe9e237a5eef663aad210e1656afe1ea25f19b0350b1e7e488583659344f2f38756d48773818dea120998a5f3d0fa15954061a500f278ef43c84e5b28771f145f4120b882bb1124128a2bc3de6557898ee670d47a2803995021f33d984195f8ed40e4e2471030674eaafc3453713af5cfeeb1d475b85b00c5d8d7763aa5908272f19f3626a54380cabc25b943d999a18b6f0585d3b0769f64a0f8dbfea14b49a0c25b43d74b0c4d8652f690e2a91039bec5798ac8a54e22d6c2bafa03fed4a7cf649adb6c9697ea00ed19f9df0d2e4161aea097a3f9663a2a43e445cd12665d26e4576b0348935e99fe3e9ccb15a4ab083ea86f8a90cba53bd66b229b8ae93d6bbcd2d2ccf086af932498a33a4a281af1b725d9874d6d70fe38e315c5ec59545913e4f83cc6c2c21869b6803bc2f8e9503ce97d167bf8fb9e7a1f0f4f08b232191d8dc0542370bb953d838f0b3230f63c32eccfc82b995bde6a343b46c731d7479232c986c1c60d834c34d7e87c1ff860d6e994f4ce1ddeb0fec497c1d017740280c542093e6d27d0d673a8eb1a40ddb0d64e0fead811a3777a8f75e087d881f94bd28a278617decc4c410e275e81a7f06fd8272ff955d9ade9d281c24fa82062c0fa09863e5bfdcdceb16fae0768142e6fef3976ead0f0cd6c0beca1fbb41dfaef4aae14eb86a25a0fb9d2f1d70c09bf167fd069215c36efe345c3f60299d726dee5cd90df37f42657c69a2c6db2cb639bbc027be6bef959f5d708574ba312710614ad6ee47f6aa2b89791e8ff134607f016deb88ad3b100c49a065f6062e6ef9cacfc28097c3feac7cfe0592b642815de705bb178c88f54567aaa3cb76b6c106a3a1ff04b4cca2a9bafc3f8181172dcc72fed7f822760c920290d1a06e15282edd559f0af037d4aae632db830bd45a0cc130aa13d5a4af7ccef69a880e5ed6febfc5ce314b900eb074aa9770df3355ec9c9519f1810ed445d686a1387bf355a53ab8ef27b10ec13e45b82334af3e41aefdb0181218920227e95e7034f833b083195bd78636a9575b23998e80444fb526a0568a57bb20afcf4d584c02028cbc1d760851def0167fbcbae31e718052b1df96d6c7a01b927d10dc217c398a738b43595e8317d1b295cd507299800b3f73236f1c6540820fea994acfa1e142466197f632a8ed7a166bb1a288a13d52b8c097460a35dd8d3543a1162fe38c224dfe04a4732553a72a7cb31d4cdda6c24e5a9c09373c97fa4294b87edd8424969e1299e3ddd2be3eb10739dd5c9f1c7646131a62096a7f2cfbcc45d32ccd8aa435966590abcea105985331de19cf260875123e48e036c0d558e5170aed318f13b70a28ca55655e1fdb85c777957b2b6e75f10d442525dafe839a46ae12299c0cfc4694c4883199322bae0d752d8f20cdcbad6cb8b00053c333964c71a69dd5f4989ed422c6b512b6ee2be78a0936b507c1f2b05806adf76481b6e4856396c9994b1f307ecf631d5e2d4c7b3fa4adcd4810efd789eef1e70b562472abdc5fc77690eee9e8d1ea4e0766f4e56e007640da223fa6f295e04087c16f4fb9cf89e0b2d446bbcd7d2485812ed5c1db21e3dfec3f0f747c09d6f163f41fce474172fa2359fe23de4d5c72af14d57f826d5e16a2b23439d6f983336f2b076b07251cdd082953dbd97032a440167c4ffc539606fab913904098a19ffa024b560982ee9f8a3c93f743f857692207a4f733909fc07277e64a11c000cc029d6e4185beec04964c202920f7099bdc2ad82b76daea8d1029c90ea46a9c61fc096b202747f1576f1dcb0fccc1aa9b1a33d7112807c26b26dcaf57eb47786ca0b0064cd9f9a0e833125ff97fafe6a5c23a2c565276580f0910430dc2e21123d74dbde11545ceba84594af207f92d2a1810785cc3880ad3be08be90cc1821aa3177cc1d0e0733d078cbf3a5cfbce975f1fc899688535fb132401c264e81ad0c38349234f4c89b81cbca97f2faceb6149576fc662b70253ab4da8a6319a50faf74123b1a8dd2faca311355ea75e5d5006bd0182fbb7724ca94b8b8c9e8d0a05442862c8f7566cef706af56613e4f22195783f41aa329683448f6ef72f0b66c3d44408bb319c5e4db8e85683ae99b1a217e8e0140fc353b3da378410cf7af89a052763bd84d70863fe49335ff7021cb823f02fa4fed52c2022e37d42721ef60fa78a10411fc5d0a7bff3e6d8ef432380a2570f0bfcc3befad8c9ffc8655f18e08bcc0bf6d371de3084c287b4213e8b9cc81f3e6436f8fc039018a654057a68e3e34efc1aeef476e4454d6b53af27f1c0c7bd0914ae62eb68c4f487e148842a62d91d728ddb8f6b140b28555d67dc73a05d370215d293dfe47523d00e81d06957e52ad79f10b0200012145b47f506fea40f3d8c5f33c80e81931f2a7c269ef8ce03677a3e17e17f4f03e879d421e47aa64f0004433ae9e8efcb4ae29f8448f77f30c98a3261ca838b92ab01a36ba3765fcb1b964a7bb8b1cc62e85cbe0d61d64c5a27053d30f3378173e933244aa570f1cd9197480ff0fb2ac3a083cbfb6d55b853750f64e82adfa3db7a2c00277b6b59c782571bf26ea655f2a12c602f7bbee9d43726d7b30d00d4ef717873b2a93a8dc66d2a0f86226c99eada8ab6600fdc21b03fdfb2af0ea5d330a023e19803a0c934e273184ef7668cf980f294faf24af6555abe7d9029cb17493968617e7e981eb4e8aa9192c7902f0c478fef9f31a59a38777fe93037c5146186210b12abf6b8a7c13b8ef567b63bcf47221dbf1cf3855dda4be09f2ef18ef43634d002b5e1468194cabd9a4b6e0ebefe5d8923eebcf9c0ee17504382b2ff125e9dacce5a5feee9c9449a01cf9eaf9c561f91b760a61265a0061ffda06fea7f8713e3f2e62fabe212fd0262cab3f57deeedddfa9e1f6fb6e3444f8dd1ccb4c20c2d9d0ff53434371213b7e2e803cc96b20cb15207feaa7c60d97766fd93d49f000525b2f8e97826e2132e9a75f259229f7a37c34438bf12af7084dcbdd65461eec374a56b3ebc7e3203904f2386c01bf365f3f5b6f80ce8ca51450e399f91fbdc68705e28dae31363c8a81e29857a268a1f967e0baf5d3a18f417b0c5a265d2d2b44c7ae327bc6212c119cb85376a51099d3e77252115b807b9bff3c7ea4568400f71a0bb367cbf6aec3758c7bb4987a0d9264042ce08b0970bf92b622b1e264c09c0ee6a0fa30a377871a025c78392bf5f223432c5d1a67d32ebea96e66b7dbceace4802ca6dddc639f48904ffcd243ff07f001145555174a7fa6321fefeea1d20d53313d1fed47b475420cd49215959b7d82d1c3abe1ce20addf1290ab28571d49b4d29ee1f4083be76459486f7ad6b8ee2a77c128e7864c5a471413f50d066f6e7887070ea48c5eb08d099c1db4a3b46f008ae5a4f90d817d961984ef9f21e4fb0fb30d534ca11e57ec16dfca1fa3af94be9f705862442217200e26569296a6c4c9eb0d71776024cdba6ffdc5cfe039e15584a3b8fbada53782a31e926fd9c72aeeaee0ee575ff51f80148a5421f81ef6ef85e884d6ac93757bd348079d05d9744e1739324366ef9fbb53e2d19f5ebf26bbdad2e1687ce33958f46d87ff441467453b0b2a6b76856a85eea90a848e470f29cd26cda35f128eeabdf1108f6e3fd243a698b5ccc20e21e1369421ea1ecf4b9da392f0440c9cfb5bebe9938bd3b7ac4609710bd1b9c2a68722fcc09d95e58085420b84afcefc51048b4f1d509db83789df5b0239eaa427492d501b7fe1d267b9d13c2a1e114441b6909629cbdf9de2cfd8307610c52a7897c6f40593105db0306f8a62141454c0c23e3663c39127ab9f9ef5441c871096d853a31416b226ea5f72b034c86e9004fd14898286e14df57f46ee8b4dd25276aa9f8ac3b97f0f0d771be6352057b289dfc3a35b31d9dc89b7059af3b1b4db1d915206d7bc120dd313f07aa2fad46b0d652f4f4d276350e9bdc6a7b1668c4aeacd50a3f74132eb511665d3cf2dc53efa7f9b584f977df84eb57ede4a676bf79c323b35b5f9312a8d530fa932137d4dad5b6102b87462c656c3a6abb402f0d624d39d47f54a9f2ca43e364a757f270bd8979e9b8bef5aa999073488d113ba466918b8f980f117f48aef8edfadb2fbbb1095f5d1829fd486e200562d1f1aa0d5cfb120ad6952251ffab3f14c7ede4c0703adc85bf48129a8cef4835124688af1f6cce730c38a0418b16f30efa7fff581125dc188b89c2c9c4f7a15062548b90b8510aae7361c320c2aae17e33d22f544bcfd4139f06c182963ec421f5c34f282f9cd32601a0610af2eba62825f2df77d1b2d8ef779c14d494920ebc1e2e43b6d179fddcfd52acf0e7f613cb1b0ec620f6a18ec2afef53b6dc78d1dcd835bb22c50b9d8a1b7cb8b87484e5bcad3692011cb7096003daefd6288529614fee0c72af470cbada7b99979f50c6b38832a132c205e3fc6e671bfd6234c71234cd4a178d7c3ced167683722dc2cb734b5b68d01959ec35fd6c086527321189d887e7660444a1a957ad35bf7a29a83cda16b2150f1adf979c7b2f62a9263bc8912c899e5fcef41f6e02bc16a5f8fe507de1a3b5e1baf2fd9234ed9d49891341f6c3b0b01a01f5f453d994c4629572f89f8da8300be01e7964c322e1d43e86e7838cc3b08fa088786b98e278fd36d68a932e002c454a459884bcf9538e39e2a645a1e2ef8fa1325bb460015c5c830914ade9ad1a4af3222f9e4dd0aa2a0ed10281b246392cd2408ee7b066b221f9ad6b1ff67d1206b0d5db70b2dd39c87d61d962a3a170a591c59bae753c24e87c147faeacfa5dd5c5d9c6ec52f8976dc71c55cfd7e9f5c093fb92db0ddf44304cfd56bf3258e6a329225d19c1d91d0814768063a97eac770fe37d60a80859765962e73cc0551807cc947157fc52867d494b167e80ae6ea84ffe320c9c617ef4583791f1f746d6b0413fe8961ea7aa19746434ffb52eea8a27cc2f49f57e7d044beb83f8fbf7c0c6d8d70db5ec16d9fcf9716ad5317dab2e60f1855450c9f601871abe81b330e40ddb2606a456792d91bb3ee850eb36e1e3cd102bc1caeafdab95be1c466c5f52b64d252067eb7230b61959d29c6c9ca8416458fef1e95eec9357ab936f4acc073f02edeb05b0e2ecac65d1e3ee68ecbfd6b6bb0dc95c3eb53b3469f4292bb594de36eb062079eeef1cfc5c669b24bd5ebf1f71be6f1ab4c5e2ebb299117c460ad3199b2eceadf024e2b8eb4688c696fccd8d8bde0eefe6d0572ea6d0dd856bd655491ffc1a5e1dd5fc21b88af9c911f973d1f6e327caf2338953972838d5debb62c0d84dbea1acbbd57b1961388feab2c8581d0f4e711dd59aa0ab0b2bdaaec4a48fb37d34b44dd9d23c6d5aaab0d10ca3698d397ce18b053122e0e2549ac6fc6048ff0f7c57dd773b965313d2d8415a039cabb401f22da46bf80086a1aaac3dbef99f500bf40c296e4602f4b4ca2bbb9fb8b6bdc934c7a5e763949e19ee8e0e606f1ba66267182f6813920d7b2efc6160e68ff95b71133a2417bcaf7d26ec3a1313aa5566d7512038a7202046160c042732706c834a8027c97f31840e26b096fb329390fe30defebc9d688b7b56d12bf1392370e41abe274bda4e3dd6b8d596fd5cf9ab804ba429d184d6a2df4e20978a59634e6622c23efb1262635046ddeca1146cf0046fee29fb531800a8703ee505cecb0a8b7300492c2f1645125b7dd437dc841e584a84a306ec8b0648f777565731888269f30be29f3f6f40f0306283e8c38fcd0b0df3521374bc8f1ae714f915bb3a264c27ca531ccb27c4f9974e97c7153a75cc76b5c533f248119f0c936f7cdfc5bb716f25465689e33753bb41a724672630a3f34221770bd0e43d91bf2984e8b6592e196e985cda261530306d25808975b095fc761434e064e43a24c07ac3bafcce9800637d79068102ef869ab2a5c6385039746df0cc254f865c281fc2b18e9ce282c502d82c24bd27af47e8e6a8ba06fbd32c8451c72bc9f18e4d25ee5fdce3bcd362fe2e20eef677498cd78a87cb8cf20b43af3a322590dbff5770d41fc3eb33e917443a639c999b9dc8f7c21a72be8c29d642f7ad8654b19aa1b9af90d217c83246106aea75669cebf3bc32e0b56e0d6f9f53f2f2004ef19f53a42e13ae0d2b214e68fefde72fff8d1992766f4591f12c9829dadebc12e53c8a7dfa9fb151c17a7d704dcdc50b7453bfff4b7b1ba75d781b3f8ed14889f2e455ad3fbf0e5a318ac132d19735be1ef4d7232ef0aef0c540a967f02a8160ab01965f88d439d851cdfed1b2185d10c515ede81a5bd539910069cdf012db4170e7684568d25618923b70ee4db38a00562bf3373889674ed91a812d0729745547db4a953848022bbe7fbaabb0ed75a6e38d605081ed0aa60cf21ad8bf50ead25eb3a5ad470c68545253d2ce7894cd4ed8c2204f2d67ebea9d6e3c5f2bae72c4516c4a3259dca9579e685920ef64a3aa342e0ff00aa5523d3286ea31f50f6ed169947890a3e2b00c0d4ea5658513e2f4a972faa9dcdfa9e53a34dff194cd40c53fbd35efd4afcbba7898ffefdf1809009b32f95f8ee71ff6d3c3b1ac141a32a7e2d44b4e135028a127c4508721b7b72430f6df6c3f782651a12e8959fc493dc282a6deb2bc1588fa5a5ee451270323442af3ea7a7a69bb37f7e97fcfcbf61a9ba7b8887193af22f0a5763d4a454c12c21a516dcb11c0ebe24c07860f04194005778b3a57f610f73f2e1e809b79d16e5e9cd0b381f4973c8fe4b091766cdc50d88fd69deb6f8e06d7d2a9e0da43c2076ed295bbf395348946f45f186f5a9c1152d58b49d836e4646a2da920e5f761c64c4e332926203ad608e0a6338d0e41d1cf28b36128e2c12b260044169b2184d51718f4e9759858114ae00e0b74eb4547a68c09e5efcae8f92f100f44ef337a288084b1e1d870d3a50f51a832fca15dfa0c3671067bc6c1c5b833c12df1e016c810823b567becec469f0b4686b13bcd350b87432b056a7e03354c7bf21905a670caa4b6e7cb5bdeb30a3315208642df66d2e35ad33a1ed8b2da5b55855d359138c90df2a04bc2b5a4f5ed770faf381571b5d3cfe2cb3249ec4902cad3c6afdee3c1dc42254e27ae8197542c522572968f10b0de9425262627d3db01314ed712d00269a88401134254ad879bfe46a67a40b7e22efd38e2d45ab2032c2a39ebf1b69f7d137304a6e0f1a8faf9c19f5e4cf6f01f83ca5ba828c5f9303a16346859e07030a60972ba9fbab42512b7b9d985a13777839302b3861c91ecec4482c7bd20c74f3450c45635a20f1ad21c590ea378501333ce504164bf7a1a5a23bdd15655f39efa147e62ab05cbfac89a4733f6971bf4f4614b0bac41903eec4c09811191102810f045c286ebc42af132da406bf343ea07f3dd2e7ac0ee732ceeb53cb4542fd8d30096b1daff2f804ecf35a8b4243650ab3b289c7bf3f44951cbf89e88cccba5c300de287100af90ef48b875cfc7787ae73bd312a147b23354d2171d0b5617fad8091ab2e03fe18db81fcf115ad55f181bb15afec19e9375eee39638129c7e8d99e68ea0d29be920a5167fb0ba1e55f89468088565bb9e472179a680440d653dab8a802504daf53e1640fc072c0eab7a80ca7ab9c803b29c087cc253f0d4db110cda794e4d23cab100e52a90ad48907fc09fcc1efcee8b36b1148ee13044da4bb8606efab0ff141d3e9e98cf8a5217d15014a0cb648098ba18b7415ac8f478212815903c0ee221370cb0a610dba88bf8a27b230efc657494b4120781a08d4fe79166e316ddc3777a0f0563430eb78b8f706797302d26c0fc5c44887542812043bbf0cf3ebd6454d62d79b3f96cd0c4c93ce550c1eebc980075a2df669129677f58a2237f17db574934372c76106667dbd430e91a85e4eb3a772ecc210a9fc05d0b6672d8cd85382a28ff437a8811f22dba975a7d5862c474fe52f40456923dbadca57412698bd1d1ab93ec3bec4241fbd5c904cb310515893ae00edbfca1b06692bbc68fab07d9c5327ef66ee174fda49450e87e2a744b9d3bd56d1fe969789f80fe98ba29615d251fb29193847a65a95aa8f8f3f4045a5ca37de0c68ba57e81dea95a2c882a04be73f904fc50104090c0481248a110721a06a06387ddc37a157fb3b750347db60ffc775985dd9474a0f3cc8eb2e4f2291faca7ccc32784561a7ac5fad3b6b37cd38fa27655fb9e6cd557d14e46b658dc86bd97f08676d445e5026f836d36eda9c1174e3f4a32da511b75125bb8ae5c91e257f83f89aa8001d180ac0069c4edaa4dcdff8c165c314f2f68b3069e3fc21301ce74d3edfb01f0eb75fe27f6e0e3bb3f40adfd7c75e67c5e3a1c327e1a321933991801d142d3fe8a0d9d7691337210c421fdf3e85f893406a83d0c623ffe81cc7928ee543e7544f16bd0fef95b4668994817614458d059e94ae23f8d3376ec7ad0b8e8a8ed7bbb46911a941d7d43fd2ad06149798576b431a95e4dccd2c717f407343a57e93881cca40455a37a4cae093e7a800de1acc1f1b37020ead0def95f17132d15d60e6157541aa4c1a5d508943a1a673d4feb7360a2062e56744e875f258e1dda0450819f0ed25c145b60924cba90f7a5e3a4b28cee58e8bcac936618aae5ebee4527879fe95b2b0b22eff59dbae97aae1223878037fb5d825aab5e7705550a9f647a3e41416f986aa7197ccc2f837ed7e60953d3f602019ddabc1d196facc9c580571cdd2c0205e9cbe6c1e7c4299554aa20b3797a3614b4aa12d19657b7847de99bbeedf5405fcacdf1aed9290978450cc04f8ff30cdfd111c217495f7082aabc41ef4c2a1f170da820e8a2f2ee013413fe98c81f912b35ab5f84a1e3e901efc746d42e6a8b204dd09027f7bcbec1800f4117984e98bd4707b519606e01c7681538387ffb2666c4eae4658845f021712bb79969274a0f97d1c982353807231e1bdd4c9c7e8ab7387a5412cf1c5c8423cdb28e890af78802cd178522c798d2bde8698ffb6ef426c79174013ad269487a06fee2dc0ba92fa83121a97248b1b216a80133cd5679f779fd1b68f0315c63f8222cfdecac176c24f829d2f287ce93f9db4a464bf66f73fe021e82594eb3e401d28fb1b16334382434f7187b7b74c280219ddd1fee2b889b4381716440755da169088b3a44ec62198504b0608dacabe31fbe8192eb1bda4f20cdf164c965c34269eabbce991c099ce1096a32cf90f7db050c726f68688d2a7f7f14615ad842079dd48601843545e2281c986b1a79e056865c7e15b2635f0fbf20c882aa5972c25c08d1b4fb9aef39767771d34a408842592e02f2b368d3b7888c7d11988ee8c51dafc04f9443a81167e7a01d686f19d841fe1ae52809b8391c426f960ce947a897214389d93e93a3777510355a7f8559c96a7b1a7cc26f9e76d67ada3c1db8f90f9dcb15727e4341812b74a32e5fd6b78a9fb294a9ab29d2af034c96d303fd34255771945bb0fc7eeb238dd4ede69d2d0f50ae73a6885d0b41318394350ce9ba33afd660ff76df0fe06bd3fe2a6f7bb65f9f67b8ed259cfcd03c337e03e0aee1d085139a33c436a402caa23a685db81f743ee932178f2c45fe2877238468f639d1885e9fd3c93dd66c2d7214cd72c3dc7f66f56a29a471ae4ec35b1940a5f827af286adeae98d0ceb0429722faf94534418eb2e44d6356fdb592d29b1a6a86ed1de06713701d129ade08c238996ff75f9c569014283927a479fda624f2379aea1bc4bcd9683dbf9e42e2560cfb8fd22102ba030beeb8bc1948fd54a2b74752c9bc770502b6ed4db8f6e0fbc6543eed5bc6f3de4c035773ed860db1053106be551f2852dc514a3d1670f5a0ddf2e1eee7d3590ce6b62ebebf5888293253a95d0636f2eb28c354224a00a85599187d4ee06bb314ae573b0c3a12639abbe04c2404e8e566919e213bd18a26e15c9bc966960bfc8da7a32e374f2e1dd35f78650d570e678c9bb65d818f34e4f1dbdf2b816dd08a51535324e611a8554d2250258e4b750c75e11d530e6443fec00526984a9759c56b2a9f3089fb3ce8f2612bb5d388731b404930b0fc64a9db1f8f65c07057634c71f4eb70bdb1250c4fde6fee0ebf083e2590041128bcf98caa480311f73fe9ec3e20a7220ae1d2d5d2aa921cdb5d33806eeb10f18b837a71247310c2ea7f9bee8fb1bcc3dcb90e5758292482a45f87f6c572386c6e3701fb824b840260178dcf12376040c086687a8c9af0a39f8f4b1ccdf3a74d279436020ee031e483e0d9674e517f53d382776044fa98140a04435c65660a92d7d3b58c2553f90e84b9ea609a6093f60e0a9a891f014ab704cc581c85099a248e6677ca638af0d60e5f2d9da5cfe6dfc918c055e64760d78d85ff102ae4f54bd2fa3b67dcfa499931c7946455758d9a29006a88b139e915dc51bc7e16060a37b7b0347f4a89526933cc28d944cf826deba6c0ab24cf87a74b501b7cf6dd4e780036b2b6c548c0f5098e52e9a036a7928b2631b240caa58dd34b89d8688ef091a6b0585b388cd1326464f09809434c216d1f628e2f21c2e0e10f78b8403bf890acfde8f0a1b38755ac5e926e708dfef160ba87983f010a654ed667d11ef6463e1ec921a2ae204df4a792da6752073b6716eb215d40f5a1b19e68353fc00e4db74c3ad9ac8a38f158690305a196b75ecc2339f27b074ca64a7328d68010bbc75d9c385a474ff36b8dd9ac325fe6d387515fb2dbcd76d576165e1db47a3ef75cc4cd3398f20da57a0132edf9be9a22a74451d4649959dec7be671d219099b0566501f997aec122eb3d22c34581c141cc818b53a696db650394a2785afdc725ec73d379111ebab0c1a292265d3599e5d4c87fd2d571a0cb563b5760882f20b048f6e684059a379d13b31e6b65890113748327897d3c1fa07348e0c3841f99b44d64462f496bd6eb4bd9159b3ae7a318df2a06d96f80dc9302cebc0678d141020e3c5a0caccf61a9bb2af58a4def44f01b89f0d619d0a43ee455f4bb9636ba2e1f6fa2b6dcb4953fa5222fc0ea08dd36ea9a5ccff18dcd48e7ee2a0a8577afd9d012ad1cba1a5c0c26b1b546d411af7b22befd517523b06e4a173eaa0a4675d1675a884f9ab87d4402f7abf22218ee42e5ce040b5599ab7bf52ee1d74cdf439280026e80e3ec1240eef68d527ce6ccf6c97ceb8e796ef745e092996702bd74a27830c3aa0d2ca1e9df0d97e0be68f1f9b42397a56bb75551c1eda75c6c03e40c55b4aa3f30115bb1ced70b5403110dfa02ef1d1c579b1c11db2aeb2bc94af949a824b6215ad260bf48eb0c9fae460c8e85ca0b4bbe927e8e84b879c6d91943201834d405786e6818d00030dd30267b014ba3a4d592d3c0ea842c32a6484612aea1ad2ab6bf8ed226583a65164edea9f7950c596637bd5850ccee4becbad08d627740787d48233b06c6df21772225900192f21d7f525deb4a3c43b13a19a41e70682632e4d6db6396febc25c8dad39ae2c165889b7d60beea3c038f7fa974390d970a36771132f1bece686dcb47537f047d6c39d4d513ed0815103f4449df05f0d26798ba336077fb1179dc91e0d93c02f4762c319f8838be321fe8472ae6a1f5c9ef619dba1825f72cc6008c808d60b37c7b13e80ae5e74dfd190fe0a68db24dcc7a3e1fdb553c9e5b0af587cc67f608b34a508617f753ff339af40dfc819834099584fb0029cb35b6228dd52b2313f2d83acbb12335b583cfb16dc85a7ca0d07e4aa47f4c8217f5a3bd41e5c41d163f2439579406c9c7c1b3fcd41b4d9198c515c09392210b782ea26d01f4dafdf8c016162057e6154a6467f2eb3fe90075bbe98399fb7d0fb56b593dbc947dda443a30f02171522cf7d921e50207a95a146452b0f4c5c13911e9e8bc496bd60f47589d7af8cf2ee196ebf128eaf16b05a9525d2d8edbeb33ac0d352d18b94835a9a62d727153f8ae1ca733fb8e40f6016aeea7488212f39054a3b284d5ea0b3cb900af1718068da06d4da01612f53c5125d6f7b386a1c5b48c778f7656b3556645ee3e959775ed656bbffaeeafb39d59baec1501118a0670ce6d819948d02fd3275a1093e613754c6ce71ffa03e98e8e49c0d0c667678b52bca684425f0e1ba66244a1bf730fc3fdab78a6d4e179992609a7458c5a19961d3a264ba35d01cc04e7a52c29b59527eb458d3a446b69e07505657c1f13facf7c34a933e50e18279cf7d7a7837492dab88e4bcb7d28389b6582cae7b5e0e041cb929c0ec7f0685706af98dc4a0cbf33a7983555f06712ccc37e4be7dcfb4a9ad240b522eafc38852c303456a02b40a2f91856976ee5845f81b38d7e2cbc1cfa512016fea9daaffdacf6896fcda6cbebe5e173494df7c77576118ea0b0ab65460c1f2378195fb56911e65c37cd8bf59b857bd488262fb4b5a893c993cf4de64ee96e9eb6a0be70fb4eccb545a0b244bf1a699f48b54b9c82b004aa48612bf2eac713f6400ccda45a6ba43bcc7a6e9556962f15671862548d26d5cb385f82eb7433be076c06fc80025dc040f12853494941735613d314d165228f61a34014717cff7aec125a3f255f2bb08301da5274102e4a7a569384e8031f0324dc3927e329e0440c48a1056033c0f4cbc77fd481c0eb57ef215aa85a376cf34e9121c84d80f15a449fe26b8f13d2acaa5cd11e5d42e3495188f72c39de16009f476f0042527f9996eaff2136a3146b1c9472be40d4c3790efdb8eeded3a810377c70507ccd3451db0ff6c5675728c8751e1e621d76372e5c259c7310eef41cf6423d89a93ef5327bcbf8660e7d762a55e7dbc2467cf2629098fc5f723bb3004eb8e034bdef45448ed694fe58b9ff933047b20059982615741a5eaa4a991f8ec0128bfcde32d6a7c37d28365991f815fb4c45c918ca1518cbea30dac226bd272baa75f36a1d2238163ba5c8e6b18ae82e42b8d520bcda77f8e27a0bf06e683973beac56cf7317c31ac305095491d2252084eb89549fb015cf4ea33b15ff247ccb8cf0a8fc54640290f94fba867e66e2eb3367f070b99cc432ade3fe18e00351a2b2527377acdc9424d897812d3387d3180f1276bb9856cbb1e2c14df875a862cc3e7eb5f84ac1aa40559f7a21292f90d5f3df01b737a8789b97963b4b2475faf124d8edbac2f465863ae1f59f7d57da65a9b2d8ad397343a1f05c3e7d78e193e920e8688d57341f5b16d97b71d200a95c87e5e2017799e64e492d53321c3381ab01738c4f3fd067cbfc406206d2e07c59ea18e1e22f0ca1fb8d7cf8391eebde8551150fd3ca58d6f768a2ca08047ff3c87b57b03aac1dfeeabcc36559928fa58bb8b2e9fae422f53a028640bca6af4ea8e27adbb23fd52813d86da478652e7e704cc141e66c37522ec64707a4fe82cf1d94ae712c42d6aa6291907fa02cd26a287359d0352fc7d6193bb3c04ff483ff8ad89755f205938d52ca43f55bb3e6f8959b91789149afceaa116a774b4ccb013c6d7e31f951ed705d6ea87324f2ca991ba1bc9cd889e6736dd725f7ba60b191ca0d15c2c95bb1b97a0bfb614b7b7d4de369aae742f65b34436490a7cb24330c585d0d7096e3e192a7a242ea1182bf99aba952bb7c1b99715961d550586310fdb1929d709a167aedb3fe69fc2c285298bff0e642c2b5546fd6186aa34df5e4b48154050b1e4a7ddbfff762a7421f6739dde08a4cba8574606077eb7e00e311a2094fbb9f2abf63b018864092a6594b595075ff4eee357eea837e38a8d72a3fabac7182a2eef2a48a86a0580db935605c44b57232aa2aa80892ad7a4097467b33c2ed31cd404dd03867f63ef6491990322196c0b2d0badc990f236de84071ed8e75c60ec78bb016a572f1645853a82a6e403dbab4ab0e3a6acd6c2e1186de1dc4aba0086c7a9291e0c9cc52d37216a3feabd26e6192600aa74b2ef783c25cbfa619bf741b9e768f0a546afe7c9554b73a574308fc69d6e58305746fea774d7e85a86be24e48d428353f8d7b725d6ad576ce3c800cf11ab6c5da4ee2852f5510bdfd3bdc7024a6ec7b239c9487a76f4b677fc6b143ef2c24a39a7aff87375a050449b2955bd3f28f1ead218580c63d559510986012f2402a23ac99cfbb82dfcea84ad0a8bcaf76e56718e345a4bb8317d258bf88d53c3dc682440e40dd57d6c05ccc3ee3763ca581c7e3279f5946bb9c59a0acd94f7f5ee8887aa218915704f5ba4c6c30cf2c4ba76adeb4cdb81adfe1d2948d988f8c99aafaaf0d2b3609d349af759239636be83753e3b560a518b54f70d29133de5eaeedf6c55e0c041176de8b803e633401fb9210bc530cb1d0c48f68ba874141bbb6c3ec5eda20effbbae614615ca2df7b7fd089394fd21990feb414b9fca47891b1e6bac36f94412d08e226ac8102b907b2dc9f06590fa16650272dd19bf8462763c55445e1a3f4b568f911609f5b204da77da36c3fb0f6b74eedeecf6c9e1560d01b1109e72b0b15421ce266cb4b494a88b7923c2882fc6e86bae55402fae2281326c2edab9ec192f6db9020211bd4e497b9b940331a8c84afb70bdf0396a496e6bad75e22bc07f7ad3f51944986c04f1941569dfd03f001e34b09c8224ff4d4b46e9c1cc7d93e9181394ca519d67b925ddfe8845d54fa298c0bf45a84d6a6d0cf3b74650420360a939693ba1d9afab123a812156058e4fd0d215752d6b73b9766ce650422e2feacce56c488eb88e637f93a9d60d0b8bf10d08474d0d6c287e09acffb9f2e1c27fcdeab72d979c1f84440251818daa09e9f43ae16077128dfba5e6aa14cd55e4b25fb41ef40208e523ea43d6f024a7adb0986ea9f17cb35f58c291bc9752d41b5d1f2a2dd16e4f77c49a3aa2bc8a8be126856b1dd4c27e2c3248dd8706a220e0b060a4099d2d9a4c3c8b1422f10ca7a49cb289602d30a0ab55c4552925bc9e3c920fe4b55109b25a4f7850b964a97c662b4001ac00bec815e08211c9b9887bf1779a653f950fb36b857b610d0030e809eab35c91889c7ede961d6cc76564a02ed8021700eb47940d4097361ef412c5a2e562e563e351993ed9fcf941e38fcd3aa7184318fe293c3cca26a83b0bb1c286dd1fdcce124bd587897081ba762afd91312265fc3b174f45974c58940c58fa68a46656e27ead1d90e708b20fce15d027f173fab9ffdff117bd473041cd90958aec82326e997efaf1b96112986d7f9e61c70c2256efe3b22cab419caf841a69357589d69c601599348354bf4675cf0a85ff7f43d9997312e29e45a0659bb37fb299e479fcdec3fd1a8520043dd94e8223197a03960baeeb6635472417c914315e039802e185a1de02441c05be8fc4c10e56105086b63918d6c7c60eeb6363877547db62a3bc4d3cddc06f798cd1277b3ab8db727723befffe3a7af11d025ce47ac3629c399bc8b2ae54efaa67792934a7cf97975a45e156f05242aebad99aedaeedeede52a62403400ee50dfa0eb623487c70c004ce661fc9e94467b3a7a521f961a2019ca5d5240ca10752b6f1b36f2dede7055080b2b5b8e2a88a0d095865ab2e2385c97d2489eb081503741248f0029ccd7e8d016cf6ed8924885a0c94584114487076cb119870b6fb5dab76083252b2776d2f12c6aa935e778219e248206a5d42aeb508b5cacafa87e19582c2518b04f39b08ef94328269d55753535252566dbb565c726aa16ebfa00695e65230a8b39e41eda8ae7597ec525dbc2ca1a4b25b79be5db255149cf0b2cb35084cc85e6d8b47ed692694547b9bbe842f5b09909e4992f742d0bdd049d3c3210e4cd4a79829d00bf9ec73154320d065e071f48aff1f4ecd30147a1af1fcfe3b871cbd0ff403d35f04a293bff799f7e99d5dcf3f8ed6c47751bcf9c7d1cbf3f3133e8851472f1a7edf87f2841ee879df1982de091ea8ca2e7ac3d0f3c865ac3c26a8f359ea8139307795634a0294f27dd54959b9ec6fc5ca94ecd67aaef6a45dfbebaf27eae80f4ff7f9e34d76c94cd1fc6351479dc7fefca03f55308ba83d3d3dfe3f9e437de6b4b675ae4e30d3f0aa88a51ad4be547d8aa83dfea3eb6c8d01c237df3c3d29213054186af6ac6a8f7b5d7c96a66e25e39ef2f8a9c7c551ef62b9641c08d62fdf26a75e944735e5681747fd5dd8abca99f56b76995e94a555294b33b2f97b53565bb2a75e14e99e7328eff11c06ccaba55a7320b287578e7aeeda931a74b34f5927661a5ebdc59e97634a3253d41261d6fece39a61b5e894896e6b92ab3342fca0bafbc290be6d0d03a810ad95d88ecee6e65998490c14fd11c545a3865657edfed0fb2db2264b74bc8855396165eb9cc3ffcc2aba9f02abb877382998239d00b78055ad51e0f99f214aa18448b4baa98735af8fe602e2abb37038f0b0fb27b37c8fe22113f1095783a88a27a3508a550833c1984575df3b4b48f7f109c6c41caeed920fba72210fd5e94cffab4ef7db5d6ec79298aa6e1edeab6e59985eb4f5938168e7554bfe65b6bad157bd9b272b9aef83e19253254fe25eddc76083395712b97e17f75e57ee528ea6d873059df99757aae663b8429da38ead65a6badb5d6e644d173ee61e7aa78f1daf47dff0782f65a2539f762656bb55fbfd3adbc50b9955ff917f7d204b732612ed5141eaf9e617be39d5ddb6d470f57cfd6dc5c8dd05d4cff7a866958b1648bc562c9f6eb2effb09eb5b61b08e8e70745ade779e72e5f1bf9c7bd9ee779a1cf52986c6152dcb6d6faf09993d65a4b86b5dafa83dc1e8f23cff7ec289b47e557083aaaa3dcfbc20f64b158e5df07c151243560ee706fd1ddc1511f4bd2b57ce148d280acac86d5ac32f52bfaea2c4132ebef65e1a29b6f47f52eca843d0dc82d6f297e4c817768b5583031d6a7f8919582c52ac73216fbf2c732f6acf2168b855e79de5ac2945e8a1bb252e0fbb1ee591343ab759261c882b1ee0b9b564c4fc127e21d82f82cc81d4911b7c0243fad5564df56ebbfd6dff2d66a3968037a0ac6f1ac5df30e3e13df1f88cfc6f70f5274f378d6c4c0e2e1e6fb290847ffce1d58600271d4adeb50f45f46c68176f0962aad9904deb0d65ab97855e94df2b86467ba48de4daa954b05c6bf5fc3fab1da214c2773f8acf5fe361081ad91f597c53ad3f14df25938bd99f5faf1b4ac4b03ccdf9ff9d7f8459e4d183cbec693848df89ae585fd785ef361a3dff11ba96be48f974566d61973c933ed568a928631c34a18547c0b4c6a2eaff27673a4f871acaf37f1ebadccbf84f97bc9677db3ba2aa76452d7609e84d5d04073a69e3a8c4c43d809037b180ca7373fcda7187f987fcd87f914e692cf3ac9b306e65f3ffe8fa5fd78cf1407eb2f0dacbff7c9f3ded7082b61a8f97bfeb80ffb57cdebc686ac21c99b33bdf9f5296cceb495f2abe695c299f278b19761602e0cec759aa6f9437626e655905f8a37ef3d491679599fe2ef99c2c633edd1922a70cd5eded3b434f1cbf3c7d2c47328f63267579fd15c4f9e30f26160cef4e60b033b7f90cf7a983385b9e4c37c2d6760dd7fc1dcb3e6f5e68f1fbb6fb2605e050d0be661e39b250cacff0173a6ed2a692033eb73388c8a33c5195364f37d18eb7e6c1cbfc78f95b771ac39bf706792c5f2f2c55e663d2bbf7065e148f1ac146fbe706af4434e3e4ec93cdaf246de3753c018c1a468a56cfe906dbd89c177e123d749e57e7535979c83353eeb5f258efb25f9e38f23ebba2e7971dc9165455c43bdd1394705727f481a0463a2188fa878ef9d451103a96788a454ffc54f6d7dfb6957a9a723d9d1cb5bf5af4121aeb536a4b36d2847fd53fb0d55a5ad4079dfb58add4121e5ca825c7725d8e5d14b01542d397c3bc4d9440b91edf3705a23b9cc3f74db4df6c9b6ba267e4a664b25dbf76aada9549f7ba6e109a4a3b27786675d62d6b4abd0200ff972ea48559490905ee0623d00380d3399cf2f0a6698f67b5f772580ca60f86d94eb90fdf07df7bf6757b1ed50aeab52e5892c561cc9955cca9521ccb487aaf8771524abc4547ac1964c360966fd29aaaa1c9f07b4f37ec63bbbd6baadf556f7bc7aad3675aff551d38618a8b2c42e572c4b34f1e48a85045e72356b1daa4e348ca08a656ccdc82fc2a94af5428d5cbff6a7b45cffa7d664689f4ae3616540e98a6c1fd75a6fa933f5a6ce5e6f65603e7d65cbca293ea66b6d05d6ae1a42b54fbffd01fae1b9112307ec6367ea399833750f10b54fd770e84f71a69ea36bb0f36582a20d4409c0a142b1a2bc71efbdf7de7befbdf7de7befbdf7de7befbdf71e3552bd2fdb1257df388bad3d53f0ec5d4389ee32475db35d6b2b00e85ae7daa76bfdd43edd357b3e91dbb3076a4fd720f76fc9fd2edc2ba8687f0b97cd82dc4fc62037142fc84d78a006b50a6a4f77adafb44fbf96ec442e6707b91f24bb84212707470738383739b8b9b199b2b1a9c1414d0dcd0d6868666c30332303858c8c8a1aa8501143839898d813b1188c140c4c8a19a4480193010cf68ac1eb656a314d170c5caed60b5aadd20565494691e4d8827164b180c5129d10c5304b18824d80e0c7c4f77958b2777b97ef5d410db2d6aaa0063954a7e046670b22f9cb56829da37dc5d1de9d0de5684d0cac472244667dfdc202331c712afef82979bff7c00fafbd81697f7cef26dcba9c297e15dfbe1432db6fffb106fc9a3fbfb93a855f43f1acf1d6133f3eb3b4f07f2aee0809d7501d7545cb4f0cdc910ccadac04cfb29b7cebbba4ad515ea04a369696d822aebc712ba20f73fd1a609557bda6c322dd0e50252505566698dbb27685b4c196b0d13f6f66f98150b0670d905738bf957fbb2dfd93f8bb68a54b803af22206486596b613058ad7e85d5b7150613dffe285a11068359d1dab6229daef88eedbf88d317fe9e608a5f21401e29791b15fb495ce54d14ada5023b7fd887518100e9e4ddeb97b42571ddf7b395305f2fe2f465f6ecbaa335d63bcb74346c3fc78a4d47c5eff2268a40c86c675e2634557c8c9731583c3a76bd88fd2d4e710ce2fb59c6e0ef3a7fd411a79d79f09c1ec9459e9dbeaf58141dd7c440925fb3ad89a1fce174bffc9a819019763a7d678d7fb5912eeb1a7f5b06aa7466da566d556764ce34cca6795aa98839ad62478861f2e8be92b7795f82a75c77b9ee9620421e9db4f2726515fa94dcde14923ec5ca9b7c967ace93b06a2fad14ef250ce9689ff60866eaba9691de6c45d1753e4bc5777dcb78fd10bfbe23591a10b5a5d0b326d499aba2fdfdb6df6f642e04ae020d14526426d4a016d93efd2b868882e8ba3e537fd7a7feb0af46d987dcd4f535387272fd9c5cdf75f5ac6697aeb33de7b3b43f57996971bb07ccc6595ad8542db655387a476a6eb09ef53506b83f787fbfdaea7b6f64f3701d5f2cccba72a6378bb58441c435372a16a72c597c2c4ebbdc968553a3cc12d2b96271ba922fae3140f883ad9644e0dfb3c6867ba446fc6af3ce1ada76bfdabe37b219d9c273c8ed10cc3434b19d32d3de49595a5865fd4d5565fd69ef9aaa2f4095d3f085c433b216c74afdcffe17c7f03e81eaf9837f8080f04ffdf407c867f5043a7f6238ea9dff07a80254dc16d1bacfe5a63099303a52bedbfdd65a6badb5d65a8ff4f0a77319fef6c84ccd244198ff7d498030edf7e7d4ace9afd97ee71ad767377d2f18477777777777f7cfbbd6bab5d6dddddd1d7477ef7eadb55aefb6351cab8ca3b7d7570c3fadf56d3dc1cfbbd67b14433bb692f9171b798b2d18ab4efa59d599f54145a7bc5c3da9ec6f7de03d51a1ec0080b0533ebb67cd06cc296ba96a90e5627f506b0d557dfcbf494667de5cf61f1b84f1533395f128f4a632d9a372d4dffb9a6bb75cc9fe1789c9699d25e76fb9f8ac6221c114bfdfbbab94fdb39e47f9acc6cc308e7a8d41762d8e84f93d599a9dfa7496e651764ac6f3a87c9686cefab186b67955accc9f704d0d6d13bfc600e2b39e758aa7d764651e1797f9933036323a3c39f5a8bcaf38edd359993fcaf33a3236264c986d11ccf48bb25566fae9b27ffa4565ffefe79e5fe7fa1e95a535d40f8afecbc898a6134c7fc1e92ea83dfea3b51ef81787f7867f311032b75785cec5bb7c5e3cca31604ef994a350b527c465ebc44c7fb2bbd75b1acaa12c959dba8159f37df57c763f1b475d06a97f5ac9a360507b5c507dfc1b7b5413b33fb553afe3a84dfaac5e5270aa2968c9154b125c320bb93221821364223508f62e92304cf2c5f2c67ab184c1338bacdf4bd49a8c0c54086acdc64604df3eee81984fdb04b9760a72fd76a20699ad447f20571d0d1c516b3aa8940ad41aca0325d7ef226a8da77dfcbbc94f2bf505728d926b3b69a41e22d76f216a2d46fbf87712206a0da86b135a0ab5fb41183fad564cc8b542a14621d74f6b1572fdea968c7baa0766cb744dc8a66b43dfb51774ba0680b7c9791cb46b32b44fd772b48fdbd03e31806e501b9e9a991b36db17ea1a8fa1aef9689faeedd03e40dac71f0bfd2ee74c1de794a16b417274ada87d88b4cf54fbf8df68b191aa8182e69cda32e4c6e2d135bfe2a36bbedba16b9e0548d73caaaf507996aa28ff205d732d455d73a9f6f1af39145df3a9f699ea9a9ba0faf86bb1723741d6c11813b8dc39a61ddd8e149fd1e09eacccea2a776aab526b3592586769a22ef7c398d50c5be38ff6c7a7d6393ed92fcff129f799da44d51efb2cdcb5c7be88435cfb234b18c26c86d97fa8ac88a48dc0f00789b1eaa42c72f5bd233ac64aea8252a014b8059c82e1267b3338c2f3ce18ab18ab8cb303a56a8f8353de8db102b7e47e0f9c02a51cc5d9ddf7ee89b3c3d981542c7249c6b1dd8158873165dababb5f6b43bf37f43c128ab7fa43981163d501a39e6afdf41b2f6e5b8853e42f392547242b6b6a75695965ffdbaacad1a2ca39ea9e57ab55eeaf1888b5ca5fe8c1f2775585cb9e1552f63ccf7bd2627ade077edff7ca61f88121088252366a973387dc11c95199e75d8fb457a699478f0cb12b731975d9471dabab6dc13047a5f226e6b287b57a5ef5c27a8e4a8efac8947d4472d4736d6511b3acacec7b82699f720a466507df62990b3ed1c027f029bb8f4839b553585f5c863febca5177e5aeb2bf4b8acfc2cff98cc9753eebf06944cafe212e338864af72385d0e53b02987e76dda92533b25bb7775aea6fe5234611c5d892b49cd5c6bedae5dabe7f5bd2018dafccbc0c8c0d8985d6bedaa83a26ff3363a32b5d65abbcdd39574aded4a5c49bf109cdce7cdd55875521955b2d7b6019f683bda2b26ceae0685ae29ae27476ff2f53e65b1846d72be670f55ed418119669c5ded71d793cf5250ca7a3d545372ff536e57932b0339f72b032c8b25f08994c552f6b4076797bd21dd4375effdd4d574032783382730db9ec04c5d4d6d6393bdd3d5547bfcc6d5943d5f57151955ac8c55878afd5a5343f9ac272a4fd61355fb35e147adca4636239b8bf74cfb087c163e62fde7c51452af72b5955fe25a5d84a7a3df596d602a93bd8c0146fede8666fdf5d9f72c6cde90882cb4e0077ed6ba6d6b6d2d5be3c983b58da07fe18b57c4b5fa781fe28cc3e67b5e2adf175d6ba9f050ad3d85d8fc799f57bdefbbc0585b307f2c8d75374d776bed597f4e30d61bfd2007bc64f0cbbf6389c791f5ac1f9f7502c0d121fc82a3b70593cbafaeaf8ca11f7cafc431fef8d765dffc71f4c6d11b0243cdb5af8796bca607f3c98036e18b3a2c74e4217fca18ad0c43cd408ede2f7265186a16ba5230d32ac5c3f5f7738c3a6a50cd8d311d42400d62fdbd48b2f823eb6f79135f9e287e8d2c72804b6e3d090507b2e4f249337b67da1f900cc2f0250dad73c88e3f46ef4cc7b3ff7b16f6ca770c04a6a63c6f944ffebdf7de3b8af8873ff92edc250ef24b277fc8c3ef49d6dfa83d60bb7ec4a9f7adeff286f7ac16c6acf24beffcca5a9e178b583860257b4f852db9e61abef7e1fff0eeec5a6fb7a33dc1d0b3a31d4f1b86ad4fc9af4a660d8ed7d7afa75126dffef8c2a6896fb57e2d6fd8ecc243baf68c5f347ec5e90ff0bd2f5dbe6cc9153c6b70b857debcd1bdbe2cf9ba515f2f5c83a3feeb5f675ae37dcd691be5b17ee98dcef5eb8dceafd739be896f636b2ccb1f9d7461f259cec29e2babf4ec8aa1a3f0ec9f90ce9f90f6568a24830fa6a11024d9fb11a7ac07bfc43fc81be4f835b2f7e378fe08fa7b21f63faf0d1dbd61bf55ccd25873787f53249072e7ce588eb83aa22a7f34787fcfd34397f9ebd825f89beefed77a97f67d7d19cfd90f1f0982c3e17036afbc796fed7f58c9dba8f4bb131b6869c193f1bec04c7f7e4cf0de5de760ac3aee2ca89ee7e0e749c06180eb42762d6c1ffb5f48f39dfede8fee7fcf5bc45875b298f6ac325d23a9989ee95d26a018258e9d6c86f776770ca0f000978ce6ca840798727d7da3ab5f03a6fdfac3fb2224ccebb3b4060971dd0eeb0cdf835edf8ec27d7b4bb206d59cdab77fbdefb4e50cf62f0f6de47543b51266bde0f7fd1f5dbdd04b32ebd75df6bc33fdbea845af6b4fdb7ccf113c41e5d167c8150b0932837f9f46fe3ca4d10fefc9ecffc3c553bd4f9e2ae38d91af9873f456efd7d78ced3bd3ae62cf6a7b9775392698e4930cde6738434eeeb375fe6dbffb610d7e83dff6c57426bfaa8c2473a57595f97712665bb517a7e5a890ee4dc2d1dbe46456dbb5cbb264959fba2e27c5674e811af4fdfdfedafbde2a67e8cf3f6f91adabddb7764d29c9912c827de74ed8b7877797bf33c443c2a2ce20e8ead3afec5c1f86a6e9fa54e6b2de7596c63adbcacaee87a169cac8b455edb9bf04d3fcb4ada4d420fbb7d614f5d54de5a1fd21ef299fddefcb1fb2430e56189c02bfce21dee7c2433aa7aeebe2b3dbe47f3b177ad93a9fe96030d8d9ba10d7dc2f93657ada3926476feb1cbd2126bf96ee6ad7e5fb7edfa5987fbf99746368ad7562fd0ffb45edf8c787a31f14e2cae1b3ca19fa41219dad6d11e40b759d5a4f6b9dd5654f32df9ba90cd9495c187c49bfb2be50e42b9e66edb16dfbcb80197ed6eb4bfacc3a954753c5c411520eca9589245a720d1ad25f6b8f91fbf5bc7b3d9d7ced5b9edae33b21ae967cc938b932b1644bd6e57e0f29e5b919fcb16b8f4ef6f0839f38b3c94532b9f545379750d97edab99f40f79e303b7f0bd7dad3faf24710ebe4108fffe11ae29074146ca5fdf7c9af89813584879b6bedb95f4f7b7a390378ced09ff7e10cde8367dfff7ef47bff7dfae1cf3b3d2690bc274cfbcde368fda2ce4db6064c21aedcb5a73ba4c1e82bc81230c265ff7bbfefbbdff7a537fce1d5dcf0dedf28a743d96b4fadc151237b8dece7f5ea5eabcfbeefda75ec28f2c5291220f2f57ca3c8e5ed5aef9a1b7ed6e0a891ebd7c85164f7bc5cc9f6ef8fae4ba373d67286fbf60c53a35cd3eac5bcfde51ea101a6cc42ae5890a8ca9ffff9c3cb9b7defeda7457efeb8ef3378270c45edb3dbb66fd8b7a1a3357c7ba6feee7e1e61034f7945ae583670944757ff58a56c54becb6371884978c9250ddf8b6775d4f14ffd528d8e46fb0133d5c9fe0133bd1948fdf2e368f7155aebd3ba0417cda7f507b93e510d6a1a7ce4886da8d386f005e3e8fdbe37bef70f1c657f99f78097fcf913c5ef3d3725dff7803956db01d36ec0748f32bfdfb538cdf5dd54a8473914b9dfb17d32fd6b7632492f9d5c3fe49603a6f9a9d54149f159cd14a841f77f8867daa96d6a9533f8dfdb227de7b3761709c3ec2cae2b666d4851f80e55d28024cbbcffe827898b3a93e387c91feff7664903cedf69af385aedcb6ff5566ae4d4b23a4b6b9d61689aba1b367bd406ccb4f30f982feacc4ac24c6b55fd527c9a93af8ce956d66ac01c913053abcbfdb6c9673fc467d52015df6f295083b259e2109f058a1f7b15b833ac9c417cd6d7f2c6436798b3a16278264bd4e95135c83f2b7fd8ebab2d533c0f611662ab80cbb60659f361d8ca2acd49c5189cde5d367f6c25ccd47f74dff9cc65644e5c73bfcab01cdb8dceef1d2a75281fb2ed954798a943595dd71c88dc6fa538945ff1dd78a636f380c3461145fe9c5cff83fca2bef887fff7fee387e50cfe1fc94367f2c71147b5bebff23254617badfb43be031016ee9d8da3a5d559b917bfea5a73699f171eb338daac1c32385a557cda22fd3c8eb171647df9fd360d75badcbf83a5ed542d2d725aab9c781ad9df6cff58fbc344595aedd03c53a32ad9ff154512d5a03ab34d66da5057cc6e2ffd25f7d72cad4f4d13d49a0f14a4d54bed92562edd5369457d8bd364705937972ee2a739d95f45a50d595905eb8c74555aeb63efac4f3dfb6983a3f563b8561fd69936eb434bc3a1cafc0190e24c4dd86b4459b8d61eff9b5ea02a54d12a5465fe16bf6056595869323ebad6d5c7ff47067dfb2597279719fc3f9b0173fc1f427e08d59d5194dfa83a290ff81587601a05f811e59d1c99247f87ef47f2472ce4fbc4f7efc92f6dc87ea640aa54fe51b391bf1016ebfb4ef24cef0fbb4f7c2f59effdd86978a65dc1cff57aa78edde5bac28dcacbc2152db4370592c51fbfaf8e4fdeece58f977cf28867f13b6b0d3fd957cb0018e5afc471bfcfa1103a2a031e260dc6d145422106416281620624962186f014b9eea0e0e2d6dddd1d0032bc100a01e0060016b887d6cdf75df85f6efc5a6128610e0179a92272a4bf680720404247beeffb3e0ff4f011978138daf869f8b1fe03df718eb6c0043fad57471c6d1e3c7c8447e349e57bb1c4f1b1f0f704f3c3f1f9103fd657e2f84ed2070f1fdfc9c347f6fffe45d1fd4e1e6ef601633ada95ca06990c009fa532a8a511e9ef9f5c8180720ebaa05ae5a8cafe36d42de3dfdcfaaa430639828bca22b2897238da720a67399c5c3f5a6b6320bd2dfcd1c3dfdb1f229fb5ad9888c85133dfeebe16fc91e8ceae6d2227b2e19637f159f8263e91cffa5b6ca26b0391e7202272d4df06475d14c5d3c6d12a95233cc3f7208e7acd7896678d28be9f452edb2561be5566e10ba3334d234c900ccd1c9616c4b4d50ae77ada99322ec3df3f60a6285a81a26cff0892f7a4d2df65ade78ffe8aa3811c2d71f40b7de5cdda6beb2964bd556b8f053a532021182f77f4fdb9bd1f149d92fd796274bf3d7f78258ebe8f7e7fd73ec9d444511e98db81f06d124cffd0bd2f4cc5bf677534fc9cf02714ad98be288ae00f58cfeaa8d4a3c290064cd99f8cd2b205c6e6b4484171c5770ee5a8dba3d0caaa6aadf5b157397a7adda1cac6ff7e1cb145b238478f1c758be391eddb7c3bd73ed3cff6742b15985539f52b2fd95dc9bd7494a3956671321930c55cb130b140e851b60833eda8a86b6371563644484791cb2f0a73ebd39b4332b75923bf929fdadc7d8e2676fd88bdfc3dc1747dae1f9b2c5dae9afbde3cba0d3f3b62cfaef1c91f451739822e5c5f6f3e89871499997cd78fac6ae612570f475cb3896b45abc94398cbb356d2faa442540b93fb4cede32f37ebed159fb14e08e86428f1b613f80dded7526bf5428ff29e8ad222d504d3e22cceed91cfdae690b49167634bc6b18ea3f5eabddec5a9ad46b7ad134892a471b4b3755baffd2e295b752bd505e723049dcd9e546a7074b6b02134059e7324c779268c86d5d5955515d52e4a6a6a4a2a6ad7f454050aaaca5353e31aa973ad6b5de71aa971343c134655e5995f84afacaaa862343c332646c3f3ce20b8c70dad0bebf1b1f7743c4a84e6fea9f1931b4808904c910c8d333583e06a651df33dba86228b3461649ef452e7623411448a18f1e1fd0120430ea21e382082f408da4a93a0972801d4bb09defb04ef286825fa4aa74005bd6b2ccd4437f19ea59d786741b7a0a356d02e787f010c3a062d839e414bf5134d83f71a341436682d37681cf454eba077d05b9a07dd83f64153f50fde81d03978ef203497f7170211aa8a6084237469242441093d04ab253413bc38e109ef0f85ab2848610a5f9a40852a44c1023cf463db00b707bea1c00d821f3b073813d1c4c9d2932298a044516292d4363f8299daa820b83b70d4f446b82bf048705be06fc0b7ff010bfd11dc48ef036e23de08ee23be086e249e08ee255f84ddd683892857b93211a52aa7f608b932c1f4258722ab6bade43d09d0d3fec6ecfd757c26e3fd514bbb7d0c18ef0f3454837cde3fd7a016dedfd6b59ef77fc1673cef0f009fb1f02b64d97b10efcf43881ee21510d4c3fb17a94132ef6fa46b30ef7fc467e27b03f1deb8f76fa37724efdfba1fde8fbc1351837c78ff16410d32f2fead44d78a1079fff6ef2b435ffb59d0fbb7d48cf717fe5d55835cbc7f17a106fdbc7f1ba16be87b0b16bff3fe7de5389fe9bcb707dedf57bc9fefef3a4bf3d15483f0fbfb1135e8dfdf91e85acefb7b123ec3f99bf7f7293eb3f91d5ff3fe7ec56734afe3eb8ca511d520cf410d6a275df32d4da5464311f2fe4a489656d44aba0f33bd20346237c420097d1b815401a7172c20f4d788a6a3f7ca01b4cd855e62496c9fa83d34681f206c69d8dec076866d95ed6263600b035b1bbe3ef822d51eff16f05d527bfc7bf04d6a1f7f1e7c59c077057c33be327c2780af027ac05706460402df281cbe5a0e80af11be48f02500be3fe07b84081f8c14c1570aede34f04df2f2fece16ad80bca616fc68b225c60af0aea41b9a703f674b09703f6569c3ef063af083837363b6a68f0a7037f4833444d9c2c3d61aa01258a1025fc4531c19f1647a592f09783dae31f047f3d30d8c29f0eb23f6c4bf637a9b2bfc725fbd3b0fabc64ff1bbe2b107794fd08b8a97c4751052a7c998214a27005852738c10b11f0d75485bf238a803f248c80bf24bae06f0a12f0572509f8835202feae0c017fbb25e04f0b13f02715dada0a3bd9be9bc084255829210948e872042314a18a083ac05ed50eb057842dd833020fb0d7a507d8b3f201f6bc5061efea07f8c301017f4741c05f8e0bfe9884803f1d68eb2108814b1080f0032a1ff480075b76a0831860af8a0cb0278219604f0929ec5979027b5034c0de951a606fa7057b5237c0de140eb0b7650a7b549fad73c0227b0a0737b0011435a0c113523390410c76f85e61c1370a4ce02b8526f0fd92057b3827b077d402ece5a2b0c7b402ece95c80bda617602f091860efc9b3b51692ec2bb843f00d8317b820aa052c70224b134c60d9f508f0dd59c1170b09f06562097cb39400df28287cb598005fa913e03b85027cb728812f550af0ed82afd5b5f50a5480bb03df29c07df47d0577d3370a7057e0fb04b82df06d027cfb86c242df25c08df4bd046e23be4980fb886f2bb891f81e01ee254308f13df004de0877108f047795bf012be07fc03de58fe016c1fb80fb036f0477922f825bc913c1f783e00b656dadc40bafe46d2efcc80a5b4e36fe08b38e9c88003b09f9ae829d6a7c0b819d88be91b0535fe01b0a76daf1fd043bf9f80e017622c03708b0930edf54b0d301be9bf41421df41d4f81e82e8852ef01d45c737d38eef251fdf1fc8e1db033a7c77001fb1905c867f9220dd816f269df4ade4be0b1849c6e6c2b712e6c1cc9d743ae9f84e827930f33d9d72f81b3e4ae2c280947f011f25e9286c543a895712da5c38935410c9d278b0a1c35ea4fb74a32c8d86f7bf551e92a50dbdbff76469395e87f71adeff43b2b4fcfedfd3176569b6f7ffaaaaf835458b3efd4981847c5aa314f06995aaf1699d4a6b555aadd27a953652dab9b475fd947695b4a13a2a6da9b4a7d2ae6ac0a76db5c3a77de5804f1d29fdd4733f3e759d0d9ffa138f4fbd0a54013ef528037cea52387cea5308f8d4ab12f0a95b2de053bf62c0a71609029fda5c043eb5ba219fda27097c6aab58281bb607c09eb1d780bdc973e0a6c24d00dc34e0c64d20073ce463870e1eb0dd0d89000418b0800420008701f0b0e147ea801d1a0023e6f1398bc9b8c17181a88602843411459deb2270b5bd0483b92f70bf8d6823928c48fa2f9f4bb097bb02b89d92c04efd747363c0cc48a1ad9fbea5e021f78520c1e596f2635b00fb133e2a7a256f2b3aa974d2277d33f9766aa7a4a41ff276c29ddb02b87333f9924e0bc8386a81bb010e6ce0c73662031c680e6c60031c0037709a46f4126cf3e8916d01ec54f49d047e298d81ef0a9c321ac047543ae9938051441185ad35f02c9ccfbaa933e0b3cec0fb3712b86bcffdbe000eadec7e6300572b3b02d7f6499760ef246ce270b8241ce270b89c81a61f59e5ad9bce176667a033f0689881f3c7d19b541d4dc232381c2e8f1fd94b96603137063095d67d5f001f51690abceec79682ad14ec52704bc135c3e09bdb02b82b808dc09e11f81a81ad11d8735300878eeabe93be29f0a3abbc3505ce1766eb743fe44d01dcb98dc09d3b09873218e5d69da6a3d7085cb3a346e02f8f5d857e090e97e09ac7916495f7fc9c30697c7f10dc447c0381bbc9370eb793ef813bf746b8971e09ee277f03ee22fe07dc4c7f043794f701779437825be98be066f2447027bd104e3bd358d248df3d2c692542bc1de2c74bdab2917af84e12c47b473fde6f6c09e928987cd651dedfea7cd650de9be9fd6d93a57511ffa4972cad73efede4fded159f7593f7b73b4b6b22dedf3ee95a2bbdbf5daa41cde4fd6dae0675d2fbbb5359da8f77171026eed3301b7d6a6693458baf96268047df719696f6aea92c4dc80f9f9a3906494c4c48153bf26998633b3a2bbe5a1ad1ff7cb5341defe25f9cf89f88a5f5b0f1433ebc7056e6463e3573112a2b732242395f2ded85c7b9b1a9a1f96a693e7ec6574bcbe183be5a9a0e5f7b3f82e9df4bb08d42c256cb10d84e29c1768b10d8521d61cb2509b6554160dbc551ff70e7df40ec32d8647146670a4259997f88e44cc31fced404a58e9c2948e5c39986a09595f99b46ced42c72a62191333585ce74b481bf1b6ef82180193e1c6578d106f68204618561c862d9c0f70007f0f43d8600fc88c306b60a50000f215296e680bff1230c2f02c006f61e3d5e68dd7678175e1ca3c2f1478fb481db76b15351d1db28d034411b258a55749ac004de46594b6bc00f7d8ae61fbf5a9acd06066d173b11e07b20c083394b0b9baccc3fdc41599a03797f6b65690f787f1b6569367c4eca04a16ab0513968f8f14129ff10c91fa4b234b1c9caac2c4ddc85612b447aa29232a340ab506c39d560e5b0c9d220f0fee193a50de0fdc32a964680f70fa12ccd86f70f77961681f70fa32cad00ef1f4a599a01fe86f70fa92c2dc8fb87559646f4fea195a5e9d8f1fe22ced2841ce01d87f71775968680f7179b2c6dc8fb8b4f9696807705bcf3787f71676912787f31cad216f0fea294a531e0fdc5294bebf1fe2295a515bdbf5865693ede5fb4b2b41cde5fbcb2341d2cadc6fbb3902c6d02efcfca591a0fefcfd2591a0f7802b8084b00f318825b086eaa20d89b22801d02d8e21e80ed0e08b64d3fb053a5d81d8077c08db332ff06e88073c0d5ca82de07ee8119b0001eb85ad9ec158013801180ab95c5781c70b532187f00bc03ebc044b85ad9cfdf800d800b806dc0d5ca7a9e0078005f03ce8169c002c033601970b532161f030e00c681ab95ad78006018fe06ae56f67fc32fe06a6538ef02b6e18c87b00d5c6d3570b532bf2a626230297efcc8506ccd7c2d854e1a58e887ac10ee5cc34138ad52698d4aabd50ccbc031300cfc02a70e957a151f9cba540f4ead2e5ba4d43ea5b60a8a5be0d4ad52bfcafe3b5807a79e4b5d8771da52694f21754e87a63f3338b5b9d4a762ba4a5aaf70da4dd977d97143caa6ec4fb2422a113724cca94835c4cc290bd73935ca4278e41faca6b3ae40d2fef945e246620c92194642e877a582b1eaa433566dc99085ed16500a940262bd22986e7e0d9b1ac706ad05a168a547b2f8a08841a9ef03cb1b370b21f3180542e1a8b3a66c06bbeb90bb725135883616cc95834d617107a59eba5a07c10f6ca546593c419b3fcf76ea7e6aa7f297d3fb3320d35875d214bbecb5fb034201d37bef882b230b938e7adeed8d278b75a640ac551e5f4c323bb39ef59d59b8b377dff03e3da2cb1fe935a298d6641f6168e50e3f05a3004621a9cc7d8e55b5670933754d8d55aea9d18e55d96f7c279962d721105419f6749f72bdb98b34465959d76aa966349f5a2e339fda2a9acca7d6aa8666aeafc7ba31cad2605351611ea370786b8bd6de2fe6297baef9abcbe67b12339db11a806853f1636d2b1e2e1df32ffb3438f1664f2751fcab834d591ab8bb53760adc02ee2c0db6b3b2fe7037c23098613438940ae5825a59197a5583db664eb4caca5a8693e5723f2cddc9c56431531e2aace342ca4a972b314df32a836f963710a9ab105ebd78a87257c14c3158c67e144918fe720c76b2d1c243857532df99c23b4c8e4a45c9743b13ea46ea46ea068a9b291e2a4bb3a1c1f150d520da6c6cb02ecb90eb331609c31e599665d982e19b6968aa3ce5fe1432fdd5c2683e854df150c9a4b8acdf2c6fb11f5fafd7bfcadb4bd6248b61994ec54b850a0c53a142a686323232310fc3b68a95dc3f2363698e5cd6270dced126cc7427e77d4c18133396640c77861d5181c59ee689a689664a956a652a687056d66f9a3c545b727facbcc17e8c89897d4c798bc5c4bcb5ef504cd8c76020a0d48f0f6058a69349913531f1e30397e9f27e7c5083ea8d544c59836813dfe29a9d9c28d3c53cc974352a6a6a66ac66ae7ce6fd1d674818e64c79b3a2a9a46dc1306b9ea6bc01a1a9cafd8a796badb53136168bb131fb43d6d6fcab86878ba330dc39863bd740e57e9a9f2bb97f06ef78bae4fe1436c543856f74b91fb6b3b498a7348cc176b9657ec8776ea472bf0cdec95959bf0abca3b3b29d1d54ee8f79daa992fb63313b4ffd164b544ac343857524d6ede476260d8ee68806c967df954cca74b95fa69335f9ccfb8e3d787bc5c4bcc5a91b61dad8c7589e2a479b87cb8d06577bfa6398876a0566ba93cbfd290f55eedf69f2594a134b37365fff851a1a1383f9cb3d4cd3849922699af7bd997c0e0e76bae1c771bcf61302c66814c5680869314663acac91d5327f88a041adc79ec4942b3bcfa2e5c606db2795498f196bc5d6c076676539a83d3a689f2d5f968460a7b23217ae9345cd9960425e9190108d1a356cd818ca34683499f136ca1b7e1b43e54de887d033c75ca2597ac1c286a6c50e8ef7433e03058a8b15333f3a37345058eb821e4589c938bab531211913438e24ab6576f91939a7e7494cb9e23bcfe25a7caaf66c71d4a9b87871f48bc54579d23e96c98abd62774d309ab36951b32206b33adb9469d0807d8df2d64c5ebc8df286f33686ca5b33fd502e6f2b3edbc4eef7f2d672713a8162eee09998a4efa49bb751fea242d7bdbc5d1b1a3aef95b79cf79a94372265ad3d313f7ae00541eb0d8262abc3c63607c3dda5bd388af3232526137b128efa14bfe2bb2caec5a77c4b52142ffec5e22c0d6cad143bc5516b25075b1c162d273b4b37b79fd8e8e46255b5f4cfc9a1f92f6f373c8fcb5b67791c74f36779dbf973c50a1daba3e35d7574c496ff8f98ec13b5e40a1df2c67082080bf22bdbc9c330618845a2e8b8f8d719a205462e9e49ab856bc58a1649a2645c2b5a42f4c028c160cf82c50bd20532232c600fc36c25325688c5fee7e715fb16626f832d690364c95bdef05f5a0f3f8c30dc4db6488f6c164946a1d1427913fa167cca5b3b69f13fe50dc9ffb8286f42ef8289c5bf286fff2f2e0c0b0386778501a385f216fb5abbddddda7bcbdbcedf56faf216e5c50b18e5ad25f431cadbfd18505a2fa3bc7512932634b115f33060c4286ffde45f4679bb2f6336a3d1802c1090770502125bfecd44e9dba3bc8fd696b7d68cd25ffc5e79bb792fc9dabfe347822ede2b6fadf7beefc98fe1c3307f1e2c6f3a0f36938fb920818072727e4650508e9050cccb5841e805c4cfcfab84d24203f133fa8047806a139fe2ebdb27b5167e7dcb546738ffe4535b65f6c3a7d6ca8cf6a9859af5f0a9bd92ebceea72f8d4365911d811e4146161c287d9129a9b9d209e1a0b484e94488c2230c4d6955d47b59696eaa34f7b6ac70a974c27f9d48f1cc99584e8616104caaff82e8b12193ee55b9cca53afb2722f7ee55f2cce1e11e0538bf4b1769d822b8d82af2768a82fc11224b0f22350d104ee3cb6c8b2f4bee57299a6399ae4eb35c260b0f1befdf7ae0f6384050b7286cc080b25325668a209fce51c9a68a2096c730e393c69a2091ce61c72c821c9063714283fa62061a5f7291a0626168b758c8c8969152a54acb02b5678d715dd72ad5861cbdb8c8c6b450bf15e79eb8101411004892002c49e9b8826e2c79004979ac035cf70c18f32a48ad27b99d8cc0c0d0d0d0d5953debcafc9b1216d6c7c88ddecd0d0dcf004edd0b080a476a2670c22280cb148ebb8f8d719a289918b3fc2b566e076c133cd44fd7843da94dedfc070707272726039e43f0c632cb332997795c17668686e788276685840523bd1330611148658640677d4b713274e949cb4e0c793c4a5f7e70a72850ea9a3d3d22177765a2c58b4782c0f8f77e569ddecd0d0dcf004edb080a4d6021f3e7cf8683171e2bd6721cbf9162b34138f9637f3512cdf5164e5cd7b5947e1215bb35f61858ee2637d7cbcab4fcb091d3a74e8b8597e64816c756e2759b030b1c33905d8f6820c38601e3e8010e92bb848a3001b39d1184560887d02dc228072e1a3b54081b4a3044bac70c91cb9121309acb03042ba6005194b2350f1837d8b537989dd8b7fb1473687ed52ede90fb17d522d9395629bacac1f08db29d68a3d00b63b560e26798258d041c4091da316f451540b2173a816a010c022e57e1babeba741cd6666cc08aad584847068d0701dd9d9a12d39b2336ba107a1574f7da51ac4d7771dd45a0fb1a5ce6e983ef5ab2f3c9f5a1c1048543b3ef5aa284c21850a9e9f999c153f3230173045aeea175c1f21e5a204c97e20baaf831a81b1e4838fe8e46c6124e3533f42120369e7c511af0275a587590b5dc41150f8b1c07224957c4dd242283935113fe69038a5f739ff18e31193e739ae58b1a284a15bb02db4e05d5b3082be8fe8c36d513bcf16329a189d319090385152c25fde81ab4d494909dbbc63079312f6bc6387120ef38e1d3b9294686c7e1462c2e4471d7245e9bd4eefecb06051decc67918205d9a2bc79df02072551d4d55166b0a8910535fa0f63c908fa3ea20f37278acee19cd013274d96f85146a2a5f7b2d80a2bb0c0020b3116481e9e584f4f4f0c1b2386778d11fb59310353c4450cb3a8a9c15166b0a8f921c88913274e9ae05ee2db891327509cdc2512fcd802d9537adf820fe9f343c23055fc4f7933ff27c50fe9a2bc79ef02e705f9e2058c666934ef4a8b32339323f3330353c44593e0efbd4a51acfcf88284517aff4206cec398a948f1777661982cfcacbc35ee6737c8060579d7a09a6f14dc5674e8d0a1a35b77043fd2ee089c6e78154182040912244851519020e137835f90018711601e3e80b0406ce488a3b83e0a92fda0548e8fc2f04184422a398d642071266faa3d7e84103b47684c7ac09ec5512d3e1504f61c0c815d07d5a7ff06fb16a7722e8e7af12f16678f8eb0c86a9227e70746c70c2b443a4648e0d342875e22c613f62533a9dc2f8cc4429016e5cde66b424236356ae8d47456b8476ab2253e2cbd90f9d4a13e9b506de1eb3b12b5d6f3f53d893aabf928333ef52c1e65e453d792ba943f117dea55b03411a4837021166972f33b46301648829a266705104c7e1a47a48573b68842fab4b5bc7af86109ebd47438b202922b796e49fdd4753c2c1c013d6d2623c657183e5f7f1472ea227eec21794aef7b5a68c1c7a7bc99efe343fefc8c2e5cb818ef0c3b6386779d41e4a7cf16383827d213570b9c1e7e78b58484f09789848484b0cd44442029843d13e13124857098898888949cccfcd84b2f4817a5f72f1a068c183162740c52868c9ecd66425648c8bb0a3592a058ce8a6ff7f29603041374050d8ec84f2f2d2d2d2d619b75d061097bd6418725a6255cb3d013253fd2c859e93d2d06043463c68cd80c32282856abd56236ac0d1bded5468dddfc8d116ce79b200962d16ab55a4f702bf956ab95e44721b2567a2f4483460d1286d941c06043e5adc80fc170e75827f9ee308fb116e2471b24ac730b01d688f9f710d8e986ef208a8a8a8a8a8a8204297a6f14c5239c81b0adbe20030e42b008a2cd68725630b514223262e0b4407afdc004c3a7cb91157c60f2a616b01fd1831d094fc2a7f815df7916d7e2683f0f0b362e8adcec20d9817692d3d54a7ad0d1e1ca6cb8ae9fa84a6e98fe2e424a2c7a51ab3511a241a3468d26366c0cd9a121ef3a6464068b7b83bf6b6e7e7081ff1e71bd68263a45ea43f9b4ab7205e2ebb7116aad715deaac9dcc567cea4a443ef59c33b9ceaae8d3bef225cea44910d90ae70b9db1c9a97d508b2fb8b491723e7cda4c492c8cccd0917695b6d25069ef5a4bda544648be917af8ba44e6614428b87347c14e3afc0844d24aef8166cc080a2a6fe6070591b5f2e67d4d48a8bca12f54c2706bd81a35bc6b8d582dc76629a8c5e30809f990b483236464068b0b050afe7211142850b0cd454550b0e7a2222838cc4545450f05d7ccf4e4c7662244c668d41a35c818ae79b441c66cacf0e39039241b2263998c35c964ecf5a38d8ca13632d6f9da2c2dd5726c96825a3c1326f8cbe3259b09f69830c136e7900313ec39871c98e03043799e1f4bb255debc6fb95ce58d86f9660c77e9fdebf5b0f2863e2c76b3b79b77bdc5787ec78e1d3ba230618105d90a4444444444444182906423e1dc03b6d51764a8611c58c03c7c007194c81219211a1c1f581899010546acc16d25871f5eb85c4655ede922d49e3642f5e92e8e7af122d88f5cc9d1fe1fec4c584482594da23339413b767874dcb8801de9ab7e22fd3abc8882d084849668d4a861c306bae2669bb377cd2cde263643d3a4c97b799b9901c3344d5eec9c17c7c5a71d65ab5fd3ae92ebebebb712b5e6fafa6da5cefac9d759cea70dc5cfa73da5f3696f51fab4a9a072eddd1335583af2c30dba2205cabb9aa546d5b4681b167f3543a303cf809d2f76faa916e483912f42e46be756c24e3e7e64d1f871ac24399634ca1f7e6c99477ec8c716e9fde8ea251826fa26d9e38df976c2c4e4a445d730b1789bd88349f8cb413a097b49f826619b8304e924ec39081ec31c24489067f251a06418ecc7b22c5b240cb397be55de68fc0fef2a6fe6bb4a183a1fa1f16679f3de5c42ff55de56fcab611606f3aeb0b015f39daf976fb659074e921305a843c78f2179719895bee64ec23523f9b17db41723f9580c1c45f1c80f3d76ecd8b163c78e1d3b76bc37ba5cac56587b3a866bf6016723d8c624d40289cd2b1521828d549f2333b871337033bdc0bd833b892ab5a7ff855b04b54789ead3561ced2b8ebac0ad25073714bdc5076e2a2bebaf056156fb902b9cd4a861a103c388fa29574771a27ea6d01d33ca1b0d1a4c356cd8181acad9666d36ef6a7bb20405ca936fb2f417ca8f9e57de6cde83cd9c9fb6ad5e05b1f8fa466a6de7eb1fa9b38ef2356d5d4bc19f7653ae9d443f35ae919a026d01a61f174e706ad29aa52e4935f92b946323244667c5a32dbefe18d3f1638d1a356c9030cc661a2261982e7e7ecc24d38f36128689f3b6f256e324e6633d62d88bb5622c56ab936ec047549a89d28f637f356ad4a831921564b5fc7f5cc41011c5c4e0cfbb644c0c11d147c6101111bd37c2c010168b415b605bf57941068c83f3b0c14088d49e7e16b848ede9dfc146aacf11267d74e2666a291877d30dee2474566056fb908ed6f49328a91c9731d3391dfd8443542454de626758a386d2db286f34707ea884a1f30d8dcfe5cdfbace4e46de5ad9fbc2dca8fd705eb820bded585b015fb232a0de595bc8d8ad0a775aa52317d5ab934e9a56aa544e3e6d3c741531e27fd2405aa55aafea00621d7209baf6fabb59aafff429d35938fd1ccc07ac0603d7a9030ecb9071ec1c64e357e0c49180e738f1e3d7e64892c562be66fc047545a497f8333c670070112d2516433c6f3138346cb3c26a97563c81806f3079231ec85640cdf3c8a640cdb9c2858640c7b51d18f2319c361a6819d84bc37cad49194f1af19866bb6a9c1b6eaf382a332380ecd847d2c6120334a988c8279fa09be01bf95f56828b83ada4d6ae07682ab0b298ab164cc25633e320624631cf310e6d88f2019239231ac3ab25a4361ee249c6b4f7f0bdbf00b3cdc0706e2a8679556a96ca36a0c1eaa3dfd9f3efae3f5b8e15399984f59c8b506d55a33f9fa649d75d2d7e7a931aaacd662624fe3eb8fa363307ff9065c6da3bd79f46ec04972a2f86ec037fc088e20e922ab25c495633ecc8d452698958449fc56d6dfc23c3546457b3ca01b686059f5e98fe1fe2e63ceb0f6b48ead5fc32398eeefed9a9eaa404155796ab2388b64735667753667912c8eaacaeaeacaaa8aca84ddd60ee65f84a7a4a2764d4f55a0a0aa3c799367c2ae7fcc914cd71561d705acf7a8d48a986b733dc91822110000000400c314002030180c88c4a2d180385265c90714000d95be6064248fa320c721840c4184100018000000000000040620d0026dfc17e3ba7e998cfc5bb0d9206f2d1ba68892286bf7cc1e0f614896aa12fab3d175ec582dcc987aa0750e3f671defc7a92b7340428402c25885844c40c90597db4fa43e2030021bc5e7c63ab4556b81a001f222936bab07729ae185997b723d55614a1a76b84076e9c9b0aaf5672361ec58597606512e2dae8cf5c99bed5a730ae3613ce4b54adbf64382e158722d6dbdf01d9fdcc9fa5d580ab102ff839a04a55c87215d1ccf6a6582fc756b8cabdec363840e0d03d142ef6109d3f565f650b0b410226b9073d31379ae4ce4ee3ff148d6280d36d2555c57bb2e0589f469bcdae362818337058c5a38e942df8d071a0141ebf545e5ed3a94efa877418a952655b3c4cecba71a51c27850d0718d2f87b3ef6817175728a3c99065e00a77c02a10e6687c5ba66ba241bc9ddcf43930b1538a54b4622e7aa6c1141daf4bdc5005e21256b6fb3fba25dace6890b55b3ffb564e59ecb257d1d61327f5d7ac13635d6eda2cf476559b359f501f71d2c4fc7d34917d2eccfafd01adb67ebf6aa06d73c5c058c4d76759653eac00d5b2e75f47fa14e68a67693a4704bdf1491f238dbc0e8131368b1c9e46c737515c24919d5373ef04434373590b63f018e3f2d67fb042689ab0e20f89b7514ca97a6eca852c86001f677200c163a98c2ca6db272f7b354666d70760a1dde29da401496ee6f6dd9479c23e84083e2a31f7c833b3f108802b0350602ae7486ae8cb902b17e0ea0509de43c2504e33f2ab1dc923075ff83792dc4412df10ac557fec578517dacab7de705671303db21b000b2a391ca313f1b8aea0a97053e885d8229241a1a12df298114200a615f4834cd0d8b78e1b29dcc9e8787bfd52250efa6a387d1814ed1a68b64070531158ac484c1f89bc6d3f84b96cecf666c4878604147f8de23de33114dfc6c5247201a0df453f7771b3f25de8d18a17e9b1dd92668aeca6614199c029ad0373d48d5225f6eddf1fbd3e51ec4f5b4913815af1c5ca3eaca559a1184cbf79b4433cf7cce57ff7b570f434f94f7e3fe37fb59e44ec4715cfaed9ad925de2c500e137682fc06a4241588629a48f4aefb4e8660f67487a1d1e423737487be8e77a76230acb8ddc869556948e54a99326538981d0a7fa6add4b237c7fbd465244750b4b5c72ca8151eafa3b6cdd2fcf69c7330814e48b4834aa9ed4d2b978ea9299e1ca93bec0247d92fc123a84a12b78888f1779e8f1e1d08856879649874f0c952de8c5b7c8b4fa8d58455b89d9f733ca4dafd614ea87e95a59c2be22e1f5828c455818e606f0855a5d57e6a2af2e8e6182c3f959ce3725e698621d78375a38dfaa06f72c9b748acb4b27272619dc02d395463139f8b0f94e04293e3d4cee0533aaa78552cda212fc56db37dcd8034b58b969ec9f724e6346f7fa9dbef6fbdca34dbeb3aed38aa597295c04d88f5bb4b47be3b0812b9c511b61f76a9311770037f83e7bd36387386d9516c5eeb72899868575e52bccfe8d75715fe8ac726162d97c1d7f92ce70a7e1eb6558bdec8d1fae24540546e4c7f6e067d43085fe4b75855455757a117639c04bc88f4227bfa1e70db766d494af01226440a2abd6882cf69fdeba386d593be5236080168f957fe9033838577b413c2c47438e357714817d67da468b797e2b53060ea04c0df7ee1965fdbc8aedc458606f48f62085c963e4783e5f7c448079086891373e7e9ab1afa9ac9553bc3b98520596a02b81213b5a153810a7a6b214663afe24f093009c19bba67a87bba3756de98e5e560fc8365d9f153dfa3f47a162ea704874846e7492b814a6985dfefd20a668553d3f0e0ede4b1711987896d6196f1018535e7dab46d0f22709300724db564f8b764b4d4598d0d6f8bc9f588aaa0a36a68fb3a038c498a0408d475f50878467b8bfc34f0533a7eb82e00ba3d9d58987beae7f11a745d23dbc37258965dfa5e4a5e84ebaf088d36dc6e13c13d23d1f6ae24d3d95ac61635a706479db20ac927c9a5a8c839be7690faadf52764e8a49df9ec8cf063a6c1626a99401b329c8ce7c10ac35d01ba374097e62cd4e2931c58a9970b073e0cea728fe0c908595238850afcbca9761bb3b01354ea8065ebd35ded8aedbbe6a9cf14a7b5c0bc493b651874d36c437e046a07d040de109f9b58cfd6d2307ef651b02a3a766bf8a2a2f6c6b19c79a41fee1f7a8946fa85e1e51d95746818e3d8d6d4ead40cf6ceccf07ea0099f762f6183cc134d0b3fa939193aaf0c2926cce2efd67a09dbde147ca35e92b63d0dabca85654e3f96c641dc465decc2a1335a49006b5cf81113762371dd5d89f318bfc9f7d49d2043e7e28b4cba3e65cf9a1b9dc646fb4ed74168d7c07528311e3efa85fcc64de3058d0c4672e35deca185e0b9c5908c4219da675cafa2f6d00185649ec2c8f2fdb845d2bfcafd03e75100a654ea2ebb4a2b82e19faf1e16211e8b244ba749149a0f06114aaa4fc8d6240132c11c932f57d750400a84f3d53103232080ea8af68e42dad862bf2b8dad723e3100e81d905e4b3702da400276001795e1df198b8824b497bee5aa9ba6cc1766e311c83541a6c5ecb9ef9ded1e4d792ebf53d398d193388b0718cfe73acfa95c336cb562dafa478094b5768549dec00635fb727af479d4212a88bf9356ece548b5d829e2cb40ad643874f120abdf89f89b5dcec302d46a90f9c3a3d6510929e3a84e3fad1f9f342b818d546c8bcdaece0a6b1372f7604c3a0572d2f6cf3abf33f8c022afc9af3f41413f5b51eb08fb11d60beb995cb92ef47c954d4cdad2afcec74280ac2d9fd0aac5a2d5779e65d2f7b927e715537765f4382a89278a559c9f427b2e983435e988aeaa9940e6e97bfaabd54ec22cd91d74af633a3df3d30d50423a6ff99ab5a5d0e584ee1482dc9a15c86a972a70de09b78c162012cd78896e2d43d9369864db5bb0ce3784a1910d3702dd1ce34430dba709fe6519479564079383394a34c005c86ed4ef9a980fc9b85c789c29050b53bac24095ca8a65368e651359b6f2ce05ff406aace9391e59cc27c3b8cf99dcdf7038a85bc4a04f08e41d2e2a26c87190d8476eec16d7e4cfd1fb70b19329d1b1f616387ee3f2c0a74c252fcdd95c6a9aa10f7f7ca45db1754f3cfc9019f01dc96e644ffd4c70503e5cffb9a515c816b37aa4d0a90d8722c8aed08b024ee32f1dc0fe34bbae851f30b225e3ee24627ec7cd33640fa2c1936504f45eff8b9c5b9c6644072dd864c7957410e54930a6f8756aa5d255e0aa16323a7a102e519ca90880c0beef58336107f1b6bbfdd58311e742a161cf737e0e58bbd4155518039dd1d17d1d484d8d2275b031363c61e85e25a00507ae7c1606c022a77a9f3b9f46cf9310b7aded221092e79d7fcaae768b31ced8da35d385af86cb4ef564da42142ed18c0a60462f6dcea4e50985e06b182676eb3d9a8274b1b1f2a998ea9c178e6139cf1207630e08e82b5dca2988c4dc604513445b0442b58380d2b7563eb931d441508e2a157c75ac677d165141b5e7e84e89c0794ab557b2b9a46758a8243aae3fd8096b1198e5c72c0309884cd07b120593918de152e22f95e1c20e02fe8616f2842cd1ac532241a462be92b2c51157215efc731c5edaedf0efa9712ca20587af92e3c52bc31ddbdafe50b980a36594186c9af91619126d5e7e2c0d464341cbb68e3e0e8cebc1f870eeb5f00370e863a30b05f54b2480ad787e027488df40b73c72fe2582dc6fea7a02cc25860a5bfd574cb1de57dac7065137c048617c7810725e001007829c14fecfeb8008f2214c011b96289b2601f36dd0267ab9abfbc485b2084309e503daa724a60677cb638d04c3c18b8ea65f0a752b1d4cbc321080d93d94d0d8039ad0014d788ecc217a5c2ef6f864aebe438a06018c4ede89b266a492068fad6d1c9cd494169167f6d283cd8484f4c8a75b251f910deba48908640db65e321fe7cdcd8f1b42982501d92d0f8bbafc1d0d37f4c4c157be663eb98d931824022ee67e0020b144a8cafd19c074f07da47bddbca76d20d4d598561cddc08d5c2709a718631430713dbc36d336c4ed67bbbd91a335cc0e336d30c690882f1918c8c78c24e229661fea7f14ed624885b2b65af18868818b8ea452d69b3b0bdeaf5bec9b70f992cf62802b915c13c3d3082d9f08ee7f23ae3b6a03267e03927bed2d2677307d417dd12b58474d20df88c9035764394967b17b57447d5b3be302600a6f91f027557df1cc0909f19a60d5ff205cd62f0da572635f67fee960c7a8713f7ede103e5d6e2085c728b887cb731a105264be8b07d75c5234e3377c5fe6f2a5cdcc95ecafba411db611105b41aad951e93621f095a24520788e936326bd35d19fbdacf3b28317d4339c3d0410a2478771e10d099e99351baa2c6677e0338b3008b211df4e7ac9c24928334dec0ab353a9a3a3721ffbbc0e021b55b9cd0bbd81f7cf1d6471b1c22ca89d0c1de020b3b4f8161f77846e20753c3803a606258c2a43f0e9c1fc74ec474e3a40a17d1457b148b63661b7977bbc6578f1fafe848432429db922653930af8a0c4981a2acdc28c7f5b40cd25a4ddc582ed3168d20dd134df25ce3b9ef33b85158beb3ee7beb31fb9637084a875846b68340d2dcb876ca24a810563c7dc236457301e5bb89fbf296c17fd9c034b3113c71309f41db0c74f4150781732a078ccd11b041b1b0fcf8f6aebfcc426e1e43a3e7117a318e12909079d30bfe99d6a1a424274159e873867d2de3f393958bbce1e7ba598df1bbf67403465ba499ac02e20e73d7bb9e3d8802b6096efc8dc9550b9960769c0109bc9dd9140d256ae18a8a79e0dc931e0926eb1ca50a093185a5fa9adea00ca22748b95f9d28a10f8cfc8f604c1a6895969085195aa35e62683f173876374955635aa5b8b298b93945f824a053eb8f92d7b12c6a4736758a0f3cbd60d160930298a00c37b135d825105f884938bef1449fd2a0a6b238422324a19f0f49f4cbe2b7a844e32d53c48b4eddc2465a7ca8c24d6b7b4178317701c814bcade2d73c4d4877e9958801ec59721b1aba03e08e53dd652a717af65769efb722a0e3ca2d7b6b7ac2d9132942169a109a35995556f9b930b4a817cab4c2b006e43eb0abbbbab9874596cb78c104ddf9600d1e6b14fb27015199ed452d05419cc91529fbb48f0f719c76c5574f952c5ed7b319112a97d56d24c2db1e02c4abba7d4015a8593d9bfc1f860e431f8f914b33cc600fc8295e3a543bcce9b12adaa4e1e3791b7401e7986c6a60cec89ea4247d96c8ea604b50f2939ea2c4590379cce4ffda28fa87c9c82f12e513d49c6573f7452978c1ebf148d18c300f983708783bcb4c54657eb205651c9a5189190c2096c66a57928f533bca7efc92794d6ba875460eee3eb4a9cf785d3772aec60c1467ae601b7a439e62aa5ec9e15bc6b16a3317d8568d48072c7ed2a868a23c7234fa82769b1a9608516c56879dd1b2bc771fd74ff2d25a2eab9fe092eae6d1066b4340c3c522a764202707052e8467d426a4c3269f346aa41b64d62a939f414f0771ced54601db5ff4c0c89954463913f6a831834baef0ef84861fc27a08d11d85f69ee0d95a01999516975be59c9384ee20f66d2cf5ccde270001f3962c4bc775582306b8ab42b8dc636928f2f56f80da34d7809cf10a34815ebdf3950b61790d3c63cd880ec1215e07d5c07f5149a2a87841ef795dce5aa61dac947607b108e976cb15281c79a414e4e090102cee0f47e8e9f569149cfadbe7a5f7588232534e0c97f275c4cd2656cc1a99ad855a72f65f3ecbb64c6ccf4e15539c3af583281025326442acc99f9b29d9189bc7989fdcd26f65f24f8d515b6ac934d9eb11e84d9ba8df033cca89cd02866d3284b2910466b732d9c7e1c80467fd53295c9301ba611f819bc65fcc7668a874f4e0b80c27ab8e1db5abc2de03e564e7318bf272cbc9a65b33581428183044846ef5273d92af7666852c63e94535b305f4ab8901ebafa41507aa83144e1c05b89d97a0bbb8fcc27e5df43f355a865d30e46bc8bb088fe32cfe3e035bd4e82598c63b33eee5d60c9ded42774c0a2d6b67624b67d6fb2ffed653e637c297c78b3f2c295d2102e686b2c1347e587f6cc6c88d4590f0bdc58228448de9560d2297168c11bafed83727ed328f2ea481d234f83515e8f3cbcd035517441fbc276256a60e3e884390ad67d142092b17eac448f2e3d1dc51939b3401b3b94ca8f1df362986ab15466c152c24bb89b6eb25342ce955fd401a19c8bd379b5ad75c90bbb5c8bbec7fbbafa156b64a04be251f5f94e97e4c2d001ea06f9d4d296069621afbcf7d9b6f6aff8f6cbf7b971e1f64d58a9883022955126bd2a0dee5b98691bd82565af9a59b4afdca8351aabf45d225af64a8503a9293ea10303eab2b5ecb86471953d1b6d8729924b48354bafb767db8c65566536e5bfb34f9b119d046781d03d72ac8fca6b32ee3821a0d96f54e4aefbe860380fd8b6f0a1d8927b2399c4b8126012e79622304f82c283d5d703fb547b9f5bae49507c2a65b96bc51aecb87c6ab18b96aa9486fd352cca9dc5cbc3ede9770bb219326ad72fd9aeb79773051f658d275b54f3e7df703df9926de50675662a4496ab3aae0047a5cb99a3f924078f0ace49ef5dfc4ca555c637c72edaabd7f4f33442716524e19533bfc764393fa53218ae9f8b1b56c29f8bd419abef825984a61f545ae980e5ecdeea2ddc35f85a88b02bf2fb6d7e4728a49a7bba0157e8ff7e74ccb9eaba07a51be3052b6972b7d9b5df42ec3b0cec4a6109f84d1c4dd30dad16c00882ef575051f69e488b19e67f281472961d79b22e0dd0021a0b91c5b414d422f64fe04ae5c64fc1a75aea31012d7845ae7cce577cc31738029327df28c13e61790719e24c00ebe26cf650e385a2b04c555503e1366d5ea9f89a639d727866e1ca55872c05f83737700b86c7870f01d094692e4d6dd1355d59f155f2ea0e08cfcde61f015b0bcefb02ece6340a53a4cf2897797d71a048f6c128e53248c06234c39db8e8e749fc431299b12b8da4fd1364f86c42471f640516d3aab634fa0e0636c5a3762a92967f4dd1adc39cf708e747cfb25f7db894d2b12435090a26299dcbd33ae984835095e9a56e0d5b94c88f93c498d3f5c0e9100eed93b04d2a4f5a15cd5002a509791ae98bbb46a6b143a68eb9291ae209cd61e34e28de5d10f440a870fb48df98583bf221bd209cb8b0a8cb382591835570838646b4a39d0116132a85d6d5d598d24d718ab9f141cefeef0d2c27bf34f7afab550926fb53e89be3412b1fd8a8cfe50427ab9b04a829d98a661adcd8a18b27f84284cee32af1d852f197f41d349295b15ccfff43c64fb6c74c35c78cfe19d5b041b0f33d2d5fa2402babc10a114f21a9b1b7c80b1196b3a48f62a6e18198ca3cf90006ee701e1fb90a91951beb6dedb7d3f48f77720d48db95c543a0b2b0deee906f58d29b9ce80a447950ef9584e6b71f606a1a6176f70d2abab0f7e419f1ecaddfdf6783351449f2cb9e7d07cb195c836f79c11f1e40d6bffb5276abe9123d329800e19f7570ede026477e2d8b740a3fcc99c37615b3d37c345f47b3c6a875ed78dea90237f3988495f90b38e97cfbf66d4f7eae6b92f6baa2390299af4659cd8de5d5e874a39b1402751488d640a3038a79d693c9c4487e6b4ebc7a8539b2d01f67b89fcce82af1d9560dfd73a17b8040812e8085ff8d16c0610c0bce9ceac9326f24f913db2a6db13fedf9dcd4389c9e12ba9432befea82cdde810a49acee269456ba9002865d3a0db76cf04490abfeaecc1871803ffdd6c1a089e57af4ce646f06c749ae6d5289b59633c1b9dd6e88d8aa08e4aa219717430504b27e40249f3916e4d3477116e42723882bc638349f706c7e166b149aa04be6e8942528f8399d46f85b7ec615442c6eef9a6fa603a77fc3f92d758adc306352270927fe80217de19b012aabd9e0b9265d4d6e23648dcde23444a5db364530599386c1891ca3708e073150ef868db984203495c83b8bbbccb0d25cffa745a946086ed9e9424312daebfab6b4b36a1b4051cc850d70df0431d00cf2804293a887b0dfe320634642042094bc17b33bc457059d7436436e496425652b1d8800404d3a6610728c8ce4658863af90216776e05e2a22f485c75903582201d486f00310dc11f8894531d015807007bc9ff282968f610713dee7de751707828ef0e276287a233749fe8d019625db01c0288e3be5c70540854861871c3716d48181bb75a63e5d4c0461a9d647c3be71973d599e10503307e320a660c6d89e1046188198cabf862cf5ee0d04547e6420b5bb8d68121240b162e5ba69c9e36b2bd79450d26e652514535223c1576b97fa778e96e2144b1a8dc163402a84b7d242465d5501921d65d1694623099b50754a0a8ef32453647eacdb33a211849bd9586f1079774848ea4128b5ddea559ef07814dd7a333d285b555542e6f14ed91a0b102ca9e356b8c9681cd74c7fb9fdfb8a0c9667183595914ea1e1d2c91b5e6c53d5dc14e19fbc8e0cb422fca617c032c82cc52e43590753300149bc9bf6744ce4c9e920ee92478296d3c87a575af49d1e6209352688d0f4cd43243f19e786a5ff4ea0edb3c3a6365f9b51fd592c2525471caef80607dfe1c06c2614c45a2c398c04eed7ce82aa162e8d3f7739eceb379ea1e749eeb6d9ed0b35cc69a0d098a68dcf9cd8707ff6b8a3a33a3344f19cd939bbc6c6b6eab68bf5938d2dd34e5eb12bae685dcc06b6e802d93b46316ebcb22ae6565b96c5b0a59b590c518b24e43163964850f590591a522b22a91f5f4843faf3f801ae480cdc57735823d7430438364090e18180df6959e153adefa5dadfe6414424565a9bfe757dee29156e3072494d7303a5d60250cc415545b2cc45fc263432a6b0a58b2027a0ec0421b2063222d49ba7156b7ecd8cdf315f01878d562d47bf6da7656cda752fa487ca98fd559b5e960ecd22e6bcc7eb6c92df0272b8d20185be5631781dc086c0632238657cd5f4b7133caafc931f4037743c02b823cb4ebbbed94cf8c22e98ebede457fc703784ac373e2e249f89168ac023f7771dd318fbd626acfb2eca5d6127eb8a4ead35ad29c8face77939e8923c2361ec570c575095490c8efb6a4756b3fa7abb446daf73b4fd07d0fa06d41d0fa17737a7b85011d2c8cf6b41454d0fe90595642fbbe5b0976f34190894f335ba9fbe0781aea2bc8b4df7f8d31e9330b2f8b1fab2fcb64ddf772dd2fcdbfcddba414b0eaf42ad486ae7da3131c0e327a12c996e2c3919970de3cacb97832ef75d1f7780f5a4148947660db6758d98eb8273b97258cc402e321b0772582c59a299176961dd0b12be47b69322456c7f8c64828a56171c1371a69c71ecaa6706b01f337b73c62bcf0abebb441e12d99c92c75209caae92f75b0ab2fd13ce4ef6df131cd6a9710d0756abc58147ed0b345cd4bafeb1e97d869bdcd2fc8711ad09384a0d187889ec96d4e84a3e37625bc0fd7297d50a0c65d2e59ec5cb115b9fbcdf3bd7d6946a60fa8b14b9d4141a4ad8adc23e433f9a2abe75766d377b17a13f396045d0ab0ac63fa5f602e3c7e0f5f2208faba7f0ab2b34c00a327e259df8a38ab945332e19768c9592c3b784fb7bc6721a2192eb794d55741a9659ad20810b3b633a1d210939b9edd3e0548af85f68269584864e175dd65a072bea88700ae4a8a03e847d43331301a03184b46b83c00f2fcf77b1fb3efdf05128a6e291a24f4e7ba0300fce61387399e69af1bea3819d45d1d9e8983d6ab9466253f666f0c64da09f726381ae1347dab8b635cec5343e4a987d4fd3430e937a987c405fe6380ea1c91ec574c91dfdf59b0ea82bf73f55a0386e62013b9a93411ca5552048bae857951f51f513344a4539ac725a51a3716e4c44c3f98f65fdbc8041eee22ebae1358b2e5e9a9adfb51f1e9b2e997a3f34eade35c9ae01ceb59ab79f39ec33d84f1a76f0684af020d7bb4ecc0256501ff5cc59989a7ef0bc313210f494e0f3a4d2193cebe7af7bcbdbb072cbd0ef91b8b2af4aa915d2c0335d904e4515d3aa9105ceb4e3dfcde4784935dcaa2ea30fe69831d96182d3b1dc93f6c9a0a22ce513d13380e403156427803de79cacd53ad1d305d829e42c0fca1149468a12604ca9bdfd4ae8649ff29e78e6676dfc213d2403c24b559fbb0e22db7e0b1a03c1f32e90b385cb5ae19e5feb0bfe9ac855ca5eaa210210945af7ed370828d9617e5041babaa520576af81c18b5fe4cd3e6ecc985ca82bf4944744a0d0efd693be24fe3ea8cf164af7ad0e419427c9e1b8a4ded9082c90c2fda8923578d099edc94c2452f973828746b1ee15ea63a2b379ae5fba134fb8063f1f55486c854ff21de7b2b4ad3ab48e6b2a07434d61fc1db16e4b64ab99518e8030b52637a21757b07c57daf93b4e2371cced9870c57aeda7abe2bc91d2e8f560dddc51e51f9de2d09dd76b22a042a5d66c0e48cc78414e9c9a28c5cef920a6050696de90a1c4b4f5009114be764797e3b27d3c179494d68f3564fb24cbc1d5ff2e2e1238698126810ec55a7e84418108999124fd5a6f81986327dda0d576219d4a92da5641446ab90b8d06aee5d780d8d274a273c76bf1b9cadabd82312903e1f5f9ee37a07ce4b10ba758bb1defb71d3e6b5c054fc6f20b2302d71785178031c175c3c632bd259d2d771c14cb02de9fa2ec829c9c42d280e1600256a3e7c663b7b9162cff7ae98e71f1fe0499b4abb998079d7668e3974b23e063503f68d7c7f3e924dbe7404a9866988564d572e03d3c27a5904d8b27831b04479828479d7190b6f4dfe9365812c5ffcfd83d0a261bd6ead1d9127e871d8442fbcea1087ebbf5e35d53e9735465b4ca72b8ac0aef6816fe4c061be96b68639f8306585e96d16534b03356c6137cbe914c270b5b6fffc8b5416e416903739e71c2c50af2f26e991807c3e9add68f3de7daa21698bca21001f39485f5de76a2fe52ed31490276945bad451e6cf252868e777f8942eb1002977ef3e0fe13fe01f873e0ed060ab7ede5414be1855e146d425decb059c988a2cb326f03edbe9cb8225d0bb4dfbd341e80bed0bd4408c4f41151255278b2d0bd50ba42c192a7b1835e9b5f26c1b88459d092c92293e705d1d12be17783111f40964801ee8f876d4af1ec8cc6b0c607b1719df130334f4eb63e2e7133106e81946ff89a5f6559c838455a310c13a8642a78a991544e43733006b58fa078648766c63078c698c891e319600b6905ac92135c39e8c1e2e264827aa55cb27c861a4ba111f9a79fd1f477c5cd225e0c08b79dc187d6ef89149ad7f583f0ba97bbadb03d4c486566d2128b63296c7aa4ebb8877b4bab41b33d104995a20f2c437123edfc838a4cba5fc820ef2525b97ae277c689b39042f168ef18de4230611431434bdc9376273b5bf88d81b719a97578d525b13ee40592827c2a76e329ffa5c4f59042cd496e7562fbc4c3d29cd2f303d98dc5c9bdf4e1a71a71122bfe338a71b7e22046bce2ff3172fc6897fb7d91cd1cc3514b7519dddd03d72983b309615df4ad0e409b9e067bf13c6996253607cf8c7e925424842256ce26dce9a44ffbde997578db380a6354d5f04403d3571a24c824af958897acf269e79004e1fbccf528228b5e6e1007c7aa76183af237fbad06e9f09fd81575ea055ec7a7344846c536dd69a50c9f5622409fcf59bd22e79fb0f1ca1c73ccd20eda55a985f0c220894d045659711196af4fbdd8644c00692c529f885030ab654a7daf29a4c3d3bdc53de31e9d2be29a43f829e86c98b505aa701d178adc073ec6e73fbc31acc88004888f31ebbbbf1aa61dfd24d9a4ba75ff44c72a4245f4b2220ba75a4fb8a670d54914e65383d8c4aca7167d23d411e106b808ea4f9bbddfa1b4bb661b3c806226fb4a461ff147a86897e2e6b9883cdf1319947c52f7eb8fbb8d0b78c987a0a1fd32d2c36cc4f9ad7361c848c225c490abab15568d5dc0a2186b13ff0d2c22017cf37c08480d976078f4bc478162ccb168150a312f99c5e2952aff99d5c38dcc2cbbaca2fff79ba73955bfe45c25fb494cee4dd00dc110e6cb9cee9ed47115ded5074f34a26fd6c4408be714ab2e7368882eaf8924551a4d17fa50c34847b32419842fea9a16dd31b42efebe57c9d762b6058a56a5381e4c9884c07b146574d12cb6e68783aa27cf755b1b2d503625a692feebc4039304d5e376ed3e3f1440d42e126c25257c5ca38d7342fa65751c48468d8fba9dbe53e262cddc9e4ea24a31ebf7f70535365325bfcf66d3447a32b60236c35e644feef3c110a36612f0d257d89a1c16b02ca3b4c8bcb1c84a6a21c00b7b1db214d892e96f077d5f8149653bee3a2ba0cfc93a0c75a8c9f27335ee3ed4438231d4ff439305a853d1428037087834aa6a3f1a6daf6a4bd6ac6229adfa9b85019c03e29149dd97135ace7aa23e0725b6271c30c61d5a5f56116baa0d54b6140915d851e54a1815f43933bcc6e94df2a44ed0f8b188dc0d7bf319588adc68a13200410f4796dbf2f38fb38250d92046d35d3b3d5bd6818f345f07fa983d6140aadfad08b9f0966aa2ffdff094749651fd87215e2017b2fc57a14d9d30d73602f5806e46e6c00d578ace76579295e38cb6862f86ddf96fb45af93a173ccebde8af3c1a57f4262870fc4dd84000db5e2838a9ccda8f172a164dae66ff709a308c4ef57c758434ac39639a09610acbf55c8187dde7c13cf7a075b6709205d414e6275e95109652a994ac28966349385aa67b28cee6844f50f59cd299cd47668fa9059921b360f0682264eab1918078dbc2bda64dfac285762ab2f73e458e332f54eb36c64be1ade53f6e01958a962aee7410f40e6061a467033dce577dc1d403f1dbf4c3e6aeb3808f71322b7f470e8d9e45f7ae6309317107006e6c9303c879679006243cfbd82b8a6b7a5c1a92ea7baa2f4a72962520daab48bb0e13c52962c64c4c57469d55881eecd340a89162a8d59675a0e7b8d2fb9231fae5abb76488df937824e444d384f085963254b9ab82075e29d371ce05b3cd2241ad91045a9f8ce869b0c2231dcee45b9ad765166d137472f6aa9468fd9257693bf053acee07d7a42fb5b8be2f7a1474b2e210c5422572c8b08d6e14b8554173ed1d398490826c75aa1af58819b71794f2d21d4696dc04a7c80e4b8ea5668a5f72ae0abe65febb08d16c496ae652d41a0b8f9562101873b7a13fabdd5162d1f329b773d92f7b6a05555e5573ee7b85310a61c49fb43ee86d5a8e5ff39e07cd734e91b48d463c6f4a3c0dc8e62b325baeb6ca6fb60749a046ab48ec7f8c0759e57abc27e5f8a96bd86443e0529808a5edb5e2cef6014276428647a9550d3f8fc735bb985a52a6a53ade1b178dc4220126981660a129f620dbe1061945e63a73c79162525c0527dd45bb14950059ded2a8412e832ccf8ae68cee58b94222ce387e2cef112fe3e59a3128e4847cfdb114aa7e6bfec6080a911a95358f654f3269ea5da080ed13a459239270f2332461e0fb007883da1cf44f3a9c09c3302f304db8fb62d75540ce64608faada2a899ce690a3728397952d6271c5ba46662f2cb7ea028a486e2d4c2f71d17205fa75163b84837b2061fdc2f2608b861ec8cdd022d6129d91a570e78a0c7f86eaf199d738fe9bbad54bf7a2764a5e75f047a5baf6223ce03648b283ae55150b5230ab3af3698cc7bbed1c039aa87874f867516d06614ae36c72fb599f7bcde502b49989e4d62d208cdbec1c5c8946c6fe43d05246adebf8aace1ddfdfa8d58b379470b55d95a4c1b49b53a4fbfbd4c47cd8fc8a3c0ffb1ed2e5aacaf29ebcf3645278a42dfa568eec58e32c4c1f49b9ada464cab89988d3332842f736ead60074cd962f939c68d31c07c02bd7ba405a0fccd5b780cafad880efb1239654c8ad70a6788726814d5c713b7712e2a532008153e287c12211fe42da9a592030c15643241e62314b0acbade5b5e25afa119461b7dab121f8061f4d12c6fbc0565cb4dbaf55d75f15104414a5cad9b4f7ce8f81877c720075a966d252cf29c66e263500c20484ac826a80fab96badd5d8a6151815c704165d80411a9b4ae2448dcea7207a913b2f57494ce1c335390ac71d5e569093e72f27724fbe6062d395ecca1edb833ab36bb3ff5820f603e46c8709b696a38ad40a998dda145ab4700538c00ef0024d414ea7a6abe9c3dd30af0a01e8c8eae19d0694617acb5b122ff8e750c79ac2ac9a25da8a1766ab2c32a94989203e42b4830cda11126790e3323dfcc67ce0245344b27e31c20ccb35f7d35cf3b076c4b476efd44ee5f15df58eb5ea5c8c4777e94e23990506e567444de878576b72c9936b9313012f7942acfdd49a803fb8946ec4967420aa207c3e83317eb701b08e15dae713404f031d2c4dc076dd067bb0f23c15225a63ab7eef18137072d6a0023fa3061cea2355d29436c413cde6c22945aefb34ba8dbac456b7ce53143a629a8508c28a4429dbc45cd5e6d05916876367e553dafadcf1885367e01f7291f12cdfc3968e7d6b58c140ba03ce1b6dccc432698707d6182345aef5c5adac45f0d7ea3a9aa3f506d6a024e00e718c530b86da62e9434ef35f5bf9f8e83cc86e5e0329f506b81661196478e22f1df765666edb9200edd4fd2acd6033c5fab8769361e6e1a7904c1569cf14cdc629e1831278e50b76a3308b7f7b0688563124bc82cc9d36832aee202a4797c7d3526810de0da63617df35f4b57d33375ed21f988e8f7252621e3e8e6a3e852e41734f27eefd8c6c02f5765748655d3e5f23228057835c8c406450651fcd92afb425bba61fe04209a554206567f9546ff6b4b3ecb14cf2360a4480f3b0401914fb11300b90e0b58f6ff177f07da18b5c16394447a30e800a60e0ba156384dd97b7c66b5b44e72ef63528d9f65fcb76ca07444067084868aad3394c14e4345940ba8319e89878c18e02b5390bc6aa81687a0c45e039700d7243bc25343ab52ef7827535ded4d8276c03cf39d5a8605d16c30f844e30001f24f2126d21fc907e844d251d3b3a3986c3049419f1baa35975a42e3f810bd48ba14fc52f419ef571104bfa182b7d6f11622a4fe463397295cc574a77d12aa773dbb4f8329a6df13bfc36692a6b7fab7e7365fafbf79f4b5fc8b47eba5295f112b99809ee3c7b64feaddb86c3e8b35ac3f56668833467629d54d037dcb268696544b68d293e7709a9b067618bb220a3ee6de4a3e4028d131aa3e66693c8d04317e92a884b4c70bde512248aa87ac631c3c0988596c720528e4074d0f2b0f64470a3779c99f4f403709f739b15dc25ef2d7bcef74d98e024f3b3256875b4892644f08748889a714e8939cec067801a8e070132f046d4f046536b68953cd4819c2edc476e697b452b77577a17aea43206fd69067f516abf690416c9e75d1324778df8f2c75a0251fa008ab6fd8affe9d9d3dbd03890cef33dd43489e42b672b34a152a2a568ab6883b276e09bdbd05cb303af17cdbde2a6366647fd492f17850ad3d80c27afcd3c7f5a834ec83e0318d6fa3fc978962ca4955fefa1ffe7f1144e42276a77e5bf88e1026ed590ff37a686cf1b86e77275bbe64dfcf79a40fe383d623054b2c7fa248c235028292b619f8b28828a6f207da75faf950375853f9b32d3a3808c728fe4bb33b81c36c5391cebd16e560903ff10696339b5efd0450bfad90e3345c192046f120f356495c291fe267c74d7f7d52b33872e3ec3b7696f2a5287fa64872e21f45ccce23836efcc9ff031ae6732153499096dbaf975710e6061c77897345c5cb2620ea0398750d2130860f5c60819d52a09b1f78be8b7630c868b7367121bc52bd2686bc0ca49f03e0344d9da361aada36caccca43bca38e1db8488e5846728ccbc4f9c415fcfe6374f01b596fcd403f64466a54e8a5a52a765b819f87c2b55b3e86dd80097d49ff93ffe0fab1ee2fc7bd4b50328c26814780fb992b78ea4d6ae8b2c864100058c584c37fc2cd049afd85b28a0e64a9ad26e08167880194785c7289292888b6edad055ce91b4b2eda59eb858f7152345fcfbeb84e34ce0dc173ab75b9548718e1a1a0dddbe95592ca74247da243d0f933c30f90f0878dc21be3114ed50d302f625a9af2bb6e14d62212f124f67b5e891256b011d287b50d84a153695cec013c6b7554f30737b63a807d4923ae71c4c4090094d854a34ef6a1ec35e3eff1448406a4c5d28ec2205e53b0107384caf82ba396d8e852aa828a4c4afe2b0336d6adf5a4d1dcc2a28c88b198482c8ceabad8fe89260c64926faa33bc87c9020e900369590e374ba5bce58494673e9a5a2c32953eb2d4e58d705d5095bea59f539c4b75ea99f757c53da259a1a8a9d0411f83ce0020b517ab7b61c34e74d56aaabfbf754d29e1e1f201144ac2b5b345c3e4ed4f39d2a47b125d492fd6d0573c6e42e50d0500c0bb00a0c7533972a6a43e5011598ec0fb3120f155f4efd82388018325ed6ee5f450a5dd9cfb414b4e9a8104c6ec4e81c3f9e111eb6a6f9830cf94aad7a846cfe895d450627aa921860d342e35bc7f1fbefa2f40ff3387666b40f35cdc4cb3c16cd381bc75c12a3ef34ca3680b37fae4a869d07a6d5339fcb39ac1dc0d17e2b03ff383216700c2ff38f5483556f179ef9bdbe6fb610189406aff8a6ad4c880a3b13269822386313c23e08e55491c2f3aece16f82252ef52ddb5b35e148466d2b8ba78389024796d88170ed5f3328680a4cf6f99436ce626ce194fbeba12032c3e5c884c2db2358198cb12247a4dd7fb67d7a6b53fcc26f082510f1cb7bb9fcc7138e9c1df465eb7be388d29b50aea3f0891e6f99bae38a563e9312b551700289df6258bd538046c264c38e17697721607ee72032ee8e54c1a9ceaa2fadfce35407d4702d620ad7037041658c1992ebd86fe0d2443940a205f701cfc44b7ffafd1e22e7fad99e7b4e741632c663b083febd26d76c72ff757fc58e1b5e063e5763b8de034c233e6ddd353b720babeb1f16132e6ca02c81e21cd87785f1a68622bf67e75e3c1eb0cb6f1280c8332315c4189871ac43b00186acabf51ce912829384aca0c822b6104d7b7f566e1ccc19d1380ce1656fd11828695910191a04a85495d87591638b66047e987db4c7c217a1aff97ab17bc06d6ad08992e9b426e2a08df1f82089380e6ecf76c307c9ccdcaea8c7f2565a783b97ef3500962b877de0eb054d3f40b84934f9c6d7ccc1e2ac0fc4295b36386eb1273e6c5f543b170ec4894ee2f887c2359c0bfbfb8df68284693ec0a712ccca09178b13129eb024d9190d0cf47ed66867744345da20331f8746668b3d6df4be1b09f95e02516b1915cc1a7921d91bdc47969eec49100bfcc36aacd4e3f6d42e5b39a7f10e6816aaa82842bdbd85cc2e5d0c20764443910d3560f529b9b0871099377ef10754979b93c97041a1d6066a178a4d69e6add81f6e8e915d7d0c3a19bcbd5672945b1a0366304862732c0262db59d51038365cb1fd086542888c4972ecc8759472000428470b16ea3508b20f27921a855437a2d2f44461944186202bdeda8de39b948c6a808989ba43d3b15a04c9ab4926f13e95443d45614c51d5e7e3b024bf332c7c875ec3490067c171a3784ede565a2fdf46269a0b3c3ed2ac130afe0d825fa6ca0e6708b6edaf4c2fdedc3d5376e28f82a8a877ccd5e309aaab4379d73541f08f2030a4da0ca5698f64d814e0f1dca502c605f49c28722a579f15565026cb408d7b46612bf5399c65425a4da80be4f7a63bfe62a45a4b0c3031fc2cc419a8889db5f24fe85ea816ff87113621098003253e00444679b1db8579cc9240c89c5cd461a0e2bb9ff1c77a9f20345c06de8c0fdb4baa86fda894231dc9f12fb8e102394125ca99ff233c51b76d202ca99885be0496581f289ff0c255e3e8526f47abf6c7a87d2fab06f09008a31cb0a27411ee16d221a6ddf12ce1543fc81193770bcc0ac0411ed0f89331901eeee63da64532328b412f580b30f848ece912a65365a4ff4aa7ca0595b749084bc952da547f806350817b5026eeae0006e6a9e903ca1bcb1e2f6d91c6f905ad03b80d922e2413d067a7bb900c2be2f0d440ec6d5b53eb0cf49e3cc0eaa3ec23922e500c30e976e30e05c0fffb32fb734704e5a6b8090a9528f5ef9b0e2d0800ff1b55899cd82fa8a84091ec81384329148696cc99bb6cf5575af993d0e59270fbc10661d9de54ab96bbd559bfef1bbd1f63390fc8d29891893346278bc4d74acdca88469fb975670282cc53ae972bba622eee5bdcdd2f423b26970f02cbcc7e6d61507cbae14d3c1aaee71e6321014037c4a41349d72384d0ccc8ab846d44b5a3eeddaedd02dbe81462a24d936205d86789fd4c561c897f55ec45c6948cb9c7a5e0f540ad2d46136f53497912b2e0a626693c527a9b15ff6bf496c8f1001835c3e273ce94d0dc4710f9213714d1fd23c6f39ce042355557d0af770468a1e7e31c6df820980811560ebb100e37e5abf27cf7520d81b2b433b2860f2923238b38397c19663baf539ebaaf5b2df1cb95e4418d7a53a3bd17a926fc31b39d244794fc0d449d0956ab96950bbfcb7a7c5a5ddf80cbdee1f0f71841e8677c92df0ce03cd8cc05c46e216326d397edcdc21bef998ce5f19893e883a08851dbd95aa6d87dffaef7da4056fbf1115a39aad913ed44ea77c42b1e903e11424b4f9b93bafa8c059d379c68e907e775a44ecfa7060aed8ed3733462ba9498c9b079be7e9b0178a4447f70185547a4f9e93861a883cb1c263314899e4b61848c86e078a0d2bc06a29d832db1ec3c9d1426a9cbcba29d6ab04df23e161584d39817e792a0f4205f066e60910b18eb8c29ee3d4e9b0af1d95c218bb792813a478622705f4abcf0c49e189e7b8704ab3c120c7b8034c495d4ab1bf5eca07fb5c70866008d80c63b6bbdba1343f6f4fd2d811c927637180796456c80247c25b982b085b094ca8caa4ad62b0fce47e50349305765ebc2d682ef718b205553fbd3c34293423678114ac0b9b0a60cab22f51d5746aedace914c33be85ebce46a5fac4825885f3880b3b2af683539bff2c4b37ebc9368558fd099c59e09d1954ddb7f5dc44751e2e5c3c8f736d138eaf88549c414dbe4e626ec6d2487cda72409bb264067414d97b3ad71228b2ea84a5860ea595f66fbf13ab28d8e40156b16afb8b3dc1dfe877f16818ad1f8a18895537503aa8eb3848ddb33e35c807db4c10b6cc4927e1f0ea140b0c52a6412f8c146a5a7850ae6e0076f98fa8aa5031beca975f92a08bfdf799a4cd4bf922d0c026523c83b33659ea6475425f21ca5ddd3278633e057e64810b71e1cbad074484ef89554b0e5e556c39da21fa9eb561a24a42c76192cbde08a5bd5ebd02eab89e18fcfcbf45e90eb379af373f772610f7b49c6dc200715ffd5b23eef607aadb0fc0a60cdfc31bfcee251ea7ec904190c7f8492644d4e3806593eb8e20ae43f657dc381f41ee002a2f2572a5aca99d58d3c57896ff1e82a581a4ec5951ccdbd35439f9c19796824be0ac0c91bee12ed96c16f15b7c5bd6197570ee62963e3bcdcc69a978067236f5f8205f6ef73cb8e489b01f000e9c8b42eff11680de68ceeb83dc3b84e931e55cd221056f3c3a160ab456ab01eb9e7198c2c157d18f07e00b88dc8e425b2697ea45e3746c78dfe02e181664e48ae460297e87a26d17f3fb5ec1a47b8c184f519ee6da3fa34319bdeb9972dd3f4243d6b61ce0eed72d1394c422dce64bc299df90662e3c24d4df135b52c34aa41dffb17c43502f4e1fac803dbc3d8a1984631a06f6a274cb3814b98d0c8a7ff377401733592e86048b8a869e6a5c914d3f716c40d0d2721e8b892ced8d422d202162e6081421634a014214b6b758443054ac029b489b61a911acd2e84f6ef41dd88253a33d3c1b3f781589db0cc431d7ec37760d35261f07df1d4393be05e0ac3d53fc11e25ae2d9c08c3d292204052925172201563533fcfa215f604f80418f63da92b7951614d201d3ff3b04b7b6a0150eb13d76419478aa002013a83f1101c7cd2c839900744a5532e85cac6be3265174eaf4121f3a2710ce52d12ee48be1f62903a3f7fbce3ff92709ef5bfced4f915c56fc9d41a72d81233e977751a8f45ca03d7a91df28b82527e2cea5bbf682daf78e27698aaea1da710a1e3bd7148a151a50b81ef0d2ee3c39623650d291e6ce14ea64f49c8442e197174df9a70c34e92c02b5f15008aa4d22a2126349050368edb65206bbf15b3ac711bcf0455df0362e92750a1a4d5355156267e025804ba4f10c6d3a5d87a213a4befb169b4e610c8016ca9c912a8aa803c16b6a43678cf4367d4728775edb6efc91daf240911f93c6a0da6dc8f292551f181431048828dc4eb86c6c456ee1779593224caa4e7ed9d700a396673d55502857dee06027704acee6e32a8a7951d58d042003fc86504899f2c31d0542731995ee9a09f1920484acc0b312d000a8188d601d37e1e43785964616107d43200f57fc68f0ee82c8d31f8ef28f39791ff8362d09626923e036e1a74eac10bad62ed2f53abca6d32f54e45b86da4fe58d54b21d84c9826df5b5357b02e8c752af25d29c9ae7441e495d6290cd6c684a53aab28961728712f66b308e85a14101553483d4b2a8511d7271896905e7318c5298b1c20296bc761e14eecc76e8021d511c4d36043fd5bc373027ab8e8126d823cd47395d4a8060cb51fcc15bff717574691b4378cb219029a37e628625ecb1ff5be7644dfa192204343ccf7ceda091040d87bc232418a9c3809a8538bba9d5f6c5fef3fa5a0fc631841b15d6055cb804e5a9ed93925f74b680479a1cea6ae58da1d95f93ca14dd7af161afb9f986bdc04c6ab67d1472fa238e4b470b84598b93af5aaa2c7125e17f6a472c654f1ae1815854e28da98bafa9b8914de4e80c1fe821ffb83011ae3230941f421dfb05f8d851e6aad667bb06feb45cc0c47d5e4a838f27e5868368c7f3708a8b9d57b25a372539f3da43d5b11f8e71ed3920583cb84cc99a605c21d24831f62740afb4830145ec445996ab9eb1ddc7b361a63c7a07508c2d32ae870ce5c3da3baa5176c10b512a0c691bf395dd35ca3644fa0145f88e6530e03264de976b45ec95591639ac2c67fa46d582daeea4bbd71acf0ad93e6f8ef9c2bf4432f5620dac7dcc1000aaa5f409abf4b5635b69367a02bb977e817774273e549c3211666306281b64f1d88ad1999d5ba026fb60842761c7992fc85b4d4526ed19c3015f4f3cc1671e8ea54cb3d41b5e1b6aa93499ec227d16668c0829b8278065b180bba0904cb41137dad440a20a98a8cb3e1aef5f29337e972440e7a94d58b12c8b7ce0a5325d776adb9e4278916de933d67bfcef2ffc45a05ec758367f67e3cee28b092609e5d75dcd8f4b18f981d8ca061ba86696e786799e4e2f8490bbe5c241103f44f433651803851c6f8c0a70fd7f540011c77fae446d3b2ee52cb660a3193f04585570e2afa664132eaa804f8783a07e051018dc6708c60baaa0640a4034f196d4bfa6bbebc27b50e99c106ec24ef05c4815dc38209ce364d0a8808e61b1bfc4eb8911820a298ecfc47b40b5394c7302f1744034166482ce7201bf7b48f3fcea0ca8c0c50b9a2922d064392b0ec9309fc3d8d4dedfc620abe4f9eff4694b1c75a61895e7131fc32f5df3824bb9d0fb21ba6cfc4f03ba3127e508b441b70431b36db5205be68576f48618fd131b0ddb860f2fa244c12cde62fe46449d6917e21ff24110d203cc5e8e8a2a8a19c66299618947d63d75181e2cc257302ff3c0ae5e76baa94c2893e0b960189c01ad74ed273b7a27e64f4b9039e33fd0fe27072088da9c071057304a4fe77fc6ccebbe01c7dbe18bfda058e9874a94b0d5dc090e786c5c4927f9e95f0673772a15137b4ce5e2459870bf0672692ddf46ddb63a204e31d07648b8bc0f7aa1e33ab5ef79ac442e08409d893c58e6a5cbaa2847c85d02adc44c91098f409d3d30c126a1c1930aa691b50b0ac70b823fba749ff8fa6342efcfc5dcadaacb9c7abc1f4c7d83b7025b36090fe6670ef363d03b9943bd61b812d455316f6336b1b3379a2dc998378c2cf22397b7ab545fb924b2980dbe28b4a0ccb409ea5d9098d8ec4559dfb64a273c30eb6f172c7e51b36af6f3acca33238d1d1cf925b04a4386290df1940aa9f5e6f515d2889620c1799ff7dba19ee4e9bac35f2eade63d5d48127560b34257faaa251cc1ab6bf94d75c16ce72ef621de23bed81fc5a7cb87251edb843ddcfac2395dbcacbd0272a39993e151ff3c7d1c6fa05c03a2ca8c318c1edf205e5a5803bf4250806df48906692dd2c5e7aed27c262c67d6cae3230597d9c34024ba5cd95697345f33537e3aecf2491cd7001bd3c005810eb24c1f69d6e40a9087f8ac313536408e453be3c9019b7c92c71173559ce68c00d0d7f1c17a2b7dbbde3aea4869d2df33400edbea4cba0fa524bd08d8d4c51368d1d5a27da90645eeb877259d698e38431f780a5a03bb9e20732cbe03ada648d2bb8a6e19784c7591e05658e4da41273fc42f0be553d8bcb40c2430efa9c3c06c19488f013251604ad3778a9911012ddc66da37adb11fc2d74ba38bcb0d0084f1f023a12340bf38b088a68a85dac4c55795eb0f9ff4a9a00d0604c38380bd3c124ccc52fcf3e7070138575b2d0c1fae53f0d0418b9a3df1c701032288ce173c371496dd821c14b450d40590908e04e36800a6df3f55eef445128071a10504403b9798aa9fe218bf30b103a3ceb3630b676eaf7481f190d944e0184d7a19ac700adb67c426749834c0d44c020fdc7406faeb8078748691ef0ea1e9ddb75f8f9803619a1ca2a60bb515f997bfb90708d4e506026cf21431ac97bc86e0a1a65a768dc83d5fab9503f1386aa8e61ea00e6a707b102f083b401648469fdbd19cbbe6ce12c100623745acc54274d56621285f2a9b3db9456aaff738f6d0fe75aafe4bee1918b4afce61d9f307b40dacf0841f19538f8450b81dfcf9d9797ff7380172bfcd7254b97cf3c9d6a02e675a9a0b4995d97b847dcc468b785817469e5323633190ea64c52625f033d447cb32c91dd56011ba2c9f4a663874e16a4a4ad436c28fae819e8549c7654a079fc3ca995a1514428c2bfb78391d76b33c1e9228c5a7ba9c62cbb4e8c0b444b4d96ca188f4b8aba736a8ebbf09d45fd625bb6faf0d873eeee060bb6184dcae77e9595cf42c6c4876ccd967f5b2393f490330c90eb598c66f93716054094ffc5e34f4f51c18016de499742c395aec2d0023010d742a10b07fd1eeecd891eb66d29a0bbe5f28786abce83094180eedb624a8fce915bd1be809d0ebd8215e16bbea0199bc790e8f28625a544277767a22dca4a16cc3fe3d346417d92e40d2aca4ddadbf1ebeb9e60ec645a095de486a36dddfa66483535c42d3a6e784d358a58c9fa32eba2abb11e1b81189a4fa092a033435951c17cb577f26ac03a1900b14cbcca24fcf075f1317c0d4c8d0b112fa017d3c4ebf7a5c20743edb1e1771751ac75d69e36ad6de5cd74d087c04be71d3e4d214abf2e65bb7f56e36b047d09cb0723b8ef0a7f504e6340c49f5e1e0e512ad561ff2b68efc8d7b456cb190945ce739f3f9fe07837626401018e6da29b9807e7054eacf2b5d669bb25e5382fa10cb28226d0331985239fef9621534d521c3fe54f080cdb186790ffaf1e070cc95fd29c5a2512cd1fc06d570328925f27f5142b25ed4aa8cb5c1ebddfdff965a4f44bbca4fda705284028fbabc04e338729817d82a83b2f90b03eaa3fe2be57621a0a100c182b7647f7f7d2086d9f6c2fe8bd59ed396ea963e299e9d49792a9f73a18688b639a62adffd9728d778a197e2aed79a769227d7633a742871f784b48bbfe87bc8f29c339b654606999df3db3bacc3cc67c2bb25310a99d1c2a81c4fb3c8d934a4289781f0e76896acfde1c0461929b1a19ff760e253065cf74d00c9921e7ce4577109676fe25a64c2c9f1151239156b722a0233db3b6fb9f3554553ca86ae8fab787521436abdb474ca3bdfb921588e29b4280c969329833d889bc276f0f45aca3130c249c5cab77ce26891f2b6c1598155dcc5d1bb0bad861dc86ed535f281d88a1fb4e170e2e8ac328e7869d515d6d52d0ffdf4409a8dcd167dc0108ee9d4fda88cd51e817e1a7838b0104c70214e83f8169cd02a1e12c6a42956759ec47ebec762c1af4df42efd4756d8659799d6967a952eb246de1906c28f80b750fa44c38f2358a56edc331816ad4e3afdebf51199855c884c2a32637805628473c793f366edfef064e4c3d808409c025a7b11f443eea834bc22ba2350d1b9f9c7c6d2f3d02b26ef30098a154a40dc4cabb2cddfe5d6a1534085e0da3db8ae44bcfcabdc0fda745e5047f5d11e8b71dc1b64fae3aff5f40bf8d56ba6d854d149c43e4ea7c611ab754c664ead9fc1f31802923499f7a06edd8aa6aa480936562664c556df646e45d5f4ad0f034ff16df7904fbe8f082f161c4d800b804b365bb0bb62e719a2bb06965e857d08be3404140cf4b76ba093ddc50d1e3ccf32a1016519164e8000760b79fd0bba8dc4ca2d20c15026b04af4c3e6d16fe2107a78cb451d19f0341d4ff35006762d4eb8ba2151acacf43f66655e16f04de8643b596c13a23b2792bc636687afe7e115c6e92ca3a82be25fe7286929f90ff173ef959cd65d0700470c0f49718428e2a5fcf23a0cf5580ec28b19f708c7a46dcb7e624ee49f1108b9a2177b744468c447d92b787506a759ddf4f49d482af2340862d65fae4e75fd511d5e6fed89e4b5cde4f4289a07f9996fd90b883caeab2f90b31f04fa0117c5148c80ce22c360e8af213fbb25cd309e61e1f3a4f8c5c6f89585a06f81885f1acdf858a8d4660741853cc59b201a07935942aebd336bb7e1b4eace4914635abab0a83e61b38a6e1e275c4f155d973e6283d1df03cc95e3630fbaf96d9c28cfd5529eba442675fdfd417b50c4d0cdc970d934914c487fceecf79e6ee465eac33305267b89e4419eb641c7880f804284da67146fd3996e5deca09ebc6449c52b3cf847ba1777573953367a2b7ba4050ebd02b6c3a2fcd4dbec749cf542e43baece4c5a075a393c4d5a27fc9cca1d0b5baeb2d3d67d3596e446cc64aa7fe7539d3378f64a75bcc9e8e111a1cf94293ea97b9d9ca8c161ceac8435e019b3939a1cb1c72f747c99ca61caf18d28edc06f0c93eb74d7636f4b42518ea821b12c882e129c96d55d4b2efc5dd363d2478675772dbaf94306ff6f6e1667fc57ef25d4c036a06e43b033b1bdcec073de9ba52ca6c9835a9c006ca3665b43d23414f24dd4216a9831810c7d17332470eaab8fd4973190933a48f0a5586863ad9db8ead4bc4ca7b3194612653f3acdb75e30999696cd3da132f83970265222edefb2f233359ea2917053c29d06f9eea7ea8c7223f29328b2e2d2fe29f1b401223fb573013c1a48b837498f790d1910955fde648d661cb1c56760762334f4d87b1891b913eee4a27f62061a5b3a9280babaaec7dd554ba640d2e5d46009e85df4fd1592e60d4b51987dcd8bf14e6f3a42e8a116ec95b9389c9642500a380c484b3f5b2f906ddcdf43d5629e9bc6e86700f415b2411fbaf8ebf409cf2356a109f79eefd105dcb1acb21f752a46e260eaa6ed42964cd34c8f05b3686034379ac958d38a376ddd96f41373fc5126f6f3d0269d7f4fb5e3201945722fe23b4fc587e7db33cfff9f73cb68d5912374ce86c12ce3c0f8700f8a880d132b42b804440f3a11a129bf16815f951923d0f7567d519be8473527fc16fa5b4ff41df7159c5804c4ef7df74d00f403665197aa7e8290b6a587c883f057bbfa37bdcb0722bc44d796d5bda4f0a6f33849ef4a6faf580a4a0fd2bbcde75102f7891a1f09a61fb112f3a86211447e8a3bbde353b41a8bdd84438c0055f273f9c3d1974034601083ab7f6369c1e795a6dc0887dfad16f891d9a46d831500925731881ddb058f76dc7e2241d26731a348cb6a3e28e582592c35247000c01afc140584b2f839426bafdbc0c46e05796dc2d48698025b4d6c11aa706400db06768d9366ff8307428e45c40ada0dcfa3a6e487ae4c60801afc2bf161267bcd327c57860499020e9e4a3e963788c50f36fe50d058a87c34a00ed5e6556969293742ccbff40aae3605ae82ae600b5c53b5252573ffaa641a4d62a315c8ea02a49863d8afff288bb800cba9fff792d5df716285f03942b9c5b1df916651555ec34c15fb727136abc603213aeaf63a5b5aedb5953f526a3a49cebfff0813b8d172ad7d4e31e16ee9bcfe3652d82bf57b0afd075b8ebbee5f6f94e5f880ec7a27e08f802993d60b3695aee70fcf639a4d6a6177f9f0b0fee68f65965367c9e336f98ca4c97097385bcabd7c93d9ed9e1f0ad96c54cdb55a52e3549fe82ee69d4c88b51b0bb236f3fd5fdaef1bdbe007be280081183df237f8c044d3692df706b931cd647e4295c3e122849960e2efb50ca6183028a8ab48741be3f7f2aa60085dc3496069dd3f97dfcdbbdff9107a9c795b1cc34c4948e3423ca48f4d430fe4c39ad451972b16571b86875b2190264fddae84427aaf9c297c02e19ef2e505060193bfe0c94676ad3e262b31106a3150bcfcd24291afd9ba2274525e4aeb81a26843b7f1838f0a844b086f94053b6882f82a18eb3845cfbde27f2234f2a572bad7922f6893368744f05fedc2d65841bb9ca0f6f99cfbe0347ae8b2ddd97a2a4b508d82c929ca06c28b3f275ad8e1f99c85f79201803310bd863cd521cd701708231e17742ea24f30c10245b4b5254df8406edbb5d810351fdf84229e91c3872081d1824626c85890b781641d57f172e4149a2d0e001a23e4866b03e2640180fc2a695cfdcfb2db3c85ae63d68dc92c54797f06e18a437fd12bb08c598796061bc6f171bcff279808be54de43be1bb585ad3c6a67e681ad919390df075cef1918e87cf348578b29562a674eda21bbb362856dbfa7f6256301a862237ffdbacde0d95a3ae33673a6de215e4f8915e49de02fdf88f6155469a2728cf9957586074550cf002ced50d6e4a189ec1aea447096bd0563d7845c005151f2b466fd949c44eb90302724b99c6759670775ba7cdd3ec15e1f8217a692535b42151ffd0538246ad11baa9eb65ecc97b52fe1b862ccb07070dfb2767aacce74070cdc38c97edd58026bab6601e58164c73fd37e5f6a5002645a2dfb0851a5ad6728a50c4678ccd7409d26621b64215f5aa38641449009101bb1b7adb96bec496e1f8fa510e853b442844964473c3ef1514e84c1521739c9fa67c0d384b2d7fd1457fd02d56606f58079de2e6c57cb6fe23148f656e5fb0daca51387d17452f10424ce8f5b6a6e4e25076d80b837db50220f67738312eaec9bc77be471874af78423f6c64b284ba3e172f21d2161426b0e5b1e38ea244718d2e048b5c181410535e7a2a8b4010fc92a9fd2203506cac73c8a1b7792020d7205cb3ad08c116ae64ed11e1fc95884a91ec7af580e90d22de45703d5e5f9dd794566dcc136abea204497eb77f8a0b84aa6dcc3d380236a0fc9a0281d222639d0c5f2412feaef7ae49417330629a6a5438333ae3270d56d5144cdaee3037b246f6d919bbbb66862d4fae151cbb84a0820daa6a07d11965c0a568ec6fb4de53ac11c88b99a8a4404242a10484a12f63843160d7904f3836367ccc583e4d1a92acd5b3412846eb3c0910221365b6e1a88390206e59661e18f5edbaeb820f4e4b95c684d42f418d37adb6504eaae98961f77520fca10bf2598fd69ae1c82d0e735eaecc24f33464a95638d751d4711ce36d3787f6ae925e7e3cae7c268c6e434bfe48b222403059682b92f79f36d83360abf6039516d2055c7a15a993e084e255955a05f83185ee295e92281639a63c9a9fbb7c0dccf732a34ee78ec9d24000c7dd0f5ab1a02d79c324195443468f54755547c2493c232a8357976d3f0cc4af4fadd69282b2c6fb5aad67d9920e09424bbc66c5efde181e80cbfd27394eb3a88db93aead9b5c4a89b03d4be124e78546ac4809776f09441721580516c99cff3f7bac84f0e8bead3ff0363869a291d56fd71fdb10231340ccefc15842620d826ec805e62e8d6a5fc977175cabb78fb60a74ddd27f595b3627d77e4687da60b4ad0e342cd70984bbaf1d50547788627b9e6f6b8596818cdf20a2df70a177b8106bb30d136bf16ea4059cbf6473a687bf41421ef292b4e643526a426c0bc984ee6407c57fa58abfa46757821f08a5e9e09e081336ef99d9d69662bc26e54c2082cc21e1672f2924e412a0e3d6b733c1d3e4e70d7a40e46b72a7f284497a330c002478cae2a2ed27a2e2b2d04263c061cb280b7a78737f521429fe408be040775097e3493eb2e106d14ccbde8da700d0d85ea9138e39a0af42773264a403c2cfbf7b09318446adc83b03b616935956ca1c6bd5e7044ed07102eb29ca27022deff239ca518480477ce40b65c55417443a263c1d15d293f5d1b3edc1229c92389fc71b3a48db4a869e8364e03d1eb7c354470cf005c642cbed53491ac322c670fdce1f497da8211a06d4a1c82d9a863910f8011a951800d4664c7c279f497ebb572ae75709dd14df06c43b2a548034c92626ec5772182cf4c80d60816446a9d0814cd7973cb50e486c07f98b281491001135a00d021b5faf5742888daa9aeccfc6af9933f5e8838277ad53228e11c593018c3b08c5443902a87a62c02320c94504d6420d51f27366e456fed3fe947bbd46bb72f826db9b33dd873046546905e6ea66a6ce2da9a00fc25510faf2fa6fae4892ab985f83cd0a46add4547de02c4c6632b4fa7ee597f45b58502ceff68f0d7cce04a5d7936f7b3fbc6ad13428d60de3c41d51861f145beb3d7a8156ffa718959115388c5bc8e93bab6847cc6ad346328c1a9097ecf8db6aec8f78a5ec190cb5cde3c11fd7e06aaa9d7a16adc686ab62c211e288eed8949890922602fab0753ccf90f3ba2ca93a59ac9400e2d66d5676c284d798fe796a30b6e0fc3bb6f55acca958c78a2dd5801c65e26f881d22719cd347bcf509fdc138031c8a8a2c54f1a45823934505878c6f659a2f5cbd31665729b9ef03206448679dcdcb7d9fdecb24a840299fd1dc13f6ae6aea20c2d17c7ae3044192b1462a1ea062f09b81cec2344835a495654bf3216854655df87c2ff3e71f9b8b2986b06d4761f17ccc20bc6f851bf31a8b031440fa10526601c8da84eac80af1a839caec0941fdecf24c42bd0c1cc726abfcab42e58380e844c3554a1bfc5892dbf3efada3304d4502e828e65fbd0fe9cf6f7b3a66065420bc5166593a664adc72ab0cead1eead7a1e03b4d266ae6d62926ec06427173c3593c081980a566401ef092ef8f7019c3f4dc1b077b082b15c2f18a704782d5415560ade48a62c2fdd51b0314516b0152794feec6f6fda89b963a19da63a637c9d567baade7a23cc3dbd04ef04c8d71ab7238cc5c172978b948b05ecbdc74a57b9818cd9bf29ac0187e009dd0dbbc8d5a85653fab37fdf515931d4b97d4f3d1b1260569871825e08c612e2763942141ba4cbb5f40f6e9f30900035129028a7777f9ff8266bc0b57f00b5a31936fe1d0238833cacf169f0a7ee99a0e4aa8506a234ddc5003ad1ea89bd1ef6edcc544416ffcbc81c3a72e3901242b290d3f8bd58edeb293326cc5f866e887bb17a22deabec2e3a2eaa74c4285728488746ef4fa3efc87b4c7ee5d640f5f407ba896a1a41d72491bbffb5f3d0bde2fafd9d2c2801648f5c616161b444e971890e44b160b134578189bd0855cb3849f1391b2e23a22918994ea5302cd30e9d9c58c203f7d78796b26cf911e577d236476b36f9819522a83cb33ef40cfabd103f4b8852fc525e179032fe60e853b01005258dc240459731c96fb94c4b4e6a40ac95284cd0801ab03160e1aecda3fb744bc23e20575de3efbbc831b374ab77411f089f698950c41bb18d499d10038da82c30e27f6e9155c8f4f2cf073dc9829fa4b6f7c91371d052fbf7ad7af899bd9e011f8c195cc7888d0456cd102225fa7a84ccf7a61af256a3fff23caf49584b852f38d43be7df1437b788590dbab6b7215940c1bc3285fe9ea250dc92f113843c37c29848a6d808b170368bbdd30ac805c5c139cdbe2cc566e58a69941816bc85c399248a29567ff816055a7b079188866b876eb82087898043968662172dfc8fc2d927c0c341dd4f1d556a427efa3f9beff0c409e3d8dc059e1571e408e5d8faba91be22e7f03977a2e145f79730f207611c1ce7535ee1c12b6985cbceabbb6160ffc0fce719e6597cab9a06ae41d1060a606ef513996ef18d574909f0573e1829c658e55f4108357097d1150439ee503ec7e9ffb7e9f6a351ac3ff90d2fd405977e0611ce706753c44bfcc0806bda7a0927772d068a3f47d6a7b45f49c54cf019f2f0d2b7b0d4954eef1be400690b1727f5709c5a674233715bb07a821703b232bd40735b0278080462405e6157b09b223f200bd12f29baa961e860d511b4a6f1558876d6bf1d640c5f09adeca19f25d74043e51d5dbe8fc3c432f5152438bea59997234bf1f4b2ef634c61b8b46f915487f375a0a4328f5fdd3c784003a715d7e8957c0b7c20528b4bdf08e806eba51202d47d5fc893c89c018d076f55383148ce936804610485b772db88b9c117185bbede264dc95fdb0f424610762c748657e9eba5d0aa5144d0cf790d9bb8f3fde03f1c15768e0469057d05f9d183529f1d40175f3f7bc2e4cbb934c123ae4934995a3447884cd28fb62079809df3ad5e41319ad8939f7ef3131520f2fc26a79b394d42dfa9cbce4ac758300e30a88103c284fa71f0683580ee9df05459364827f66d88619a58f59cca59355b9884a07ae6a3a08c9695b8bd52cd447d0bf0c11d10e698b565fee08199f5830991efafebed4b3684f382cdc2e673638be9b41b5d31934d9531cf4de62eb5d81571b5e15ea0136e078395d9ff207bd785cad54fcad54e1110a3b6f1e10f3b158f897f2632e136b4451e6271602b8d7b40fe12159b4b11abd89f727a1543d1587aa7af822e82e79b451bfa2cc3d58c2b29f984b1480a1b5ea2f37b9cd16131a589cd7f734320c0ccbcee23ebca7a7545f88615eb5a5f1c2a16ecd93ab69903c7d5e419d16a6c6040b1687df645df4914e03f08a64611d2198c6d213c5553e9911776ebc183605b86fcccbba06810ecdc7312893716358c733ad3d8c29a52bced5f9c7b84d17efe610776d49dc7f93ae05f3997235f7f8e8199a86ed5e4d83bb3114baa9f5f5ef775d36a96b8774c6c47c73f02ee9a3a8175951daf2982aacbe520a382ce976a25d35b987a8c13b23aede55cbf039571f96abc797c99ba4098d0db4f16b3bf16df256652116088ece3eb3d370b15e3ecdcaa7a233f843ede2b678b43ac646997f588c3fd0ee05a6311be3b56fc40265be22b9907f50180c48a9b4ae425c20031ac90774babe30443b95e54ee8d0ecd1966cd3296eba0513ae38d95e0eb7901abdbffd050c4f39a5e040d0982d4726a344dc163f6d52668d5239e48e5563703de72c59873bedce5a452f3898bca27041b4c5091f1bcfea65622cf70f978b0d7068d2005cbbabefce420d96c388074d905c986cf04a638a71c9e1d5ca550aadf852470dedca8b0700d07e70384e472f9e3b022c1250d02b62883c453ca15f1135f99dc4a9b59a313f7bcff14229f4de222263f6f6f75d945768ebce7f7700d5b9fd7974d54c0e129b9d55ac4710418450d468d2064486d4b29eb151b452ab2d0307ed427dc512fa68e8bf2fb425dd4beefb1b6ad0263137c8a0285964d69d429b1f2903648826be0bbe09fd0c1e6a277b384a4aa805cb2c202895c0d96b91969fca69710781d29dbae9052c1172e8e26f88bc4a8460186306dbaafda0f6dda11a1bfa2dee8d24bb13d30ca9d03c1762c9b32b7c40ea898d41223bb43a7fc083c499571a579b9aa97c0948e299322323c9d605e4eefd44f07f5d65eb889a5bb6fc742df62c6334d56a5de6c8b2c67d7fd2b699b1a444b51080bab93f6814d755985a57094412bc6a545cd8683b119177941d161bba84ee3bf3326c2a42beecd8669dd2126533673e098deab96394eca6b8f984c45cc6ba3515f26d9cce6ef1c2490056dd4083ab68341d790048ad1c97102b985dacbc95195f1ebc93bd388bbcff4408b6696f51064cbe9e82a23332dbf8eb9c4a27aee4ec724d87a25545fe877f5504a0237dbd9298aec721241e6d486d228c4690822622bb39d95dc934b4cb6f4de05eb00b9d2088d82aea561c8ee0479c6dab7ea953c7fa9a9a190a04a2ee7b3bc8b41e232092ab21a39b67d5e2ddac59001cb196085f6005deeb7074938d48f40d3bafaef8eff2a46be875bd89e820eeb0eb25dc5459029ca15fad641002c967bde0afd5202686c50423bd239f98194ba69a1de34ae9eab042566cdef38f8be8058685a8b2c3457e6b15672c4b90c537615d4f27817aae374e5c245dbb6de6bbc6e61b03bfbabf30ac6a0236c4b76739d1ea709e2a62b2883ddc26aa7b0797e0dc026b96f8d4e14892dec95de27a0edb93239d18ab4ee7100e028a503a97fc0228df24148c827a8beee9708338d4635e746310981d8f2f563f82c92a9e208d42b0c77d4cdea8b31cb1091f533fd224979db31bc186e566d4f1847b44d6d42263ef4bfcf91737c0c3f54ee5dcc6aa456548b5f39b817120013b9dedf08a588a00a3d6320317e8685d54866fa6f63e2e73c6aa46b4ed45c79e3e9da9c5bb789987357f0dc62e8686e62852be734888ecff0be60fd2956b1c45a6c633196d8c656ac6289cd58c5526cb18ca5d8c52eb6b18bad5862195bb18a8dfffdaffd53ddfefcf77f41fbf15d8b57f311b78e48ccd40cf62a3754a136f9e06a37b45db9eabf08d7e624f46f983c0240aaccd36c318285ea2c3dd1c2518d9b1cfe447cb9c7cadd18ac47a1b21e9cf7b424f6e6a5db6d89aa67953348a3352367f32f60eaecad1451860a85e05985730c3b3c260f1e6cde05a3e38e50a76d5569529c1ba035de710f5068ee9c89f65e0d8d0612a7bf7afd907fe2f8e7edf16197dfa35e26b32e896bbb435580e438be3b0d598abd15366c175dd9cffc62124822bde21592a9a663fa96395143c8ade2edb8954489134bcc081f0fa196354829eae48969ef639191528c6a41d0ebb80744ad306aab507d2e3dd3b5cc111b42ee6b6f458d248ab85862b65a1f00b59480570d83a276d7cc412ff96aa3d0b5926fde0476736dd1454ec6cb4aabb4516d0b6be6a4fe0fa8bf9e0de580c902fbc47d96f4cabbb90f9ce45a0b9fd374c09ab58d42637e2f1d0f1bc061c85b6f7d7d307bd36a19075015dc32601f0d45dc2e1b8a0111c16f8dd57dddc6119aebd39be42b30d09311a4e52d7e5acbef475e1e40b4bc27c2c0c4b2535ca40e41951f373660c011893fc8299920e45c5faf0879a0b78b7c195600e548a98a810aa416237088a1c1b2754597961b97f9acddf1ea6f35d13fffff7303b6ce2b7c6a55623383c6807202bd5335e465862ade10b67238a28d8b4f0af112388c85c40f90319b71e7ce76e2d4e027c3afc455512817c992329a45402a95941e0742f4b1197a6767607f5fc30c368cca1f57685597fac89ff345818d6bff33f70b3ffb7b5e71eafe7a9b127cf7fe8fb820c46ccf44b999897e4383adb7ac0117890ead20416f623163ca1ade2d481266306e6b4830b9e6f7376a14f74199f578eb4e327b8c3fa478be46f97383baca4645fb80b9e382cf29df570037c747ca6f0e0a02d711f31db0ace08d359bad41c5bcdd7b36af24c27123deb7afcc9c716b97b119cc1ac084efd428b0c1571352b088632c64eb2a4d0956d8f178f97054415132c7c62eccb7a961258d584d5451ff31698a8bd551c3cb9eea95afb0c77f582a803bbbce581b42fa8429482e821c17778aa29757a82adf64ff579e4a758e928d81a20f68c8c19515290c73f440fc263895a090ba2c0ca24aee182f05e52641d39a40132c8cf09a3527cb817aabcbf88d506078d98cbc96eb82f411adebcaf81f271d52f7b6f542529e453b76819ef0f8c80e0940e0802b3e861d28dd39656e8bee68ce5d8c82676ebb8cb8c1e4b29e1a43ec5ed80cda90f53f838ca3e98d167c686e17225090fee818f4eb9731137af1e9ef789710845d68a24487049c94611f0c3af72dc16280cd24fcd5dbde0268ea2d4d0e4c10a5f3662cbb05a13085285c308279207caeea59b4d6fd5fee98c3e74d1cde4e416c32994b628095f088f0a2aabb2498b065c53d9bbed54db13d2887b9c306efcb996243d19d50d7d2fc7f896774d15154fee0e7adb1158fe260292f60194fa88410e0dad7963621f22b13bfd076778724101962246e9095c1b13a9b9de51f011714b66194473ede2e5c1f8f975981db21285fd6912a665dfa7b727d515550fd0f019058b0b4200ace7b2c0f0ae225d722058967fb4110a0661c63bd71f0bbf1374b8b4b100532d0e1166f2d4f33dea00556754ad71f6a0ce4baf4c901a14e174cf002e99c1ba364d5a7e5ac5c5eee23f6e657a979e4510f1c8158fc806e8a99e53e37cc9d7592bff835869d389a268ed98f9aeaae19e5b57a78b5c0db7867152df3f84363834b0a1def7f30a1cf1f2abc11a2425a8d0f14acc08bf794ff74c9fbdf4be7be2e38c8636254d929d910050cd546f05f767aa1d5883b504b4082dd3414d9f694c992d381bf643bae02733e3725471ed4656c775abd2cc76b1e24d92d1900f0a16316b401a110d0f438fb786ee071f9cf0db523f3124a3bcf834b02a4d29d51085f36deb854e63f5aa93a112383ce0387a81641039a58a810b91db338123b82f430e572bf2b1e89f4b2c4b1b6494329d24b28027e38e612cc79b6e81dc42de0b3763ebb801c3accf8c88ab7a96e9e858b5aecaff6ad8611a416a06c1bfda61b6ef5b7589515476a17433304942a2a37ebd0b284602598bb2633bf8d303c06fceb2fb0b8585a03bc7b88628462c1619cc326b170dcc76dfb347c28833ec329c5401bca53f99578e6e115418319f2f689013998dd9a4ea78f752193cd0b4cdb26a1b4eab14b45b517f2498ad83fa184d4cfbf1ed18b372139233b07b821e4d1eeb3d202e9bfde177ca63e1683dabb27263dd58fa79b69abef3c8c13484378410d10966bc5b98c98cfccac6b948a6150bb749fe3cda2ea40255cf207dd020af7f8fbe16f1a05cc5a89e50a000df584a9a610c214b35cac52dff972104821306e62bc4172c2d56c21112dac4ff55efc525ea140f27c12381af7f134b2005803f428b0c32bba55f39e6d6ccf055242fa5632379bf47eaf5b075c5e57784e1fbd3fd7d898d551ac220cb85d4d8d3b48d612757b6b2346a5a7216e25a46e14019ca01e37238e3a31459b271502153a9416cbe387fd8826a4adfbd738dd3c2c356a2947656325df3a349f984ed20c72a4340d44b66a57a2b20d93c3320472f9a45431e921098a958a288e4e04016d10dcf6dcd8582040bbce8d87a757569b061d54f7ff6131ab81e15599f8a23471f09fbabd67c42e9ece680c41b59971619eab318f06ac67e6f091b62c897dfb934f550d080b71ac3c89b84e137739b9fc388942f7ed39732de7c0a47ba9e3cc6dd418191a3f79aa890433fa4d070ab4bfd4d5bd1b49b1c6ce63a6e850e0e8fd62238173b03416b439e05b1711d35e7a3b31449921b023ee833702378470e362e01b71ffb5c2d47829360640458c08cd49e1389476b7326dfe84cd78db427727b76e7f3fbdc7282e0f6cea615cff136f98921f49fd43157f07c2cb4e182fed0c4e4b554f47e5d10ef43430d18726f767114e5ab3569efb1fe9374e165d2978b9a01427f0247d293253dfefcd9384de45c9bc4e6ea568cc9c5cd358c7234fea4a96de607cd393b87912b66c42540eb0f93126b042c0fbf62fc08d6b3079e9c4bcb74874a0783f68c6114bad9cab60761564c3b3545c5ab91f704370237463f006506edc01bb49fe9968bafeed45110f33bb4b23286e8f9cba60fa95ed6d0ba1a9236d433b3c3154a167bd38b36be82a10ed1168a71fcf2771696b91776ec2319a095a3a96e5e40950e34b49abb55bba76fd5957e3570270e97166d76de329b005ce41247a5d7091878a25900c06306f2e033425c35c03ef96fc52b68d28cb84f66976d2553bce7110f0b114f6b1d2aad7f4c4c3efb15e91207f92c4bd434ceee8d87bc846c48b77ad4b5a462e984bbc408c586c28ea437287376929a1efa51ce7d3d6c743f1a15529e8b7da9448e4d5819358892700cde802517bafc064917723f13ad291b39f3de0dd47dba4034e12dd170c2c1114b7097ed2b7ef2368d4d336f7aa4aeb9ae3f0f03ad1b812484a871dcbb27b534e5960e8f953c9ef78f00ade1ce0476d1972f53b13b76016a6980029fdacd5ec4a66542ae4ac57456ed7751e6e5083d2a8dc851039e2a461e3752c78cad50ae335932c70cd6127d9f03e5d926a9bb73437c29ad7b16eecb4b0ef4b101c19775ce6a858f06964d51c026c3924b9715fc53e41cdd7992f9373d30dc437ca1e0a0789412c579bb4fb5607e4e7bf6912f75f59fd5102aeb626dc0bf3f005cc7eec71e728c7de93b4b459c242e1b8e9285852a8162f69304fbe91837867e8434eb7bea66b7a89e6ce39ba03f9ab700a89ac100e95b11630485652f4be58efd7df8e820fc04bcb2dc259f872d7fa58ab2c30371e99471442ec303b3f1ee74bdd2cfe996f28b5697fef2ef77b86ba230b0c424becf4cd155055603736bea845336ca73e38d27881e3ca2c3133c9a6fc5c41d3d10fc7e275c708a59ee5a565fc2600d8648724bd33b969e8a053ac7117fbfd295128d0ae4b4b15f6e203499930feb9b572b37c926abef594e66ea80c41120639f090af4da637d210267a40f0eb63e28d21a1a9b395f5d5f8d140b5764fcfd007304f20d655fb7f64040c2b3704f0177e91d22ee060c6d9601b0ad6ab0a8f7e54df95ca07e55cad2bc07a55b924f87413887672bc1b198c2060151e5ef2bcb9a830e47f0bbadaa1e40ac3884be6eaafe3b8af24c7e96b90b98810c242433b5ee17b6b85f9e3ac62af78de77575f2ee4bdd0a4d26e56901db84952ac3c1011d2ec0ece2ab73265e46b4e0c5edc1f7a0497957134336e6736ec6ab424971a15c9c93189f94ba7be568d8cfda8112a5505538364fba101278e7b034c03369550f7d0cb9e45113cf9566ecb3fde71223f38255bfdf641b62f36750b1adf054e985e6591b0f8c162210e241456ac23c8894fccc43dddcf2ccf6909642cdfda3663beab060cd3ca351a536e783d5a2433f66527c0d8c479a06c4516caf2396660d1d1ec4fdabe5afc6c6ba72f0bfdbedbb76ecef25e96c4ea2309ab7a834b1a477c3bf71ef15b4951e122db1dc4e675dd0392f3d724a239a39d039d5a382c8cadc92c7445aff78f8b67b889b755d8b179157d4df078e41b769c47190ad4db82fafb171c109ed71cbcdef48dc6994fffb8d3239f03f4440234d0035a07e39d5b30561d710e69a0fd5a4fe380229cc0977768965e18852881eff4a000dbabe43ed8e5fa30ca403691c27d3082ffad6ae7bd92a3f9740de623c7e903a915155485f4172fd1de7a18a08f9bb16fffdbcfda77e993d0d2922da59449ca900aba098509369bcd368e2d961071884389b2b269e2c771400234445484080f0a45a6522ad56a457b924ce95a45a2521959ad860005d1fe8e18fb8454467425b4da84955b10908f102150b75464c88d112f802455aa95ae957ec56209f1afe4bbbd23f9200e9188994a133e4384040d7184109428f8a708cd49d779df078261a83dd13d5d13752dbca1d6b5590e47ba16532a954a25140a85e281daa13aa16837cc3412a96432b59c74edff8442919e58fa92ae89ba860aad2667aa18d24875025b30d82c8634529daa378ba9411ad1a0b972ce61d775b318d2c80b3b4d7beffb401004c330d45acf62682f8e46e3389248a55289b424e99592356ad4a8417bd3e98442a1c8542a45f2f0f0f0f0f0dcb8c1c383e3c5eb7baa12217cd624052c961d1e6270cc38120017b0175290518fa017560831c411423ffc0060082bd5cdfed83835861620b6d8ac879be9430568c860d80193830e128fe746a9643a9d50a877adaa1093e984423d49ce4a93ae8d2f2e249952a95eab55e9224b2c564bd76a904bd74aef027abd60309f9f9fd2c6e7e75d2bb717675d6bf9ec8dcfc2327ef9e2e234fc63b9f3e232a3060d9144228de3972f2ea4902c85a4c9743aa1308a2453a971fcb2468d1a35be30d456d35e1c8d46e3cececece38eae8e8e8e8e8e8e8e8e8e8e8340902f48a312a67b60afcd0c4edc74f9092204260fa28c120793c0cd1d19283c5adc78605478c0eeb53d4b47654ec9801abd1a2070d1e590e60ec756dfc374ddacb55b2788cf1a2b96c25cc715de78d5efd3e10a4bd5cdf799fae8dff8561a8add65ea9a6d162c48811c3478f9d9d9d1d1eccb3338eb8495dc2c8ea35fb09d29143089a128b0b0e529123416e85454b0a54000493c5e879b1b1669830746ec468c4ecf0782e5da3bdeba56b28182ca66bb48ffd934662252023b0d66853d616d9cf5dbd4bbc4750b4590c2ef2628d48828c4ca149d022588bc57c7109758df6a10eb528eada0c357ab17cca250bf8181a490b5d890721c410479486b45a86407f7ccad5b3984558318060361c6c66bc784ca6130a4592295d6bf9d47d213e3e3647ec2d1224fefcc0708831a96be593b194aebd4ad764af2a7f1563e95acbb35ab6d5f24a5b2d4fbc3f968ae09f170c09901157cb87b512853cec65b3d96c369bcd66b3d96c3756b2bc982d4248a248092b41acd50f12232f73c1e1486988e7744291642aa552adec6ae595ae5648622e1b14ca88a952ad740df62b9627e220244882ca179298cb46bca531fff80809f548879dfec46ee323402c7305a30931c411422e4fc29fb8d91f5e64a4f5414362a805984cf84fba5642a14812a774adf5a9abb22a9557aa527922f9a3909185a40aa1b956370624a260364738b2092b3f5a4e13f24250a278125ee44a5881d9945e2312d70f50a9643a9d502852d7723cc922e589fea69a0b195a9d5ae9d8d1e3038542a150281a8d86a2d160baf67a584cd758af6b29fecbb2ac7953d7666fb278621502d3355a2bb65ab578bceb9a8a776b752df6f6de0b335d2e988f520bd305c3489121438ce89015c9c1a2055596a6aebd7a9e85a545a66bff321a9792a96be2832c61d5e2bf30c6586cd8a26be2bb84578bdfc35b965998f50dc74cc868bec060e6f83e8c5e60e39364356529ab297900b55a4027242d569e0e1815058ad5bba06bb114ba5657f0296254e81acbab589d1e000058e189e3bb9022aca50b005684174511305c588459c3f4a0b977765f60c18ed9fd17c6159e8e971ad37c0980113e6acc9e2324c9a3a5857c1e2de457c008cbf57ab15404915caf143ce028a14a65bd28195d5bbdcccc0b5debf9172e18306874ad7c9a1a5b53e395d6d4c8e81af994d6eaae6be3bbb5bad67a0bc38505c991a07aa4452ca8e5c95558cb4aa36bb4a7a9d13554ca448b11c3c91d3a7cf488751c51344ceed0e1a3c7c58a01da779ef77dba86fffbc2300c533695f24a539de824b943878f1e71dcd9d9d9d9d161a869af457134d235fca351d7683f9248249665b1bc5256273a49eed0510a499af625d2643a85275dc37f2251ba467b14499224f9b2af9757faea4427c954486adaa754a16ab5d235fc2b96aed19ed56ab59c245ba14bd3be0573e91aeac1d84bd7f0bf60652ce624f9e3a74c376e504ca3a168281acdbbbc0a3a891fe732234a0f1d2a4b3031dbf1820e2f0c015d21c4103e3bc2672e042588911e802451a4849527644a7bbbc0cbb533040f56e8c70bd80d341610236605b612461130b21a2c5841e325071f2e6020f2834f0658a4a0142ef0d4e81aed6b62e81aed63a81e06187c2463663b789c3c2f955034ac22c798198d1a30ece09183a3c293a63d8a2453614ad7f0a752a14ad768af5aad56a52d4bafb41c4732665662852b4d7b56abe572e91a7ed7eb5561305827fa8f64cc0c0618608081f631f2bf2c4bd23449161616f2c5bebc78a52f9de82339923468d0a0d103ed652d2dba867ad02574d135fc2e23ed5fc6d96c446151a0f04a5174a293e3f8128e65dd22499215488c1816a817e0e1b9e1b41b4eab4c9c87a7f4e3c5908fd1e34263ab4b5426583c080fc1103e907abcb0228922259eb037fbc312b145ac111ba4732aed103a329043070f9f01519508d102c4476ca787db0c93c6a8023560d87173006a81c4bb81b241b980c206470c0beb0504c41ad1ca1750cd8c6cc556ab160f1f97d86a07cd0bcc74b9603e5a98ae1e24183f3a84783c0c69c9c1e288add50685c205161c312410eb558e6835b3552b36c38587cfcb0e9a9d1f17cca4d1c2078c1ea41ab79e870952811f5e7512b224071b1b940b2840161616f0b6d89616afb4a5139df50202628d68e50ba86646b662ab558b878f4b6cb583e6458810214284542142901829f260fd6109495948822d362d360fba84644896147c09c917142f2139b3f91b27713c0813923021c9f2375efd69ac1710d08856be806a664f83c64f908a07c3d006e502398e248e27e95acc93487fbac97bef55f1477e682968658a183162c4700145b94447e706be8143493d00b471c45a7f626d220426485999a0566e3f7cf613a42404311e2aa24c392561fa80d18344c41619528447470b8b1c52d0535615d45a44959284cfea508c1435b6d60c971d3b345a946a18918939461c3a3784368a060484f2817ba068403132987fcbc686a6f517860b4bac050e529167d1e2736b0542d17af8c0b018196964c8cae69a955e2f650c56cba4195960b8f8a886c70e1d397c3a0ffc803e1044813e1e0c71d823a401c5c83ad1c9212b234686cc4aae959197f245d61b376ad083201886a1aea17ca0c43ad235d98f604e2229f5902449f63c59da6dcbb61ee59205bc0ff79d1ddfd971dfd9f19d1d14cd69a34f245d098fd283d8517a088c1cf12a9108c1a645c302c3e50707a948d08d1645582b545356262a2cc86f436c2e7f8931c31c7562228b1818342d5ac0d0411ac1a0699103478dcf8b65968a1c295d36fe23fe8dbd58eed6deab6b18639cc39ccd52e91a6f6eb6aec1d0411ac16891e363d0351c1f43edeb8d891845c3a3592aeb0c1ce29c69cf755d576badf5d65ac7711c59c4e8e8e8e8e8e8e8e8d0747494f83451adf8ed87ff04cd8670c44811f3e6d282e467085bc48c20a88a10d41542402d18a41c4ea56413573766613c3636b418316080e1e6c6e7e7c7577eefbdb5ea1a0d15b3f7ea1a0d63f26180a1c4f15bd758fcc6d1b596c719bd00748de60510838d2106af3486184c1909868bec6f4d8b97a0d5583262039491a068b0183e32249b362107aa8201d5873eb14feac4f677ea84ed536e28da5e89d411100bdba77daa927cbdf72a61041bdc1489224535a0538e588c31f6809c5822882a2528c1ed08911220c8d6fbf138175fe7d9eb7eb0d96f66a0423178266beac49c21121b5802576bad496bf8b141a58a119614e140131dec8033655022efa80ccf9f5983cf7e5071d9bc89e17a8c8b0686a0811324424d0002c7bff31fa5b0fd4f2aff771fb0b0fd53b84c18a3daf4890f43d8e1a64f7cd0c13e63beaa770052c71d0f22b68a0fb618638cb13b768c31c6d81d3bc6eed6b1c518638cab63c7f83a768c31c6d831c6d85ac716638c31c68e31c618e3305b6b33768cdd3c6d9410e088b69b3ad41eae96a40052d9e95274cb6a4723d895ed573030e233c56434e5de6b8ea660a2f11c35d9dec467fed72cc240fd6a8ea3281cd10046443122a3a1d1900cae1d1bc5886c277bc865a28ca6d4d114aac2ad0bc69190cb9ca326234a435d8a3000d2b7f65e0ddc6bafb5d65a6b6db5d6560bbafbb5b6bbeec3786f775dffaa93eae4ea303e724dd96cf6ae77c954d65a6badb51d47c41195600052d96912d9f8c16d65f4284db73137446758ecbf85bdb5e74966f5b1b73f5219d5a7feb435a2137405a5c96f4f6ecac6e7f7c4c60f826fdaa858f9ba6261f0abca32895a59544ea12af0672da324b230b01bf954bfae9465cb3c7910710d8cab6f95366c15aa6b18597fe3e595ca52529c5c9636f593e9643a9d9ebae9cfdfa7d936fd39db27255138aea35cd775a4fd3abbfd728d5de7d1cef33c97909326a4a7de3ac74dfa5c8e9b95ff4a0c66b59229574b568fdf55be7e546ae244a8f4ad122bc9f8af2b2ea3cae5682b01fb010b4241fa59f185f1f5848d9bcf706d7dce39e7565ead5a7995ffc6e65a9e2df3f584cfb009fbe133fcbae233cc4a924b71b394af220b833f56fe2082a564b7cc92e5c4c2e08795ac261606ff972c26168695c4c2e0d71555f9aae233fcacf2d584070097e68b8acfb079dbf8cf17958ddfbcfdf018411e87f5f8cd202e533e7ef347f5e5e36f95af0f9f95443f7e16133bf38f9fd5c4cec41e3fcb899d813dd6e29faf2b3b56b21e56c6de2c619f4bbd5949f6ebcfd2958d5d0f869dd8fab25cc2facf1fcb0fcbff2acfcb646797799e76fe5649a98afcac72a5da1d558173aa3c452c6cfc647976a8f284fdd8f84f2410364aa93cad124a8cca9335b4f18be579b5f8c439db272bc9c67f92409cb689f37565e3ef48206c12fb149f184068762611eba3f6a032e904748cbbfc61fdcce960a41b2788237430428023da3ad83ff9cce6603c5ddb5c72ffec5c76e69a8e016b59d0b750401946df14090d684aef8cd5348066908b73ed9f3408dbfe0e31db9ab6d15e72ffeca1f5b66f4975cd366e6b5207024865df077e1f86dabcb16179d623b699fffb340ddf7b593f8f87bef1ebfc69ad31d65a9767ccce3e7e1e7e6d7a06f0c76058f5f75a3bb1e78beddfe8bfa7d7a33bf4bcb0e685a3696762480fcae0dfbd87a8ff8cc0a7c7e19fccd3f1c7616c7636c2d3c73c8732bbd09fea934980d0f43eb3d5c5d8991b521886efd5226a8df79fa9cfd367fd234fcbf0bd67eae03dfe2ed47fc391fa64a2bed3e7f0fb48ee2d2a3c654dc5dddd1d67d31bdfcb5a84fe52697f548e3cf12f68be9d11cd189279bdf00bbffb2f7b5cf8fdf975dee7cfd37f6367688cad469c315b7fde995f65dcefbc1f99670cf8d508ef39b31a5157e0d746d41a9a73588da835dabbef995e28e36af37b2e3469e0fefb1bef6e2ebd4ebeac9ffa8c98627be6e9bd6886e6bd7150eb97c15f7fcf0ebdf20cb7a74beab3d003cdf3f6d8088d30c2c9aeb7f3517b3e8be2c1bd97de5affdedb7d750fc711e4bc191be1e6b87ae2ba1f18739d97e33da76b9e876d675d2602a1bdb56a1bee4bdfda5b041d220252d9291669414aad52150e41ab65e8b62db4ddd3fcf84f4ba5b33055beac5dfd985acb8c6dcde50f226cf818fccece58dfdf013c1b4a99b26d12bbbea552697dcfb4543abaef8de386325158d6edf5da2b9e38c4221fb7c75a6e0a9728bc43184f8a67f23bcecfdd6bedb5f839734990c7c9358ee2e41c8c679773309e315b7606b98f1f8cc158adbd41762d3da6eb3ad90db2a93e80e35c251626d3603c3bee9e34f4126120223f8cfeff83cb67d5962a2746d5d9b32b47b46be9e76c3bade50d55e13118fd4fea1d115b04a432fb9456776b816a756befc5b8ba6c369bcd66b3d96c369bedb65842c42020940861e3430307244998d846ec34870e1e4028c1e106aa26a9b656bf97e56eefb5c1d8b51262edd5b5928b4280eebd38678eeb3a9b15c699e3bacef36c56b534c6ccc81b244992240f0fc9c343beb804190982b584b4c87e7cca95cbc26c9c4ac386cfa6d42025d1e6fe8b07e3cc715de779fe72d96c369bcd66b3d96c98765bac227088222589a21c3a2a0f25364742c3b689784b63d10d140d87055c6b2fc6ae9ccb97ae912d9ad16cc17061391254a4c891161f5484454b0ce3cc715de77926cbc7aac97271ce1cd7759de77d1f17763e7a8020582af9e8b1b3b3b3b3b3b3b3b3b3b3b38323870eba82c76af65222848e1c362d1a1697ba4465a236d12268e35498ccd5636399238c2337441634627678eec539af38ce65833127c4c7133f67aeeb3ceffbc0950dc7759ef77d20b8b27114ad868af142d67b3ee3388ee3582a8d331d5c9ba880223112234aab45f6b3b2c9a1a3aee0f162e9043929c1441cee2771ced3b5f1bd1f100c2add2316d35e63a9542a956ea954725ab964010f629bcd66cb369b67b37558837a091f21413f41ac5511488c380e31806c576ce2c64f10c6610138eb1af9c367e477ba86face836a0dbc2068bd82a027562a567091ac6b2e7c8e71bae67a8ee63b5d7bf9ce2f0b6663c3da91a28505b3d101c3a5d57af560d1e2316a7124c8081050500e23386258681c87a4d3b5eae3adbeefc5ea3aeffb40300c3b9717182d4030d4bad6f35ad4b5d58b235d2b7ff41ac191c51ce99af82321a3ae3949d74a4f2a0119b9e348d2b56ae44b362653cc656fe6b80f85225329956ab5727981d1a2da62cc7079d981d162341ac791542a955c5e4aa6d309852249329552a966cc9831c366b3d974747474747474747474747470e40062bd826a664d52a082d80c1e2f343b954a5da232416b1337fff113a48487214574882d2c6e3d36346c1fa3e5a26207ac46a9070d233cb2219dd3094592a9944ac5623a8b49c3348c693c761859cd5e65a55297a84cb4685c7e70908a04899f434785c5c8765c3d2fac19230c9d2337622c68f0a05229d56af5458ec45c956651367f692389542ae9daf82593c9743a9d6aadb5d65a4ba57b4b2c62489224491e1e928787bc41ea94a8d4252a13b4fa8f1f212e2d487e4c212851586a11ad18319bcfaa9c01d341a154ba46f3291244fe90234046ae8febf55324a8c512b2b2b9e2bb5e369bcd66b3052a9579eed8f785874b8f161fab97511a555d99678fca7461db27cb1305cc39db76547afb3588a95472d23b6954a39837d4547ccbb3ef5b1cdd585e3a134723f3e45187c4b71f8ae6c95d53078ed35cf8a1cee1b8a73b6c317221c7bdd6399c19765687eeefdb325761ecde7bad733adbbdae795ef737ae754e679ecc1a6584818de23242dbbee8c4cac83db60f9242af994e1707e3f9db76dbffdbf4090b7cb6bf537dc220f9b13c9d519ceebdbf717faa1fa499ebbc0f3c7b76564155787d3729f51995f9ec6925853ebba68c08f4517b3e164780325b6b752f6a42e6325df8a3c538ee1befbc4d49bf6ba514097869feb36ce1805e76f4fa7bebe2b206eea9c9833e67b66ee7326eb6ae79808a73bad9d1da955cb653c6f3778c9da16f676eac5721ecd609a11328b1a990098028da54e8043d6c9b6fdbfab0eb531bbae75e06e7bd0ddf7b2fc37baa6b3bd86c003fbf8cfce187660ff073a83fcbc826f82d19b74a65d70f6b15d0ac4416c6ecf1bde7850f8644c0245f13271e93cfa4da86960ddc7bef993dbae7cc967dee7b70284e4bc6f7de531caa6d08a0389f29c3336be8be2583470f8e0f1caa6b685953c614aa757874a659872c51c55376c5c165cb86fcf8f1d0ae9ff3672ab9e80ee1245809918d99e024bb16d919fcf552d9952bef140b63037eee39b3c7c5a1383519556ed1ae3cf2df3e9c6c5ac0c78a202071c3b9e5bf445470d1ae393f369b9c77a8c40f566d03fe6c9e77080115e70e5d220bd30d39e98a441af4e006446e55a80a591e48e901945b151272f259c98215a2dc70ec532243496aadb7c9ae6f89d8993b5422d28012379c4a71aad00db2ab1d222aa252855e39edcdde76b54ec0aa65b0db4d4a55dc5b9e54a80441d8f60eedf30aedfa6e85ba9a1ca84008a7680a511dcab74d597c783dd7c51e2efdbff5f1d7d70177d5a494721ccdb4a3b4a34f29a5945a4be9e590784225cab625edc106eb121fd8b6acd9a037c1876d4b181bb44b0861db12c5066f0fdb962e1bc425d8601edab64c6d9063e2085d28836dcbdf20ed382fd3af0947006b13b4409f3421836d4bfa6483f489136edb96b15db92d68e1b6ebf77875a09f2bfbec4a604545538c97de6b1d7b59ade5329ed54a6797d27bff6b356bbd17bb9dd18caddfca899f09c001d43d1a290796467a6fa330dcf5bdbf25c6f86baeb17fc1de85efa754b6aec2997aa3528674a1aa46ffe98aa570f1c246e8a450e8108d42b3aa119f7e0ba5493d7d1714b4267cfa2aa8cce9e9b3a035a6a7ff02a5293d7d4c6bea4f5d41696a2db1a9aeeccdf79a3c32c6713757562894d50adcd79f4ae3debf56b1f54aadb902a5b1ef4c0cd99c6981b0fdab942849d8fe2a236cff5511285dc10ac2fdcfb39f1debd7f1dc60b15ca08ba03611a168fd18635df2dc0d134130aca5ef7ab333dd77666d62529f552526ecc66efdab3cc7fdfad7ab3c3b9ebb5f5fabc0cc4a85e5fd67aecb3c282bab50eb1ff67ab9fc0bf6b207613218acf5fad8b75ebf322ff559eb5dfeea9cd7c71e8cb55ea797cbebf558e7bccc187ce61f33afcf5a3742b19618ec5f6cc95ead92aec9cc16634bf6fad6cb6c11e0fff5af370ba067187ab5fef52cafdd7a11e055d6c0d7d7075d6585f26ab56e77637fff9a27385485eebf5c0ffbd777a5eb49e62d731546d7ebff573ac7f5b007612fd7d3fdaf75edcd16a3eb5dae67e91c97c900f3faec65520d7bd7eb5d1f2b299dc14cea33d7b35eae56885f158acbb8fe05ea1ae8fa1bf7aecb6c11a0d562e91b5abae6b1ca555969405588382051e5c9240452ad45e14bd3f0f9f5bd0f7e5e16bade1de53bfad1e3bf2fea9cd16393455d3a677447e068e48dfea57346a60d7c5ffa011816c3a4aeb142ea32985a7a415c7e956571cba6a55fd4aeb7339e655c49b9d5e55c57f7c24da8aa5d1fa66bd57cc0b65f4b1b2a91db5c59a1388c3f8baed9bf2e83bd6f08871b63fcbd7d500806e397f1839ff19b740e7ed0bce2bbcec19884453cc26f750ed6d95efc259d733f9b6109fe059fa46b2038c232ecb7ac42da001d149789806a0b0de1cfdeee762a1c2222a0dad767d484e959f883eb6ecedd4fb38bf96fdce66df12916ea9ab5df9ff2eb71bca7d7601996b656986bea9c6e5bdff5fe48e7dcaff64d9d7347ba66ad66c9e675164d7159b74cd360936842100fdbbeac25ff4ad76ea557ccefb74d597a603ebc075b008800f498187c37bbbb7750c8dfe59c04103be3eb3246d6450d56f277392fd9d9f318da0319d90ad5d0849cf3931e92b033be1e83a5de75b72a37c40cc137dff080221a96c80e11141c29820980348107406801be37742cdf05f2bd297c565f50220349688009459001117e30028f9ba5b8e112a1c839e39c67f909b79d77f6cf5e7743510a235682c880107c60420a47888293a53c61c21362ccd90846bacf19c5f12c84b5a68903022a332754c10954f639a3f2e37852b98ab0c54d9f2061689fe357eb4beebd17e72f2d724901092b1be0c110309002cef5818f11b8bdc3122a1a68c2480f4131c0b93db89fc265ee08440c7a38b281162401089cfb2f5c86f260dfbf49c2f6f6fd19eecb70196a837d9ff82c61df6780cb605ab46ffcc6679b3ef1316297367de223c4ad02d2a732dfb5d65aebec854d7d17b556ea5efd07a4389da5303ef3c725c5012badd4134f1cdb86a3dbd774285e073cc5dbbfdc5b4eb5b9e7cc93fb6c826557524a29ad6faf59e9ef6ac6d4188ca78bedfe2f5cb850d1991b0ae3af87ec930a51228b0593e9e433bfd7f4b7b421dbda548e3e231fb427f7eca23ed1997bfad3ee52cda9a321d55aeb9818105495a62f9578a74ca6af6422ffea1cd3df07afe9e926ffd335d26c319a4853677aac734c17a7ea06cf09eccb999e3399678e7d9fce469f75cea5ee22e96f5c55b6de4477ea6e3b9a54c5c8f45e324f1fa6b25215a3cfba467acb954aefaea95c827f55e22825b003cfb877a82b5426cf4ff98308d3a8249fa7dba99baeb404f52bd4abf0a74a0a749bacd1190550190aa03335288cd72435e84cf50085f1231cc6033ba84138c27bf7af443e834fbbbba7e7fdbec7250e0be37f2a797ce65f8908dfbf3229fd2d4b6f2acf9e6dfa1b2e53f23fef735f32a938eb2e99e7fdd1877a00f7497f632f1856ecd78634cc9e38fea62e53c79a58d2adc7b36260fb94ed8ec4955d77996cdf6cb13602e8f4de4ade50530cd4ce802a00ed011da25162464f3f060d9511ff058c176cea8ab3c5ece5bd9775295a33ab2bfc4d9b3e4db1473b738385f1f1861f8c55463663b833a4b0498b48d4c96457fba445dce392bb01bfc90e00ff35c55817b5a68617d8c6265bae441a4762c6ba0c65fce9b29afd19e03230166aa05290b2fdc3b2fad0993f583a805261d32704fa472fcff0c177ed2f3e587e1f926c6705b090852d6cd24ba067934c098ca68dee870578d8239302abedbdf8a249e9ecfc5e9b9f69f3381353cae2a439a83a16c6fe356558189b851f543840ae8e9d01325a18fb173f90fb40a805422d8c9dc1e66861e85b53a5da66aec298f2e13d6e4123409a62bcd6da5bca36577eefd1139f6135bbc7b5e7cccf75657dfab9bcb9bcd5f260a4760620959df8b6731ea5cc461a259c510da55432b55a7bafd7a5b8efba94cc90fd52df79cf21718129df73a4ef1efcf085d13ff07b610c4df6ecc2111c6f38f7e630f999b8509222898cc8a6f98650f2bf7e6c09f3b7e89c96cde33312e7852555954478b5f14f95ca34690f94e6f230125f4977fa4c845bcbc4fa56c284c8724d9a34b14fb8b5e2b609ab842d421d7298fcf608368999948fa282628bd8d956bf2b75ce7b38da99daa40e5d69325ec157c03a3474495788fb71222076fe2286d8f9f1a8635df59c798e2a13ecdc79f7dd575a639974e3a8bc3f5cc6771e4db944f7bc42f705e775c1e90db1f3bfcef1ee2dbf65a25fa51a4f2f754ec3f7e02c9c75f7461dba1d2bd695489d506833a6ea908b14b70ecd3851765b64239e9609780aefcdc2e49fdd1b91798e9c79761d95cd6083481153ea134aa038959f2652ec950bc449d7fcde5c06a6338eabcfaf5387ecccca5cfda962fd39b64a2d935a9699f5009a44d9f9af949d1f659edd4d62e72f99f7f6e55785a52db9f2bc3727df895c2e52c8f22ba94a529c9689bb7ce68685392d1317fbb44cce173e8385c98f7acbe4de6ecc6067ee0f87c94f324f953653e6fde1526f1bedd47bf7169ae73de2649e578834cf0e6596cc8f04de3e30053eec95e25054d310be779df7c478eaa73b9b5d107d6fe2481c8961e86a857ffa93a94389645e5387afd4af4d17369c57de0dfe78c267b91cfd0f2244d3c8eabacfddddc3d27b554971f068bb14f7dcc7bdc7c46172378eaab7964993f1ed9fdde6522d9dd39927f71d0df6532605eafeccd3ddfb30c5bd529c534ae37527ebde7c961f9ca53e2c8cfe671ddad95edbd9b78db67defee7c8fd8f90a5d283b135998ae1b47f354a93ef37c2792cd6083481153ea134aa038959f2652ec950b044b68af6ce7442e63fffb1dae4ae7d8931bfff43eb333968985c94f0245d3bdaaf22b3b9f3121994c2c1326df9ffea069fcefc1aeac6527c4a75a86512783fed37b894496fa759421229719cd7ac52476ce43bd92aa4f7859afd42b1ec493e40761c254f93d57826fed06bd14f5522051e952a86099d4fc9649ce7f79a03395c2789fbbcfffab1589e4fde96d4fc978c6ecd3866cdf280ddd19fffde13275e7fcf4bc4eba4ffdd93d49e77466d7791e67b62870b7f7b6d1f6debd52d3e03df7208ad02bedfec0c7a972fc6efcc6cee4a9db336f178228c2eefef059fe50f563abbbfbd99867fe3bdcfca2ce7193027577a635bdb23e217265a3d8f4099128fb6a19c6373d984bd483a854d7710f7adddb46bbeb9e7c92a6e17bcea440dd29f3f41f7fd43490be336fec8d932d029c4acf537518df64f2d42a84cb7c9fbf5492c0cfa379e347a5a8cb330036dba9133b7f3c2f4e17fb3a8c7f5885cbfe9e9358a7b76231e35bc91300c48f8f3e0eb884fbafa438b4b44ac6b312551ed04a44ab10b6ad48b8dbda3fe9942b65e75b56a22ae50aa367fa77235732c061569ea9e333fba44a64b3691f9705f0192572a24d59eebd40beb7dfdbef2df7cebd73efb79d2b2dad92d0a710055a4369ec675bc53a31ba14280d05fcb385c1ce6faf8c9688ed0668dfd339f63fee7d0a74742ca87aa0af9d65a8bb56f72ab6caa5b2f39f96c8ceef55f267af82daa87a5e2add3eb1d0ce7f4bb762ad30e2b73557b967aeb2f3e94976fe3327e1c09c3d0f2cb7af70e7b8efc107b9f2f3429b5d41f3e386fc2ee9de3bd356613c6d959df3955a85fbd33ab1abb80c67664d03f75ec565f25b2bf60aa5c9f1e74cce926e3608a52afcb9db0583409fb8369c3ef14000698a5b7fa89c58822261f440c491f875e1030143101cf5030975288ef48fa3d723f307150e10110811dc8f5efcd1eb3f3f53296e8bce14369bfee8c5b2b330f875893f8c79efb9c725e54821f72f8cb8f3bc0e73dce3e7e8131228d9987bd7350e575dcb165bbbc24fa9b7f1578aef6fcae283ebc9ce21c107305224800c6da7f6b501dcfa637c73ec3bdeb5744fe124a8d57b9ebaeb5b21c6fb76a62a57e689b767118cdd7fdd0e5de6ac94863bc1e77400294da53ec3949edeaefb9ad73671f13ecb18e37f407d3097b65a6bad6f4b6c9ef6beadd5bfbef52360cc4c48d535d9370a4ef6a9fa0019b065dbe29f31fffd4a3b8a84d1be0dd9be3238f378cef70601a45fcb4e8c5fa5dc33d89992c2dc87b1ef2df7f954c5bd4cc65b3150975c128c6fed8f96885b01043d6bafb5d71619eb5b4ae9bdf75e77ea36de9dbabb67a7945e9fe57a6badb4d6ecb5bea58eadbdd65edbe18ec36567619c526bdd1495b8d984e109c6be67cdbe2cf60d800a001785abaeb827ccbe2cfbded3dcf70cf7ad91d515f76c6d95cdbee5d8f9922e3cd59a99aad6a48871f162a41abd76e927853eab3535d015312e5ea0cc31c528469be20bb31c7d598eff25e96365e961a5e95fe5698602c8a8218a0d5485ff9ec17b7fea7d03e8ccbf55e65067feac120274e6bf52953614e07bf03bf04f477d2e51a83ff377cfda28fdf55328f31c407e3287ad51a4793a0075aa50e6f98053793ad9d87a1c6f94ca30e421955d8763ebaff52f8fad5f34290d776baa04cac4941d6e046c76e6b8ecd96d6caef7bc7cd41e6bb590da7d7f3f2f5f4628a106fef9b273569a94c67bfa2c5f771d0c2fb6dcadef3ffb52764dd9ccba39f399fd6abecfeccc65a8def4090b8eec5057954ae53395162fae55db086b9fbbe38a77104723120e8670f082ea84ceec5b1fc1f87606436b5c6fdf054ad37afb29280deb536f7f05a521df9ededa774169566f3f00b446f5f66728cdf8f661d09ad1dbafa134fac5b74fa335e1db8781d2806f7fd31c3a536f14c6be1d4d1f8407a31090e06ab1f0f64fe5ed4fa24edff62fadf4f65789a6512924b7afb6c39caced418c67bdd59bbbf59bbdbb76a87a82cac467f6eb09a84c114c5ca6a68082a6d0465813bf7b5015de1eaa05a80a1eaaad37aac2be13f1329fc96f4ba2c8815f6fbb96ac4d350d768fef3d690107762d479b6afaa488c806f1eedeb5ae894bea0f87b11f82548fccb3678f4c417c86f42ad3971e34953548bdcd6034d91f7fe3b8d299f8a452e630f6b5ceb95b8563df540e5207eab5ae05a646a8d40a5f27319488460400000000e316000020100a05c341a1280da51c6e1f14801170926c6246a58b9324c8611842c81063000000803100223030435a01d964a9cef42787c22f5b13691c0500ddb7af0f08af2c48a760e163c1923fe9364e380cc641c6fbcfe55aee5f6270e340701eecb9ffe1e230a81a4cf2b528bcc4f5e1e03e7a3d3a524865c95bdb3cc1fe1591cf7642972703fba463e9108bcd60734da82346d1e1473556d16019cc144283df5f2222df7dc9c056dde1b11c111e98a7093db154f7ddbb469b6ae1e8ac85099406a3b5a28230a53ca0e709bf68e7c92587227bb206cf925b376291f27171799694d7864aaeed4cd1146bb9078bbe6fa73c958072fe48680befbd225efb947a9ad22d4f24301124ffe935bdb3e43458eb38729e0b8a1b12ae18a23663229b2ef5dd51b90e8285c4dddf41d27a4e9285eef202c4ddc7a78027e925436798359f3781ab63eecaf6a99af75e71e8f28d1e0b2bb74be2c4eb0434dd2805a3574ac1ee10d7241b09942ed47c17e26b0790b37786e1d7463ee3f835689bdf4ddfd67c74aac3e52eb5c4868616619c0072a09067d624308b74e1781694984f6bc10a73dbb101ad7098f9ed59225c70a98a64670831348ee0f844a15e8b5765814a7028914382704fa27c9d3241c408333526684120cecf12629f3ef7a82964fbc732bf2a4752912799206f8c60a7e1e08c89f0c26f99929dacb0e362928e3f784931d5b2d64f94fbbd20120cbd6f10460323383769ec5efa67772798ff89c96be920f1d5773c9db689d94f1a874ce666525304717ea0cbc86371e220f4649e2bd0292bf964cbb9b24d0be1707560558683061502f1fe8f8fe9646a131cf72371b3e9bbfbc419f3e9b7eaa0f4c5eead13506ba171a6e8697a6d864c3c31da250c32a4e5e7cf6cf8c5106f9a3c8bc4136c34e06a88aab5a69b1e89cdbabb70f8b8992f2f390fcc4c73adf6dfbcef00bf33ff31be00bfa18056c8a37973584675170ce96e78c7a93d01c3d8fd29a70241a72aefef766e4dae34e8e588af10f32ceaf643bd7fc181c0bc3de7133cca3718fc0c7ef43163f4a8a1768ee918b716a527cd7d3d9e86802e058fc81084d6148d0da5c58a2c821aa5850e1edf1fe5d9b416eb1df046973cda39de45c7e632540f94a19a0c079837043b38985586601715d2a0815a650e3ddf3180f4d93005e1e1090775acc92e699698e5713400846a8fd5392216e53720d8764442eb35bb8c1325e2f7aba2ef804ada3fcb17b4a1a249c782c02112b36f3822107952d015b21b4b30867bca0267229678d0248f9942dda436aa3e2b694a26bf794f15b4c82a155081a0a1603d6094a43cf79183e8ceeea8ad0a189d4971eca2ae95f798a831646a207a2e41d349c9db3e18636afb3cba029a26dcc54f5dddb37467d61c46c233425d67135e84ad9a81658adf3fc43b4769b660906dde11c3235a189ea8d023664e411a2e74cb9a4b488583d32576ee0756156ea3f3c068098ac4c9ee6d08608bcd1ab176c830eafa11b1ea000bafb322bc0e59de5ff014ee60bf5b31ac4d49955a82e10833215bda869639724ff3e128b44d760ad3f35768adbf45323f46fe11c0a4013e9ff04f547dac004cab559d599732db7cbd01deb85c28e31b305bf0657ff3c3e82a8a9500bc50a584be5482f59a4bc13ac07ef48990b92bf84c0afa3b0086b6b1c88b48a6dfd91a3440f9a7197646924833463a7deecfc3247ea1aa204a4a7bf7219c6147d70407ecf513bf27de7efb149f86deaa0a6e5c0701fb21e549cfdbc2b3a059db46fb4753df5c0c62d17022b04fe02c528126bab6add93b74e3de0f2890f0a5e9025cdec73a8db438a8b26a29948728072b08323ef760c71d50627d285ffe1a697ce7a4c724a39bbbdcf7ce302e4406929a68250a4e5ddc0387c4bb6a5415da595d5f1306d0d4b47bddec71cfdf4ea3038473fa8912364afa58e398762b3de2d5564c44e8852906edca22d9a9888fa49340a56446c23f48658e8d4fb5996eab30dab12a5da92a420155d611180b1cac69e52d6cea01d8fbcf95e49b9eac70d354304ee4ca67c9af8d3b430525c1615c9acacf503ce30b09463b6eeb12fbbcada250531f5dba85f99f6f239881b21b7e3f857a727371fa616be045804eea1beef0ac128380eb302cee9681962d9d03a46dfd8a1b4f9d086b3d8ea0196e5a952f38fb245efa787e52f43e9bd971c526fffe044838a08a55446eaf2be5ba7707a39c9d98e89477caab9367a0b8278f57e65640eb19f46f0cdb67434bb24acdef7d2a7bd79d3ceccf1777f941cf3f1be05ab86c3fecc096a6db07373b7970277ca05c3ad2a65e35c50aa4d7267495d9f8a5a4b1a4bcbbc880f23a45a2f04114ea27e7be6f00196b3ead4754606c98f53673f9d7675b98734a679f67429cc580ee3353868dfad9858520e9d5e0e8d44e410245a0e9b1a020003d922ad93342bc51fd1fd61868a1a0cd3459f5b9cb4661712fbfd357d358b2f5e7e51c46a03a3374cab3992e8ff31bb2330c62a7d07295c3d99576d3cf6659110da618273449a7d14bc3b9a8e74b725ae1474418a914c5491c1037e3b1395d2316dbf037376616e1555748cad8827d0d57ee426954f41af947f4a5424b92431dc5ed548787d5198690162b7502581d06998bfc374b15c534a39ae9a753e4775cf4683977fa9890de50b2c6e128dec7a291025591bd645bc1dad28f74fa7d59e8aaac4e7ecdf30564c23608926f808188a0bf20ba942b5b62052c9a7bfb8017466b75edd4ee92755d7c377b2df3584cbc15be51dd5a0b4fbed0bc4427ff3ba40629faf1ec13a57b86f7378159f8685636dc013dc0b43ba0e7d6a52e4b3596c91d2dfb1257506a59a4a6d6b9f9347bd3a6e23183d4ed8e28359a7f91ad99f7de7cc1da95771838ec354a6cf1ed1e295d72c73dc17149f2d3979957cdf7196a01fece4f1f0e1c18f4ddb254b1cb1cc7b244a003ab66308087c5b3d717b8fb5e02d8405a66400a0d4e66305ffe5c0fb85f4ac55b3ec3a1945030c6386a5d21cd88b0d501368da89406a213ee532c4a0819fbd39cb99cacb7ed7a8e74c9785c5947f6fe4ac93b76f94978e248b1bd33c7e008b3f9dd10998dae01201161540532aee8c31de74645958b9e15518992ef341585ebf1a5e532b85a22c9c936e90962b5d5fc529c2d2e2421e46f15ca6284439841a492b53503487b1952622189e876e944bcfeb694838ed1596a942fe4ef14423b9f69b232e7f17d0c2e656afa185721b71658faf81b241b54ee9b577017834330bbb65c843f2fcf577d8f368a9ad4fb0a314d41d99a73943bf3c18211a62804baf23c4dba7f0017ed0793471c28c1ef20c6a8d09687b2faccd4e3fc12b4aa0e0e9c59ef03ff930f148cef8586c7447d918372a04db3d1294d231c7137cfb285af52c975dfa9f7b1667fb026fa2ab491fbba61ed42667f37b823fae2eaca4193f62ef331db685ee392893c8102edaa2f1bf8a8d05dafc805ff8dc4d443b41448be19b45d7d3164351b83f0e3baf9b84e123b1c635e0d178098c6068d3a2bb2dfac9366733cdaaf3f680d001b48171799e023d9496dafe912d8892c2539a4d7e4e75969fb4579c01dd0ec0c6d59fe73a2b725992d799d70aafa7427dd8143c15aa3f4bf6756342d3444a0c9e78de027090419ab6102d0cb15d644916205378d4f7b21628b5fcfcfcd7d2720cbd14361018e35aa9b2078148b029c489cdf4b917ad0d0c2e503935c0468bd76e51ff0fb20751cc0853989f862d2a51ff360c8421cb143e01026c301a02597469541d4e62e4ea8a0b568e24e3c87eed8b755e8db2f9ed3de1bf6499d38520760178c33fcb1b92ece6ccd7fc7d43eb73f8269885dcbc962509e92c5fde53999bb0cf35347f080772db1cfdaf4c253910f12dd06004ee8c81d2cde1c125da7c8325a299bb847694cdece449b6d17c7b27f964af73b7999bacead3c23efdd2a1e98396c358fcaea78d31baad6f93d0dea7acd0a6479d04dd3dd84141c223b527d2f4f7e56a0129ef2f018e5f3e1c3510ad0ff791fd3bb20fc876f399d7bf327fe843d73f80626970559afc437913d3baf09eda026de4f94cae7789718ae3638ece85429e95ed90a0e3df38640dd55e4aa49c899a3ea30efa57d404267e32217c0dc1ff7dbd76e8aa3bfbe77bc4986c4e650d06cc8dc0c2c9987742e6a57cf947e5d453ba338156f5c4a5a11a1a15dca3f7a0d0715f6f9332ea225be84c5dd9a0f3fbc90ad9ff0dcfbb2483b10a42152cd47670b93d056bc9439ed4cb2380d320d44d041c81d9ac885c70dac87d80dc1aa1bb33ae682debd8473c792dc70ba189ee83e58a129ae7c6912ba66c7b2696b08982f667de38a13a9e8e30466d33e74a6185a86ff54a6e693e3eaa015e19450c8d0355b2517c7fffc30f31bbf9668df2a61a2488b1ea69ce70e490d959c6b83ef3a5126a6d27050e3e1cc64c229b6d931efcff62c8fcdc4823db1817858656ecaf942af59825980f9f76571856c03832cf81ad74ae273ab4b218a22af0add381e862ae2ec3e11a2a0fd8ce395238f1355db6b4368cb38de2cf621c2fd17495b9c6b0b9d7f0c311f3e1f88a2721f8527e9ed7e13368c6ddb1bb9f5003d4f93e83b8fa36cf99f71ab03a70c642d2ef3539e272010ae4dd15f33e1ee9081d3a5c5d69ec605ae254549c2e6848b64900867aee2fd0e59602dc806c08f0b06f5a0c346c096bb7c63e8e4a6e8094a04e22d8718ac9c5d9e18dccb4c931cb62e1fbc147752c6b9b9969965c1f65c25fd9a98dc2687c21bdfaab2c3354252f230fc7ade5cfff94dd4ea076d2c72a83d0490a6c68a6e1afa66325eb83a6415c94c3b4b26ad9babb3612c916b85d4332cd31fd251293422735742db1f209c987205130a3fd2493827f7e3fe7c692d622ad2ec3bf1e5f1e2f3ec931d6e6584c0d3df8524325ea33acb92d24a1f3802c9517faf937fd3fbdb4fbb2dc986042729fb22b6b5b530916b3e0706cbb21837cd9a49a517dab8d27f25cb647b6122badf616e223ae96a9df9a667234e5d9848eb76e2007b92a201dc1102435d6369d2741d78d6e2c44087f6239fdc3780a61584f360ba45fb53d9ed27f4c3c954f484d1ea9db63e57a5ef3b1032f8c9d96f2286bc1325802fc305dba2e91ddf0f061d51b824d7418160a1b9d03a5c07c4f22d2f78b0c05364a8099a3ad90ea198b904ea4f376f88a39565d99e9af48d0cf7aec991a4b15d7a836616a83fd58207f797545b39c00280f913bc63dbe02dd794e2f77ea041f6717b51174d14c618c7173e9ddeaf6037fdd0856b11d3962cd6a71bb4b4b762df56c1864e9f5685d1c2763f77f535466bd11a15b5b922b5ac0c4ab8f81946e807113c765337572349260b349cbd9a4383a2bb02188107828565140a39f0e4328393b02087e5cf344c3ed9e3784784ebae0db6f6a92fca0e04700b153e219d373ce00b190ded04f91afa5689895cd57b5487b11168c9a5ed0bf1df28b04f561beb6a4680b4e99b7464f2c136cd745ef886a6e0a148f60a09726f08970b01d2d2cd292796b2f83d43db337a910df51f719e7c886c2841d6a6ac16d0bf804c929142f94c786999cf031de8a5e08f60b16631db2c3b54dd4aec38a980facbf78536004f04fbbb0aa2ebd8b826be699f69aed999ab7143cf25d77689d738a9dc788156a2e736bdca1840b772a59ef1a1f4ed82b0427137902e6370ed00d0ca1eae1416388fa150ebbe0cb09915348ebdf04568256e670ad3a4a1c4f45623abc1ec47497e6caf440667371fb2e141f3e4a0e0c2082b083708fb4b6613c0698305c53b530af62f3f0666d2383677d7912c2e188d6c50372521e16e7fd3ac4dca7d2949016e7fdce1e730d87474f42edc4424e13451ebb1c088dbdbdec265ca11c78cc74031ae0082c598533a191c003457e6ccca26ad81e15c0a9fd46d69c1e58aaf0186620ae857c3f05524c717b9a4a086c36d4edc1586f86fad7ac66c1c2cd10d3ec867258913be810522b7d08292b0a3c90edbc9c8ba6881a4302cb668e6b7008394f3a7a80ae8aa63d9e6b8b507cba72fed356910ac5998b323cc0d8fcc1bdf67530cee03948f36fd8968fdd4078f3a8a0b44390814de06cd9418d7d1947a0808cc94cfbec082193a1311119fd131b73049a668898c79a6e2ca0cb9818b7d6194769871af00c60375f6d592e9b7834105999f18a315859d891ef87b413c6f5c1fa6e6460a3f16c7c4181467700fa676d430eaac2f60d8eac0cbadf1c8bb684176d1005d725043ea914c5fdef14abadc8d4e4c6a68f2883d944a3b5e454b50749c3103f9644c5fd74b14e74a3568fe940e504cc9eb9b0c33a38f06e1917fca9e9d092c5da750d18c7f656cc3150868f842e6b23812f0a1fba327658c7d942a9674a97710987962f12443fcc9334fbb49e06002275d66ad9c7b6d772da957bfcd2d8d47a713f5b5910fd7b1e106a1a093004d0f087c79fb675d3318f7391022d738f506ed6d44fe1d11eb1ffad4da404811dfc51d57d35bb12b29db27d07367c4e4915cca3fddd9cea35bc284eddbfaecd7604dbd3a1a432f0d97840fbbaf9baf82e10cb6f08890e5d7557f49f972775526713542827e802591d809c175268a01dd217fe8f2b6ccb6dde466686d3338f93ff102cb31af4bf2a21d4e61be23bd9945d3fc407ea543b6396f3e216bc880afef3271a1e5b850b6324219964a4710e0f5bac9fd75ea82798362b84b52e609de1b057e98b042be6ab850a1fd50fad85387a89c6f4461e68ac49cb666c12dd912e6e19c4fbf0e815d610bcfbcfd2846ebaab6732ee169a256bd5be7a34b9d10e1a58eba69f64d9b159767f20f879aa258f26eca0e2b0cfae001fae8473853545d7814d3d102e0a6f4ae85f4ce545c35520d167f1ac47cec8fa38b7d80d3ff7a5caa2bb0b2380616b51a50d63348a913c2c2827a926a797bc22d1b54d428773a63d21119577a41464dd6ee1e3f940729c3cde6cd6bf78a58e94caacba25180e96c2cb64b3302e1ed45501e417724a9d52fba17c12150082a061c199bb76ce7d9c48884cd975f9415219e850495d6189651f5c931a8bb1b301b15060054187c2a3257fd4cb4c5daed89660f775695449ad572a1dd89d5932a58c01516b4acd3cd9dd311fbfab191afc8a325765607a3cedd50c724f41a401753e5c553caf31d61254ab40d4f44fad201b4f3c82adb7fc4351031e5c604bfa12170d66e660ec1f72a1482e28ef805725683e00740eba8332119f00981ef7934418aa0fc5e5937024a993cb3862807570481f422c5cd17b7fb3523c75e786f4b896e6bdc07160bcc50ad97795d86d9547a8156b206fdab2f4fec9e981df2738088017a840f8f01dec2100715f7d92bc0b39aa98b2bf1bd7383b4a78ca0d7f7d747fe45b18b5e9786bba45bb17ede9b974eb270eb8da06abeede102a404bd3330fcf660eb843d5bc7158a2de8c20f1db852c3855929dfe7395e3447cb98547aee4349ed53c045760ad0c334b9c994ed45e4ce836c65244abad523833d83bc6f642e1c0f49d3bd27e53a40a390a21294f863158a7971e70d48e6429bd775a4daf5c34e733441ce7115af3e34fe48f959bed923264c38dc02f2de8baf46e5a64986bd3b81a87ebb349638bb5c19acc4cb2dbc5e9d1ddd69d853efa97c8b13a4f0811360998f39abf29371710684402f5a9941f0b7f181804693f1e5c7df3dddcc6d75b0ed505e394326c78900158049029131a7bb93c6011b044b4f2b18efdcaec70542ab9401703bec341dc8b75db18c17fbdd4e3a361a39aee31ed39ac50ea90c0987332e48340bc842b007ad330b26c913dc4a46709e63fce31c04472dc98090e4a7a9bface87cce10455794ae8dcb1237201bfd5c28f01e1cc50e229ed8795ab7a6dc12bc8f75444e7090814e84d1918b30de41d389ecc07685db1918f193862cfa816b94470f2b20547296f16e4e5f1b90facc20f7fcbdbba284dfd67e30aac9f5d8c96eb2ae2af22f1514e6edb6615687e5a5d0f96b7c3c682ca619e66e58693ae2f4a4ce429d1ad0246981e1a75357d69ca8851c6572c0ab1f10a22625fe051ae5e4d6f1975d7632f39eccdf813235ddba91a18b9ce3aeb1218e745b3a23e480aa5af248fb82fac1553415c0aa9cf989396069063a654949aa6be9a2e74b4fa8319703537a962494809811a7c0fd0613853ebb445204e764b98026cf914943b1925373c3776c374737af4ebb68595abd69530b294c459c9b899f5bc2097c3b88f7c85bf9448bbb0c4264c15afdd008d90798e558af7c05688b7630fe68133f49146c3d16592e14ba796eef145a760dc6ceb26c46556505294dbf4205168ee8cc39ba33d6a95fdce7820044738ef6d7b4b65d1f90ebeb9e6481de63ae8f1bbb2ce23802ad40998606515b94d4b6bc80f02e9d1027d4c1a95274bcbf126d4bd1cd8bfeb238a4078bbb0e27b7e3723757ce9bca825ed92a97e46692859851d32beae0fc20c529ec0a8a7817c4a425e85e8aeb3f6641b71b46c5c6413c003737c553e59a93a85bc05a70b98bdb430a0bc39d292ac29b3aa75edcd904a203a30cc2c96f5d09b164f21457c4cc080dd0ef3ba42130107784f9200c87a0bf8f482868bd6346de6b9a6383f1935b24ee386e5621acb9eca73ae60c62fffd324cbe300e02fa652c04122fb3151704c5f4ee10808651e749ef244a3a2f79eaa3907eca72040adb0a0a22409397197b3913e3208beb87ce2ef165323ae6237acc4ec38d6a1f57f86f1a206dfa65e484c580d1531f4bd6561928ed2a4b62b26a2b0348c6ce66db3b14564cfdd6ad318a87cc0c58bfdabe8c0e366030c9c99969fa65c24324c55ea143f91a4e1fdd0c42de450c12c628ecb68b63d591f033cb6b8a91f99c726d87d2469ed2885ff1668eae18a6a40302fa04e8a3b4426dc3a6ee731c4af197416e5434a7ce72d5d1c792bb473e59bf6c1fe7fedaa5c408e704c5b713bb91d69ad15964468addf61941e76bb019b3586556fb2adb802a841ea82cfdacde9de718c6d8854601df55fb60705c866af4fa0c9964b11ade228ddf10c255a3445d4c221cfeaefca6f5d9d324f534c68438587a4160c95d7c55a952f01032f4b3ce4d73ec30983772fbcdb84f0e51924b24d6b3bd517090cb0b48a8605bbf153d68121597dfe8e20fa2e219f77831bd318651056b29f9c959b6778dc98b8c608082a5a4c6511db69ec8e53887b14995c104535a188bf8bee7bc85bd1747c4a7264fdaf1950fa4858286019e6fd2c7d0c7509fdea02c321f45e94be203b8c7c59f77abc4062d09428bfcced5dcdfed8d4a084bef5870a18f018dc4998f8694d5411ac5be294bb6b6821037895a2cc99c55a83a0befc7c8622c6a209cca6cd92dbae8327018021274a6ab972e5cd308b019f9810e72e37a396ec7a9388b6899e4de3d0402373183aa9465b955713d6b29c05b7e13812f9d9337fa4bd98fcb3a4fb4d650ef1c80b86f8c9da8e71f1df0991bd0fb58faeed02b49a68254bb84f76ec067ddc345ebfc938a88250262e86b351850a650d0f744510593b5f2918d965c91fa4104401f15068903078e0ad090164d7f410bce46d9454590c6f5e29631177908ece7ef88db6f0768fd82161da0e378afdb7f19d909e85599782730a5576292b7baf9ad32429c76be8beec90918a4dbaba91dc8d6de5e62f65c0699a9485b94008a706d0776b5bc00282d77a6caf9d7e769d6687311506ef100b03689b2eb9268268094663924043d3e99092ac2b22817f6c7d66ba3d0af07ba4a7bac2f3e7542a1679569c13be7dd2f5e0bc178d4321dcde1d32656b25be85da7d392e33e182e93f32d4e01475632aed582bfadc6630eaf415dcce37e6f54455bd995e82b9562a796702aff536659a2d65361548f6f5185f98a24e341b1375061c530c7d9fca62c9b7624e0321601950b50c30c2971af6ef286f716ad5aeb573794f713f38cbb7259815abb887ecd9293d62b50e8705713cd37497dd74d84115a865778bc61f5266042153be31b8cf15f31ca856b393471a5512e3a40a21c5209665a65e67bb8910b93cd021bf5dbec6f79a52dda151de1e319e82879587ddea9052000cc999ae99dc2c993d0ed0d8855f028dd282829127cacd8be24aa2c1ce67c190df6d41de7ce76269d19b74e0d846f49c26d0b53e2f02794fb4bea492a7a9c29c7c844ebb647a7c80b938c7728be37871d9b295d7c0b229224819ba866d7db3b568230c28ba911651588347671dfd0cdbb1afd88fa9f208a9076b4ac303d4c8f8477f24eac4841733bd07af9506eca8abfc7c99a7ae70e1f27cc53a368f3b54672cd692488179365427db23a22dcc6b10b9bbee6fea44ad6deabc62876334479c0ae48d99a960ade877ef44e8493bdcf9c5745fa395e0681a25ad731c3cb1bec65b8ac208a91c26af773344b91ae0980c34117c8aaa9a22ecd8c5976f692832ab7f896ca80980f23f95ceb919a417d429862123c196967225941e5631e573c53639ead533007900afc73cd535c9af08c316da0fe51b6e1aebb478300a1841d4d514b5f7ad6448819cd4dafd046b657e3f2dab1ebbf2a4ea17f0befdfa363d08c0c9868752726f0448a6535d5cb965420a14e461db1522befa980f099e17facfc43f57c0672de496f5d76290c8803c72896c3176986457bd7e2ce5821606518c0a38d21af81dbfb40fb9a1f864dc5df2ef847d57a8629a916c1a1666c2ee0849a63bedae2a92d1e5477cc4bcfd0cbc685aeba5628c81108a2baf8c3c0eba7d072691327534922c6a02ea621ac423be686413173e2bc4b3180371e142d5d4a4b8c618561d9cfdb9156255a0da33527a1c0c9401ab0a3a63f23c7f574acd3716b4fed3733509a6d6c2491d023b5dcd9c1f0b1646d5e790464787bf4be6dae20113c8d7ee2fd19855cd61976c2eb27bcae9be42145ec3383234fb6c3c762080f12f8c908d0b5281dd6d0381e4100dab0127d82cf5b1ea8796562b350af8e9716bff86ef1129486469ba2685c671fcc381a31b9665e588e79b2fe0fa9035035e4e40fbd05dc800cf996fcbb11d933ccf7a44bf795004a9d6e957b8e58a205ba83c1abfe3e46d248f01fd175a15410b8f00212bab1f57617ab80460e414d97107a8aa04bbbe988b6acf8e5f231bb2338fddc12cf52102360749903c076e7742837a34ba36b8109540cd6e46ae27b27dcb3a8c15181e7e159967eb013c58caddfe5569c44bec58ff0e02ad3474e7891956e4c7e30d61c9d13e21b5b51f7bb489ff15dc66d5df340dd0b7786346969aeb8a19fab6140c98c6829cde27f979d10c7740391969eaa7dc54d30c4f074216a8fed9691ae133a4dc400f8fd6050d51d2e30a532be33ca5eba3848e62374f98a4383c3cb8be0d4d0274adcfc1b73f7a9304516d746be8ef664864a7e2f40031900dc168a38156b680360242700bb95cae94b01a0fa0510954b80c0a070c538340c4480208e140518037c93a24a0623c76fa5b4436221a22d111c6eea8504cc109bb0c9f2858b51fad22111a3ad6e894204990045ebea43e86629366724152628641bfa9f41461595ce35d6db12102be5aa3e07248ebc6a955d36a40f23d0050a42225f9cf39b611bac354c13addaf6c3b4f078f4ab159b516267a9be49505664e480b1174fb1a673a079e5c13319d72d2c6a2dd81d685b60c8fd54634da93247eb07e5dc63ca8e69edc0f8fb306db11b9ea07ec87f34071f8f25748d4b5c8b9ee31a82fc5f660c9f0bbeac6a57af018c723e7f0f9ce9a023c4dc5d7af2b90ef6a750525f55f7b6b420f159664eb90d7cb337d4fce6452d0e438049ccd839a8b927142cc1ebfc4b18d577e1758bdf903e01b7cf6b1aa1ffccb6254de65608e3962a3290ff60ad5df7b98c9936df20683a55833037d0faa3146f1489cd2996830fa195b9b69d74b9c6c45e281b32148a6f94d3224775d6205e79b7916b6a1235b567f234c9030a610a20ec2f661b7c8904d7343582768b8b0ffb53a265672e9089f9fdf8a708f50fc30dfec143ec0f5336dce479461044b8f0f4e5b8aafd06b4c7f8ebaf77f1345e838415b94de0b273150d3ebd635590ebe7a289efdd9d865228c29ed2e85dd0cfb148a650623fa293341b9bd1f8cda844aa969aa48e94d986a49cf1f936c18552492b5358cc9e88d69b6ad336bf66214e284ac884bf8e7879c64b2201e3dd8e376e20ca651b0260ef98843531b6c4ba0a4abce08115fcea63fb4c7cf951c4cbe3739514f997bb3ae92463333448ef301caa8780f3f5c5147d851088d431b35ed92f43dae73eb0061b3cb2f207f5d7c1076b881f95d3c055703f41bd0f630619cde2095a1e6f989aa7c2244000dc9dd209b5e8e885219feb27dcc2a900ed1c1c9a3f65b2ae3299595653b9ce822a0c9af19a458270a534003705e9643758aa0048a3e6239f44e6e4d12a1f03ea21e3c58aeaeaffeb03407cdb2b004ee6f273826cf8e1693c40ff7feec1968c0c2dd90b61c082f17b436567842e5a0414d73bb78c62aa3f849d885870102ee8b862813e6a0174ecb5aa7d150254758db38c67b3c8ce3a96c845c3077e3b28fb50d1426fa3897c6f09828187f588c6ed09dc10c613810c642e02023b0ff050d3ff1e206638b106d1484f04fd0175ff40957036544e33a9c2012392aa2f3ac29bd42782265a08af66fa5611b71d4ebdf4e005aacf016c24198738c99a5c3f781521e796870e467ad0136f1f33fd4299ef101939022b5ea62f595bd58a6ae93731b6f0bc2dcc0cb5c55fb2c4c131b830b6bba1a085decfe78a03e9e5e2feb33eb3970adfa659a5a8d07881b6cc6f250f8c54a258e13158e945067df27249b220deac2dbfe29dceef0e1eb5d7e3baf120d1db540c070826f71311a16ed100badfbf13a1d9c2fd4142b7fa66e2e09f1024b19566a0e17a66e8a6f68641d0802fcb451f25388332e2a87a3dedb35aa0818a05f808af2ece500911d166ced27ca994cc3cf47a32df31081056d5eb96ca3e077d83d6e7408d8e184ca076f5b826ec4697f21bf9d9b8eb5807fb430b453f5f3742611a94c9bcee11f1b821882b45af0606522999e3bf714b379332537bbefe46a98a8371f3634fea7a61836bf7ab050b7d5fb6568c5bd5bc84d7a1a708c6a7c31bfa2e4bf199138ef80112521fa265bd445121d3dc62ad9b88af72df1604360332101d457973550dfb92f4d4acacee41a88d7990fa947e09cd6dc26d448844977c230c73622a59cc6d660db5fe5e3a3beb9e12b0db0a6914bb5b788bd6425d564bf95fb12f7c3d2d404018b7f686b29f679815341a058683199f1a9bd55d7500b8b94d8c8faff4a9832331bf4f989839d37fedf34f0f1c885cd08b20b30358906bf8bf0addf07a05572228ef6d5321fe17b8bd45119f76fce6fbc8208f04c37fda22c88c6dd8bd74ca4055b78f0f84922c0e40180c5fecf61d8c1d92b46f3b250cf28870b9f0a4e4f447f1c8abdcc6adfa4aae76df26047257aa60d7987257badc87e751e543e34d3da0b6abaa579635a2494a78741dacea5f2b192f2e34d6f5c5360c9d4c211e6cc05ccc7e3f4891aedab198cd7ae1831411844b9cf7b22225ef712f3805cca9463f03a70c00f0053b0a6b40ac95db0b2fb801e4b66e5dbd1c697a4f43de25655856e3907414d2dac8ba71c1fe08581ead1cf33a0d4606cfc29f75b7300916123d2cdc103ab9bee6b8a1ff0f177451d11023e5aec46537feb75d7831f25e3f2bc3078fc6d1c70fd73bf89363f6c22d4ef2b4c4701a378ec919a3c8d70cee3d6e71523e94290fc3fc537cd1cb5c366e1cdad9cf1e953f5da601754f602f95cfd0e34a85dccbe99b2c4b1af7a99c47ddf5d87d6fd427cda8dd43385fcc0b02237b477b7d35c84cdc2b48918baae5011cb381b1b7c874defc9dcc5e6a1f3ae4f03f785e0eb134e94e3459728dd435fba0bea6ba7722f039e4d5ab16950364951d61da1f91d76af65145363eb93f37155a6a4d554c7f128b04d2e4979816870fec3283e342e3b6f8229f1134e788b907520836adb838b8bf3c59467bb1fadd761cf2fef0a2452afb138acb8068a4de0bcb980b84f49d27af28b11def275c16bdc05b60a0ddeabab306fc354e617d5e81ce80e178c4e69132f7e0b54d56e80e443bf9504b6e393b99915d171f5868636515738dbc823b3a0bae008dd7fb6847930c89ef04c9f18c61dfc6a6bb3f1485b92d3358f74e78a11e00f6c5089f2ff97f9ff6c0f9714000ad85cfb73b50846b77474877dea4d00125c23b8f13f82b79b8773e11132061e0aec14c33fecee72471421103062f7e4828d7269e238cc0f521046abd73dcb57ace5244292b252115811274802579e710e84f8a406dd249681aa77837f83c82d667bdd190e061601a19b66181d8a2e716f2453aae021e5499e71ce7220d6072a801bbbbadc9c648d9680ae3d7e5d89690ec3c33c0009e32474628832c2fdf8eb2c76c8b189d578a40eb9c61d8fcf3aa2524593a1acf9e0d59da1796ec567ae236e3b0afcec71a176d4538f3aba37865a56ccd02bed66ee76abb02c5f71c8958f0bb25786d5b84964a0169a065873d658d410f07b5d324d59e432e52ad7cdf23d5d2a2f2f146b108ced1b21fb0ba71e38b9c314a1c57051ae18d5614b0e4e2ab4458e74851a32cf75daf637fe8ea0bd33139b82c6c141caa423e5d04acd7fe369244adb246d771ad5d93cfd0e65e90cf8bc854de524622f918aa10e1ff818a70741bfc0542b8525b403b7b790680a2bb4545a5d6295517575f8c016914c79a428c776663220643928873a7502290e0f40471b60d2547b10ea8a7e081aece5ed3bfb53108124ecde6e7aecb1526448a36e116f4d040161ae3666b1b3ee82bbd1f6821332bf46a11c051db72c6c0e1128323b1ecdde01300ef58145b360f0d873bda069ddffc0bbce4cbc0bff6b267044920308583d527c36d034b91494307c187d86c91925c92a6a000ca92fcaa14d50deac86b72f7dc2e4356e006d0aa06b547f6d344d2a36534830ec764d19117e212bfaf396fe88f9dddaf30f1c22852966947fba363a581ab4c5f52904a06da57dc92417811afc641de7e014c010e579628e6463d14405a83b56d494464885fe5726df145d008a80106fdf499cab42f256f2f70aab01a97a2e044307a4f0dbf23d07ff60ad29bfaebc804c0651077bbd1532a08e4aeaeea22157925d9794074f15c245030825a034191d022bc6a2002ac54d794deb4f6bcc1fa492cbe460343c032febdf070ff28b46134c16bd0f713c6b45730185906adbef13310bd2ef6519e9a5d5e7b56b8cf4f0cbe18769e3250a62594be039297509b9398e2a01e1822729bf05efd02ba829a33225fd89ad4f040b7ae5f92badbd2c7eed7213fa0633d43bb27e8726ff1a93599f1102bc1347301c1009d00a56788fbd73689367da5859a8a8c1ac0dd96f8f5ceaad89468702a67dff410409268150ea9d81855855fbd01cc2822b59eb1c7d8735410b65d299525c570ab56c11246faa84670ac4aa4631b33bf07721cd03e3d2cadbfcd877201a30266ac3124dc019c3e8e7ff5c566c28e4830de6a9b45313587f4a0455c1c33eeca2a0aef7ce3534f7033925cc82e66626838a53e84d05fc86b1dac1741c0338e089664c357e8539a9145284b48ba53b83eaa6428d031e065ae64867445f63208bc0a6eff9418b9c511ff9f4e9a32820ed871867e5619c8d94cc107a7a5671b88c81f9942c85e145e4a8b110701b9cb25841628dd154b1106076d89b0b032be95ccab12a1550106c9e10df08c3103f717678075764f3a234333cf7ec5cbc4aa611256276dae3a63bfaa23b445c0a856d8240dd84e5972a3907b80ffe6254bda820f19b194f73aeb60f042f26e86ca626ef8920f81ac1a17185aac448341268c692bee8c4b15ed8f1a5a5acb547c219945e6afb6f02e83e0ef05f7f5f2f0202800e4287219689c66523d4cfc1872070110cdc976008c94b3476c5995ccd9a5afec1322b04dee56ac03b7f01782901e6ae3d4fe1ead6330db934eeecdff2e81c202d21611fef0e682d822fa3aa84b08a44550af70a3a9eb69ec61c6b042f5dbf1f59895800116a052cb1536d44f05c29b0bccf7046189e8dfad60fa49e8371488380649d28d730ac4c54d2d7fd6ee182ae691c4ecd2f2ea729f1c303205ae1c010638ec63ce8bb45905a477273732eafd44c10c8a72aee55fa1bcca0ddb2c21b9bd57c98e86c81c4698641ec32d58d8d089aca6ad2ffa244373d339e7a3071f29d753d90f5d2a82fb2f621cc2dfa46479a46b3720dd238b5e0305e264d4cdbaa65f7df13d26cdacf957f2872b6a7a43d60b06c29ea6753a0a9a957c227d63c4608c20dd8ecedfe20ca42cc2e224303b360ebb768e9d6667fc81ade3041dbbbd0103fa92bd6af20c59dc41e812a0909b140ed6447ab0e05a2908e82b7e1fc9eb60d37407c3a962774fe976e22033b2e3f9e012398bb9e9c72164367b8e0bd0f4dbd8faa31c124edef13e3d181821d8f863fb04a76158f044489096704985aca12587fb310286d390d96fab15837289d91a8b81096796457558480919dba05caab20f839241803e144c10b3826fc4cc84dc238616c9cd86588134c30cd500d575fb9f84fc501d0e2ca371215deaeca75b44a0500f0479d5d56b0ceb20040976d6f0d8ed0075c9c45609f7f4eaa8a80f5de5207853921a83582855e8fd3e8864e3bf21293c0050e982709a20ed32a729227b0c18d7572f30ff510a910128fe993cbf8dc1d86d0427b2b274e1cd902b8b6a9054698fa040f6c127cbbea131944a94502203588ed01b5431e0ff78213b8239f334e93110463daed1bea0d4fd88f702bfe8ed1888888703a07479014fe88042414247fb72a13f1d2ce64c9bb250399344b536710c1548e2b0eddfc809dd8119415864bc3f1b409d34dac0931971ebc3fdeff717682afe05fe1a2403b89a055bb561e7dc164177328d27d86c11921963b3e03fc5910cf29b76b8cab120783f14b737c3fe6445cd310c7de2b0be712d2c3626114565a7d0839055cc9d0697022c745f32706a8079b745279607dedcfb090653218e2f16b3f7a70f820a5129ebe7f52f2291e4703cfae064778cb13be36f5090d7d76e2348b17dc5b9bac25d6b00fbbaffde04ff1dfe270ea6ede8a6a7b799d1765c081eb3559e693fe36c2297d6875f198a56b1e3bafa33cefd192c4a91b0d85762c7a51793e25d8924c80f328c42edfbc40e1a15b0a35e4181f042c4a4231682f8e88a5aae28ef196bea40c46182a65877b456de0878950b94adb7844a5b9bd27f0a4e81700e6b1e872b650139b8b8dc0af58cc300388060f252fddcab97dda109a86d7001697f6a3b6612ba5c2e204bd1ba7c2693c0af921e2ad285b3b311fb5f44c29e356d109309341e705569b111ae13025f2fdee98770e37c37592b4d70d6511fbe7d2c446b281c6cecb1d83166f39d31b39682a9c7ca114ffc5c26be3c7d4c82a4075b38babc07be963e8ea1d06c20187839808105777a1783cf32470fd528148a417b255d5e10e4c2b5f25fd12254a9684d9f1375749ee0de5a4061b905931ae269e4b2db1bce13dc47fa008b13cdd63b21e7f1c2abe7e8d0bcb10f89feacf46340a5cc4e9b162223de007c31f2a70147b0773f256046cc66e97fbbb86da512f414f4d31dc20296e5bd2c064cd5d28719ad08b87d8eaa5f99cf132d465aebb916fab3e098001f70100acc7340453421f405f4534f174b9630e1b5476c6c8ecb6c6de7676a64b686276dbedd08394f81cf6dcdeb84d850b61c191723a40530e78a8cbf9d27328cc7acfc4586952ee0ad7c9adf867fd46cd03bd236030893e57138c73a88a8299b2edc05420845245f78888f25095752197d820c306cb42b0087ad55fc646bc318e949870901b8017f47f49510cf782603bba52a09a8a948614e086d723e9b1bd6e2649bae15ba66a29ad7173f19d1477f3ba8531dce0754c95009fa806f377921053c24a0f612598f1da56e2d3e51c3a0857582a40136ad4570e3a617f51a93a365d9a908c9f545eb589b584d0d832d69556f10081ec1745cc9daee2da686e15d0dfb04f736d453c32a3c9ac2cf6258a79a84984be5bfc0abb2f1548297bdded19957dfe207907042e75d51e00096bf5629547258884a91ecd7f7275fec6c7bdab976fd416c1bbc808350d3a693caec0b3f865c41aba1437239f4eb3b1c0c0ec109bad46337630415239b5b932001f6474a141500ce0e9e67282e6761dba9a0b6ccdf0af3ccb937fc801e639c3bc46537308364e7f1e5b667343970ced291c1057e51e9fbbc0005390bce1661d3afcf972e4a473bd184a1cb900201884a2b11613801e6764b1e073231f94a6c9fbd44cb1576583153c1d5a58c67f9e15b8944eb304fc2cdcc2df30150c8e08ba23845553f4f6cc807b0daa35671f53040e17d09dae4aa1881409fc51fe58de2001f18a7393a9bd642b2501a4d0aad71aa0f96c4cbcef7af34e402abb1d00764b3c90064644d820eef22b72251a7eb89d4da2eb0ca97c5e4bd954cc0ad5e8b3bc2a961f60e087095b1947f5517a7dc331347207c5edf228474c3695cbd65042b0a46da07061a58466301196693919603cb535505e74c49c01f58749ad53b61f63401884be77d606b940c01490074cc093cc59e237546fe8715eaa69288f0f7ec27a7768fa3aa634077e1bcebf64102818dd675bf4722e095a8a906bccf1026c2c56476698ceba92525e2c076fc0ba86d0f59daee86dd880a2010d576f5f61fd19bec6f0d7cc65d8fbbc3cc2eaf4af6a1f39884e5a0af0b9567c459f24c61bbf92bba2b9ac31483938f8b81f4dabd4927dc6804f84ce7121811362992283192253892200be22998a648c0d9405623c2d5442c515cc6dfe7484e66d9bbe9f34b8490c6b32ac222143deed0d1a2e9bf814a3aae783eff399e6712c11af8d907182642bb6c9a80805393447c765559ed7f7f126a7c1b595d02647f1f2c5c4e05cbebf0dec13d1c10e0157901abfe3aec99b3bc42f1752ffb1456b983488abbc725130a08cf11f5d21af47111f750a163d52d216ae8665ccc7c767d80ee80fd295cc41df2c37f983cc4dfefc423d3f502bc1ed7e10ff0cc481d71170f9a18bc73a1b5ed102274ec2364e8c34c5b2f84f878ef821281e6c9546a9109db2878bde5128c4b157c6d32f553417429051224c73159131e152e5ba65f7c1e06bd074094cc704111a6916968402402b1d1ae358c9c6011cdf2d4520f03ce99b866a482f563ab7b54a2bb677f7f390233ecff8785db148e9cd20641220dafb7c52456e4e3789433447c736a53824402a6b25bb1d7d215abebce6f77245bb0b0edc399eb7d19043d98b0522ceb71a27b846b0b0a8782f71d3b4dabb74028e2764dbc81cf3d0522c5dd7233feec3293114672a350d1a15d2c8133cc47392599a0c36e792f914bd424e45a5a3f938b8d7a81fc2f6fd18b2a8b75b57b10b0f890c46924e0942ba121853b5ec155a26c2fc30727766b9c25a0c4c7200d334b3e043bd7b4553ff89488f0ade705cfa8d5e0ab0c136ac34fe4739a2d686170bf9ab6b9bae6a0c9d3a7df3c5340a59184d66dbec882306a1352837a3c3315c63628f56551c8bab2015a5956fbb74a608ea36edba74270bd9a864a47e83aa8d9e4a03e7166abfa718a17b7a61a8c741e3378d962b42e4ce052eaceea7a5fcac2d8d8d7241fa30e861f85afe42f07d791d8e487dcff22392fc8ec05cf7e52427bbf240b7bf8682f5bfbe611ba65dd57e456dbd07d8af2bd5ca5dc977ca95a28e62004b55c095825451b742adbbe5609a2e00c608f78a9f839b9225b1cac0e752d0ab748ed9ba47a3116e01056e9fb75cf48c24576a7156cfa29fd65ecae94e08742bbbc611e262d1345fdc1fc635125183978250c56c05b3951d281e871d0db8a55683c184bd5a0c4f923e4f6a328eca6e5430e1a6a87a265f8e92ab2af9ed7ad76c868ffbd0a0e53d00f56b2b0afaf9da62bb586974408ffc4f44eadc6e5c33fffb861e038cdb71a6b61382d22341a920c700337c69ed5981407e3dd90250b0f9d9c12e404c9792a6bbb29d887a8c54ee13256198b670baef0fe9538cc326245b42a1d563a0298f61d5b3ccb7a5a805dc12400e562a0f62e1d757444e890337bea3b9333b823af1e4f5d3f90cce296245b40b4d22af28bf0f867ecc74ff45b8fded05b234a70e9c2ae092214e2da79f4b49244761c96ec67ae8ec224580ce275d8cb5d1c3ae64099f01fb6b548f6cf00990b4c1c09a373073c06bd94aa255163a546d3ae1fdd7c66476250c99e4b722b17b9b0f3d1df70cd71bed9a4973483d716eed4e0c0448b1e02877d5a7a2712a3be4063409cf4c0ae73ad85d406c7b17a5c19cde4f8beafd8f47d2ecd342f5ca169c94a677473a1194b346ff3b3d9322869727becb528a95c9a93ab8b36528d07640b593bacf66dcd1666e6cac8804b9b04bf542efcfcc6d24897db371ae74f36926d9de5136234d6e32534f3f193b85d39aee26dadecdbc83ee29d769f442442211de1ccb5486ccf47935fe78f64718649efd64eda3e26d5830241e7c04a9586e6f73f362f80bee42f0803303c141ba63a6ce965c993561db695a1e682cdda72dfd3b2bc58e068fe57aef48414551c024dabbc611e2254044d7fc8f92369ddfc8aaa260088270f1432c4e8e5ac458a5b400c840e16b06c6787950ab2975970f29fb98ca0ec8156e7756f9e5c32616d1188c4ea836fd59d34f2882c32b5ee209a56f296ac67cfd83cc0e4fea999d47b1b21ebc7584e00cc15ae195214bc2e7d337e3a9990d0b32b43d3057876bb81b632c471eb8ea2d688f3635c00055664942a49f16863018785a68d7974f843ff67d5fe7f18a896549163742bbbc45baa5854982e18b2a6dc8a82503afb9e397c85103d4e37e6c4201120e6bf89bbda902487d405ea74b481df8dbf2777abe4a38407094468508e5fdd49b6ef10efc95e02501585197a2cd4c4f4f722a113c23d4d08cd7cb78b49a2b91e002ffdaf9f3c1729242fa6269725a5c94bdc2a3031462502244dda5506c0914ffd8cc0d2c9f1d448807d28257cbab1edbd93ce6b4f7a1db2e2b630b28f2ee14fd21e53d89587073c9e49ba2a4868f11ddef054e1b38e23c3c97d93bad23b3c4be6ac14339057061e4e67e599e7627cb892bbfd10189fd6a1f44232cb03fe6eec19e622550d1fb3ef77607c15c446caa1bddd12b0e115dbf5cd9f0fc69830efb6dbf4c2a48c8239bb60ae2ef4d27cace65298433aa81e64b615f727268e656d4a91fa6ee18b66acadfade271937c43d5f4df925c1aeba18d6eb14f82e38036c2cd22a031f0d102e52c053b09bfdc77da58ef4ad23728120a9a4895006403a5c73ddc103482cdebd700aa2f55ea92ba34c22f73cf09468347fc5b5505741eeb44ac062eba7665f791a4bb8088b69444185d648188a08ddf3a004b67e415393e2ff07de4f3f1d20a78e61cb80907b40d32cc6afc9cdd56b8bae65385f2e4409e053313960795151edf8b98baac1691b7f2056b60e8dbb7d28e5ac544a787d0574c2ceb5ec0281647220a640efc4aaf1473c68e78da17b0c136e5e81d2f61528fc05607c4d2effd7ea591d6415b3c83390c4efd9084da16947c2672f494a5a1729e03206965a358f9fabe00088376c99dfb1c78592c43983520f5c36cce714433680a639c6e050eb96433d1731a4047344e055155b27b058bb5370ee49f0c4d2bb6f162b70a47e147d4237794989f1791ff940ab3b9daf7f7789acde31fc20b4eb9f5ab216ee8dbed1cec6859d38c4e4d1d0256078524be302884b01c7b907d72ee222f09fd237d54e2cdfe3a9dbc0da6595674bb2301045ef324f60f4c4663d7154eb101258615ccc9d29b927380337e9a05752a9b3f5df893798e4567fb2e85848a2f0863fbfc75ba1df09a09c1c7925495109ec360756b26dc1b1c811a60f2923e21c30280a145176332386165e49709622ae8b0ccbbca36ca0a1a6a3b0a692885a7e2679cae4e39699c4926af0d8c27ccd69ec44ab9e81a17a51636f2b041a62fea15c988fd3a9b3e5ace342a9376b221b3e6906fd567a8a3f7163241c4d5188beddd7aff489304aa5936f25f1291586347d79c50d412febc03c31d7d8224949b8a5c0eb8a2fcc12a42f1c7a90d0460efd84df606e4605dc569ae305d7c6bc5d8fd5adfb343303a020b267c56d8f5bebe57e316f8abd3b69add1a9268e5a8ffa24916062c5a9fd10e6b26a4cd74814e390d284a71bbcb44f1239ce3aa5c315633fb46aa2482cb8e4a32424896c456e7809568acd6d4bf0cc6ef89b6566c636d8c8fdaa89bdfd2d09ec24910df8a85ab624a2f28c89dd84013739815e902846a5d1e1c3287483e545889cca22b947297a5dae3ef218b1e7fa7e95487df79ca0c49d1410391429b0162b81d2992ee6af6c46a4db1328814d64974412479d5228feea4a69d0f5f1acf18914a4ca5562e9993f4ef8e1dc332ad7cf503e7060b250400bbdf916ba2076eee36b543272955ce07ba96ce0887c84be0b0c7dbc3b66512ee8de9ed4c79c00bf8ef772d81299df68716fd21a1c38557bbce80e9a114b831b1d14bd66d2a8325cd6f18dd52adf1614a6f6ff5f28cd3b007312d3a8ca8c3e8d23c6496006e6aebc06477112690448980d447a9f003102f8c746c0823d7d136f49a0d86cbcceddf092d6e461449142c98036a1b98053e4dd237919e5ce0fa5ab06cef68acc802cb0d7711d5b1a582131fd38ed580e98965f9fee90401015a38d61ad7c702484225e2779375b5c49a28a8e9b19128fe023407f740e52c1f3bd67abebac165a181a3d2072666f15970bd5daf93383c375fea1864e3d14348f07f7f3e020d71ddb74a86f51870e313382ff7d48018298f42307974df04ce845bb1c0caf618433de5143c517a1585a8f52af3e9c90c9b1026ff7daa4a6a43fb42f89502de936f6d519faa553181bf237f4c8af81d5a98c2c72d8c8ac6211d59f675c109f2f6e7ee1feb45615747bc0b8c89c895678048b79c2a7fc1bb858ea085336feeb17884ab3a7434998bc99e9206b48acd8fb0714d15a1e0be6802369f616860ef4417308d88ff6d2397b733b07bcd0b69df0f8ba69ad0fa9466c11ab3edc23e9926156a2d1ac94b673313e739f3e1a68c278b4a4c2557a3186683883103a8844ce01ad5515358aaa25f13ef37c1a64d892e8650f0fc819af8bb1a7244e2f6ef4492fbe1468b055a8febecb59038a9f5ffee08b6b68e9fc94f4e61ed8f08ddec2c317bb9666e8fd8269edf5c1162b40a41e54ca01c28df58de65fb667e5abc0677cb3be337eb698eeb18b130409bfed1ecac8e6624205d863bd60a8572fa21d3d8142b1b690f7fcf66caac43ed4acfacbbce9edfdc0c9ed7de8acc6199374573b098373c0165ff58587d2ee3205acff31f0d04be4bb3188008e277c318911c8f50b6dcadb01bee9363e8568821608f060d6589935a52a8dcc980ae5b297db25d5d881c0d16ed227f3eed7b4fe8589bf1f03d1a02c89f30a6f46a0c5cfdff494ece88ed75e1d00f7f13495d7d25eaa60838435508837a9019406e6e2e0b5c0ce9291540946437828edd795220dbe189d023707f5be3f8bc74d059d2fc2e4609f3850af5a05400c94e05bdd991b96574d8c3312ee0da39345d323b657d18fa37cd073d4eabf813813842025c00b6e8fcb523402a76e8f85e90029203cae631ce20bc6b8b8c0c839d8392990c4e8a8d8124efb885229c674b3ae2fdb5e55c09d41e2fc57ab482d51958ea6bb00fd0806bd43dc573cf5119cb90179fbd161f3fa3b63f49b4be3935aa79cbb75dc539d5af3e2af9f7aa6e2caa3cd7ebf73b36ff0329ef6b1ef709aa4f0c6bc80fab16161920bbcb47317bbf9ca447086e3029dc04d6af619745e0e9b5352ea7b45d4635602f38433c02d22b102b511e34acb110ba50c8407aad88147a8a74b4563c3fd8009593e73800fb2ca621bbb007ec598768c08ff0018e207467d7c75767764d6de783b54622530a288213d5f246c05cce42ade08430c83da7f663dd8cef4537bc1de491e7769c317a2840b0215ea11e49868b8fa85c5968596290069abe93d765d2bd0df35f3ac54420cc566093c3f8604ff560c6617c49e260dde750b23068685e24f38d4a625a0854e00a4f8f4c372099bd8cf4053a3139641ee2f4e0da829f582a0342e3d2f4c44daa549335739f041529d98da68b39157b9f28374d8024f7782df9709bd611251d87aeb994486e32299745568dcac4f71b8fa52dd3f7bdbd699310402a60389a98e87f8408825d1afe8d328db9e427b0a8cac7012264354c0417b6f79bde79fc245d064782bd945badf6a55c5bc28b4a7f423e07213fdb82f922dbd739bc76d3274dfe9e01070620b85de0ab1caab50adf8878a65c14fc8477ad21b521dc96428ee1189cd957341dd71da6fdb5cd8e2ccc6adc6d4b938de3ea8d5ab9b4a68650bbc0c39af4c4672eb3fb7bd6504ec855dd2bd70a56ee173b9ac60013a401c9df8aa9f1d5cdf6dcc68d01b5363e219f6b270d23ad3e6fa5c0fd92f2a5ced1b5761c02bb5119d718a700362b57087f951eca2a4dbfe6fdd400de0f35a8b00d07097a49af69c9d274cd430407ce43633c9198f0356f43549b8a1b1ef7ec8bf59825db09da1e2543c812467e1a052cf9385827980512f74baf8e99b26c25dd187ca65bee1457841a1ce666efae8174c3ec8260b51cfdac0869700355dbc6b2340f4e3297217d60727af370fa53749fad8e6be5f1d2ef46021285075fa2850ae7aba1c58361297bb00224d5ceed734dfb55c5024939b6e151de9d58449babd151eacc749ed4ce8d72ce6ee4ac744981e7cd7d4d5190ddda336a7d4003b91deda494d709c05e966ab61bab20cd94240c65aecd9ea83fcd5c8848410c2ecd3846d8aa74c0d6607255760d469c617dc5a7f1a56cf012fd5ba6236286a5f25015240a7dae59e2631e8b8e45695749b7fc439f5ce9e506dfb7fdc0f8b641a24ffa04ad588aa06341ac803d581998c7e9f043140ee45c49a5fea13837acf628272dfa2e3725b22dc68447b6f3417dda8b6c18c0c841e9d51d2ed9902cf8791d86f0b02d36562901d8f2bd94de5e8dc42a1519ed79faed024f37d5b594a99e88475e8e696f331a8bb11b3b08ca3c9c83bea8e9317c6bbca50ac8af5895695ca0aeed4c0e46251fbb42c4c2b443859ab7651c485b6b5c0fcd2522013d6c67a71aa2f694cf8181210080d17ba2d09350b247f1542733da1340e71307e3e65455d919be39b1c0c89a706abe4c9071d4264260b0741ecb171f50ecb4c7af719725c814f3b659540dfcc81635ce002a881953e73ae820a3f619735acd5563317c9ca2c9cfdc6f1186cbc0f3a0049da9a48251ec4a0a0f184a94f284c4dc81775ff80b7393d55a9c9718048906ee3cbfbe4afa98d04dacf34c1f6ae8178a1dcf4dc506302a47db215f41c6882fae30ffcd4895c1fc29eea0ab4f1e408ee524238fb89fee159a4b4689c93bcc996bb84101e2ae384357ba12ea0a385ee8d732cba454e2e2a228fac4f00885ddfc8ec6b41b71751f54aad450c9d3b579053fc5e2ca63e9e218670d03ae611b95ad2e0ce99d121e838e8da19b4661891e1dae187acb65e9e736a933a69a2007d99d0800d4f8d80c99bc0104f82cdeb74e5d87266f338620375ea0eaece3e8f0c947bb03c242812390ce5dba87233a5616c6ce4a4511b521ba01a94df9d09b4797cc31618fd31a6f580b459abbf2b85a9cea7df23f83c7b7c1022906e3f1c732c55892a1c05913dd8a3f978479199306af9909094559ceccbbc9b478b803da226014acdb0f7916cea6a3ac8cc245521a55b43dbe88869635a32d3e888c2ed31e57e7ca58674f04a0f0c84b2b44b2ed303fcbc786f5a87ef621f54fa3ce627b43f9ba507118ed287fadd4a8c178157eda2bceaa382ed7d0fa34540f59398f6e5383839dadf0b48894c20ed87b7ee5860042244329e4c808ce5e415e5544f36e6519634ea9cb989404966d58ff9abdc6c1bcb192efbf8644daf695f67126e35bf7044338a521e6043e3ccefb4a532e0edebb2afe324fc5b881435e8aba566e074a63a80405365498bdafa7d65224dc872369d3303ed1990b663c7c121d982954b72d01eb2f0ed1758c33e128de63fd98ad21ef4d3de721718ed38b7da63c2213c504585a86d26f4d9078d8e3a0c2955720104ea0cda5e0b2e17509313d95e4b6c0178e7b1099521d60e2a0ca77dbc45267d0505c18ad7bf06fffa50b9719fb06ec1570abd83112420b56d8369932564bec6ea213653f0b3fffd3653a67384096945d6ce04b20f045d231b2137740d1b446251bb59a77ed0f6db23c5810e716901eae9d66d09517edcafd61d6bab50649d469bad4a12eb7ca782c47f4e17d91c357682b01613cadd94d3be5b12ceb853f5cf90bb4388244f5b4e27ffdd5b8ab7e1846c42b03a65b95ea8dac8af66cc8a5e03ab523fa8f9ef7a3bfff729685e6d01f2e1a8938b46671fcffa88036e3d8ce186f7dfd5f6ad653c5090b1c6b05cdec584a9ff3e2dd854986c3dfa8f96d815bd065294ca7fd73ac85bd28abb277f003c4a674fc09c0df03827755d81ca4bbc36fe40b34385e09a147cbd88f4591018c6f3a05581a978dd576e92cfa163c07ff09c03403d0ab6df6edc01d1a99a77b8e9f5e7ae2c3fa022035e545ed8b272cb1c61417dafadbbb6c90aabdb0b71b04216ad1af0fc25e295400c5128d3319be35145e22de31f8d1af06eafc8416f8dc7bdabff8cc27709b3a82c164eebdaece5ed7697860f9a208b4df0d2d4878de9ba80a7fb4d19d42a24a8da5efa11bf4b9ea2c87743c06333f05402ff0d3c46d35a893683e0f42338cacc3794820018585e55eefbe0da44c26e327a0a231a7d3b0202a6e3b6c0023da06519d3f460356ea1201e45bfb04b0155db5a9a6ddf9fed77c31b111b4cbcfbae3de8815786ed0b5a5dd1e5137469562f46e83c69a7b2fe48c652c50a8d989c7324f9692319cf31701336e86e9cb1c2dae7d49838f4bf155a563ce1995b22499f63ba1095b9d11abcb05581a963158c5cdf7ecbcaaa967a478933931ca640100faad509c258d287a80015961659c9fd6998a256c7cafee841ee0d07d71a118b583f4e5a72b9c741fc7569a51bf22b2b5629364ae0c30be3761ce0d19de88af904654cacd8016fc23a1f263e5563f39444302dd93f3215f707c437b886850b14afdf5907783a5c6463719a77ed6bd262e766b5b962f2655c336de6e2ce619299a26f38c01f3db9181d50f6050f50513c029d1803300345b732c5ad8933137106551307f8e38ff544ebae485ab5a1a1e1c635e88171a98ad4dceaa58aac42a4fadddccbcda132f5775cc50df07813fa27ad3057f58ea360b8c46c208a29a76b21db7d448084c3a4aa49dbb0c7afb078995f832786c5e3e93eb6756a677bc0bb48004accb887d4e4521f91a2cea4262bdcf731c3b013298b6c48a59352cb85034ac57b6b0a2bf9dea9f1fd28be2f3e75268973ab27ce54e71ab90a6d55d5f7f8593144207e089462213fc7e70394e3f210b5891ac145862a9ca86bf89ee4c5b4d5c05eb2a4ef39ff4b8bf28e9ab13809735d454458590fb96df64cf8fe493b912f6cda05964a9deb7c8b6c52bced696e03c1b0df14ebc2d38253d3c9c6daf02e0288449316efd99a91538205bbe77665c2ecf18cf9d5b6edf725d0d2bfd90efebf84b4e16a39cf1e859914ae7d8b374e7d4914a4e81ded7a81a76d8e3d47102327254cd5488ac3d1301a68942abf739db645ad2812f483cdefae6ae1733191503c180b6dc0a00f317302d93c90ce7a44b2e7050aaa8b8d7e3787e2135856fae1df7c2a1c04b205c35545ffa145678d6500bae8b26e50ff566066bd7323a6c38576ecfd22c8117ed612e8583a8a888378156b21b9e309309673b83b053152f554a5afd7d396b7dfd53469fa65d7def613c99911c7eb9a9feaf0b71e183dc4f27b40a788ef14226ad9dd8f7306e63e1156bc19b0de370bf66a2e4b42102a00213f0f21312a73b012b16f9fbf0343045600009f07d637f37ac9152125777daf4ce9f9ea26d322e97e155c5cb994bfa74188b51434122b6028e1aa76af94060008c6c9f6f739b30554340d38b95b5b7910c0ecaecb9f9145d94ccecd6d3b6d7d7db348811e2d3b73d5da3de087f7efef63347c99d4ff02593fafe4529743c9ee9d3b42f39a95dd531fb4991b542a2f8f87086ab16e1593547f0ed2e6ac2b38aaf415138e44deef0bd45370f8587375bfdf4ead5317317a3ba70f96e2a559aa823f2333ba2f4166632ed139a8f44de20a2f9817baa712dfbbffbc8e91ef0764a32be47979cff998f324053f7bc5b521d52e3315e561314ebe8d9b181af69d25a6e761d164c43c0bbdacc8b5c569748f168c4f46f30379c273e9d57de3a510483e119156ef605daa251f13ceca2ee56fffbb5e582ed5dad167a9fb794b13ad98860fc245a460c30d84165fe65754089754675d27f2b2206f1be348c398b0773e96b4db8a8cd841e163d0d6f34cb21fc8de95a320c12b43006f94cc088407d525d3f90913d93b3f610ba758fc05ba2bf73f61776833d65d7ca4310648c5864024a8c81415c3979552b27769f6beb1f7ff58165fa53bf0fb3ee5f212d4a5baff604c6ab46ada9d329c30adddb3b6fb4c688613c436e7f886dd3f45cf4426708379e34dbc185ff6eece357820ef570ddea1bb44a670235224c4e102529558c66ed3af19214bb09eca62098510bcda95323126dc870ba02b6c270f8f32b4c204d5bb00151328c3eaa2d853a511ce35f6ab68d73cf6b922a7d2849690d4f2d14b899f33737ce9b7dde3874cc95827c0c93e1cfa5ec2b52f14c275998f6810b903db66ba750986d9a9397114d41795f2cb7c2f0f29d29aa850ac051c737bd706049d4a37983293eeca7483fdf38aee9d323736dc0061dc2cc90c4fe943afb8c7a6c48af1ab174469dacd3e2393255ce016dd96617dad1cbf192ac3a4bad9f5130e76bab322100a21d2d43e3ad0d872d3c8acde3194edbf22bbaba2607e22001e9c24f16834379a8adda23fd14a5c89d2c4a759f3ae21b8738ee441f0631498a2f65214f70a1a485039a5f470bfe8e9d2e6677f0b014faa2f7a8df5294830de7c6b15cc96f9180ff508553b6964f6441966e33c95f36ffbd733d09fd1852b9d8b350a716d583d263af396f178a5e7812bea04fd20811acc53b9fc3c3b406be7b2c0f3089289a16f0155de5cc9de76b42914bda52b1f59bc5fd6758dfcfefc3ae4f93daec8f413bd23562d81e5c148f4e336c6d5f0c5531c2af5909dee8c638e040801048a2b276524549d727392c059331dc7980194140523d61cc04774b6288b06ceb38320a62f1398e30292e58bcbb64a6e6332c3f225111418fde9e026587a093905dcece4471844aeb97fcc3337f496139d9518628c362b1ad792f7e5b874bed837ec1c11b7097d0587b2977bb675da3b51ee19eb30f51be963ae2d10390b0d1d7892b7a360dcaa5fcf5dc7d9e90d801c0ab4f121a8fb0dc1e223d312e4f705f2e5c1008a74623840ad6b009476e7fd13438749b1104628c715371f6a32238945b11d42bc3a11304b15c9ced4cb07d9ee49ce98495a9f9081ed122374f19ba0285eabb18c8121c8123a0bf40166e3c5e7236e456eef743a4492b5b9e93f481e2aaf87295c3580f621ecc0d4bc42fd9ea42ba62d1477044ac177ba5a305c15271d994da5d7ebf57abd3e778fad63cd49eee2ea527711d793542aee8b7a21250b13bfdc5f3ed970c0023080c381011054cfdc0d73794c1f56832d58c4df8d642b002813e6dc07a2bfadad19d9468834b237d95bcabd03800bc00b440b2dbad0b7cd062e763eb0ca068634dc41dfbe1d5996691fb77db1ab3da66338fb68ae3679f4856a0f6177f942b42b7571648d2eb6f0674ae559d1659c3bd93563bc9af14e95cb55a97ba9fef1e20d27913b6f830d5cec12362719261176756c4e264c3870e78f3bb387530d3d486247768c58112b92e4cecfce07be31c62d79e7fbc793b3639186f6f9afb3dfb61bb80da4b2433b0a0eef6af7ee16232b0343151f4457fb022e056787495c1abbf1061a0fc714b6a8128dc8f0584295188b745c2ae5e489425c0cafe4913b9d846cdf981b6df7a1f4582ba7529a4d7df4f19057023417e471bd49e9a37b42421a69442a71a34f725f8ce1915e12c3eb2e647b6409d3c3db0937fa646cba9d70a9361d75ea982ee9664aa9ee46694b85a1e4a704490fd7c07aba0898ecd98a80692c7786e8150d617a30d513c830d6a49e4c86084be7a7c0c8aa148c663202c3a8139d70e9b716dce8935d412310f4eea76fde923642e970949cbfb5e06caac2c0c04c18cf48eac867b8973c76374ace2693c9e96447a7d3e9a59bac0918f2b597ba1ca99f4660c8a6d4519cfa442f919e9230182b61301206137de44eafe8777cba1ba04edb4274c99b91d6136968a7a0873749dd44de58100ce5908cca215cfacd05a7f5d52a9f87e83208aad73eb241f6f4cc908d301f41c2d4157ac87530dcd62e984e35e5a7ea46cf3d71a94daf44f45e8fecb91972e941305403199c3ebcc36097f6a5e7222351e8f31c37efe893dcc7895a76a2777743bbfd06721ddf548a879457047a9cc3452c402f7bb8d59d5e22e11677ca973b1475eda7f7ce0dd44fe76f3b7d25a7d34b523f629a1c743b3a0fd11d8d6e3aaacb516f021e216f0a0c4fd77eea72943e3add7b0988f2c3e8a8a34a1f813bb4d227bae90b250d2e25bdf4c921aca2a28744a74fe9d08cd26f37b08cf5061ae2eb8956216ff1b33de4ad60db700c0b64d98c3f46f4b0577bbf7f40209b65d916923ddc4329f7f49c6edc44a25068d467f042eee1420f7721ba28a5d48ee136ed1b5cfafef1f8f666ddb90e071ff46dc4953a7688a79cdc213ef5e1e11e4672016fbe251d72e96d26a7055db316a40769599665da41df32cdc60c8c399d905a7b2ea53e34ebe15606d22e4776fecc7ab815df48da27d2c8b2ac87529ad58470360ea685e88155f330ac9a9a5741976010ed02810488ab1957cd741220aee64150827216c1aaf91e1630c00ff338620d1502441af43d1ecc3ccb67e7559e70e57f1e7b6214ae7c98c39d991121224bde66aa842daa84ec73e704ef7c584d9c72a39d37b66b029735a48e6c999f5e9c40bb640d15d93253150c539ec737aab21a556c8095bcb24c4525b6b2f3559125b6e8393b334f7a9f9bc447e5cea9e283bc6ca50fabd0e0f5a9ce953a7332b34fa60629c5e59e95b91f5458a54a39213b0753183c516140e472d65990c7c6cd3b41dac6795782a15471ab8a3e41dd0f2aa108e44095950f853e1e9d4516b79ebf1e0810c410444cc0474d133752f0116ef127af8a5bdd0f2aa1e915f443052ee8077e0555ddfdc00781e11155d69038bc7b3979c85b63943558891b6badf5dbccaae5326b41b5ce7a813009256efdc671f5db516fb3a0b973de3b6bb5d6823c64ecd6575046c5adaba85a03c316559eb225144ac8054ae0090d755c9a4ad11a73297dbc26df441f0f1f51ec982f39caa5c8e40b2b10bb26242127d4e40beb4d9e7de1bc26e0129789627e3b293131e9ce1d9844d439f19e455689b2ecf42652113763138b42d99309f5fa1218ea30fd7493975cf4279f350143eff4454e996a8966e0c62cf23c91492a95ddc42b79e8a5ee7d47cac424756a9212a5401e25a0900c94fec3740b46537ca12f812a93aa575d02dfab265dc725fded7a5fd52ed34b25d3f652c874ee3f4aeffee374d07f445e4d30b47d1726ade861dad58161cc258d4e1fd3a36f87e781d92938bb2fd52b3ef7fdd840a097d124284dcd4e5bd8625cc5099cf6b3ff989f608c979026cacc2fcac88e63e4217e2271c308445fe6b7ab2fff8377e72afe877dff4736e77f343841af57f23f625cd5f77f54302c2203bd7bf948ccb79c6014fc06b71879d2c8497b2848d24ffbaca735a452e6d8217f822dfa30abc0c0cfadcf7c983a7aae7d36adf5bc54eafd68c4f464643ff05cb8c543ca68257844d06ec75537e7c5967ccf998cced975d62ec7bc7df6c95919bcd76e66de773fa85c09ce0e87fde411cf1d0e06e70e0af690778299123a979ec814a3da3449d4665e0d594edb403b6c2fb8ace3108b88e5914696e4954226eeb4a1b4921d523a98bc386141e126b7b21636f477f07674ec3dd58bc5b2d281484faf28bf7a455f3c7edbe1ab219f131dcedf54ba19e6de6ee026b74c3a7cabd55567f9964337c302c2f0562c2b1dbed5599ac562b14c67b1582c16e92c168bc5b267b1582cd69bc562b158a1b3580360098025c3e2ce625d560c2b00aced2c16005836583db0b4b3583558345833583b9cc58261b9582d960e67b162b064b07860b15c582f2c18ac1767b19ca5c2ca81d5c26281acb35cb0582c560aeb849562b1582816ab9ec5621d876e86350001c8dc980000c0460f3568cc8071b578901103c68b4b4b0e2ae05da49ca450d26463f3448944faf89a4816bc011c795ec8850d2e4020100804028140201197d222856f28b49da09c741ab703aa04b5e9c07707d00b5289546badb5d65aabc6e28d3cbe2f6ce53a2eabd96c0d748d71e070b9171f5ffe84743b7cb2c74a5bd33161a26157e3d9d7cbc76b878fcfb2fa22abf815e4d2bf5ef42cd0b715795fb00eef1f4f08e8da8d982fbe79a70dbb367be3f5c9fb8d05bbac0f0fc8a5fdeef70ebb36cbe3f5dcb6825ba8f75b0a7681ec14fdee770dbb403648bf81301276f1cc434e72b110409f7bb865dfe71c76b1cc43d6b9317d26c22def7d7e825d1c00003c64296c9c6db815baab7b78c8b01a34fa120b69845d3de3a13c2291dcedae8ef58a5ec6786831c1ae0e6513304209c50eef170f252c94af1bc61fd77cc5232ae00dc0ae69430401b8c50f63cc458a8973cd58af5a2cef6eb66917e835038676003602b0b9f41c6b97169301437b633120d55ed6a65df6d52bfa0080a10500f3d8609e1ec0d0d678d178d9196068616c5ca0cda535565faf7665b11618a678e8988c8ec500c3148ce679691e9757cb4b65d3aeec156b178dbd7a459fca010c539e0a187a1968933a18a63c1760e8c5ec0a0ced0427cf6bf26c381bce8693c3898d0db754a7df9ee0d6ead4e6aa423e38a47258c981efc9c777db5edb6b7b6daf4baf8252c14185878787e7d2df60026f001fc3a68f2feae3bb8162b158ecd283a47f7f0c835eed02d9f4eaf502bd4036201b900dc8e6d27fd6850d2ef8923ebe5aac5d9aa6699aa6699aa6dd4b69f16a97a6699aa6699aa66936844e504ef87a1f5f1b6b97b5d65a6badb5d6059732493d86edab5dd65a1bfbb2b12f6b636dac8db5b9f42d365409ea721fdf1a6b57adb5d65a6bad356507d3a9beda556badb5d65a6b4579412abdf8f8eef0f1cd62edca7878623c319e180f0f0fcfa53fc9bc51f66a576693bdb257f6ca6c329bcc26b3b9f4261c128598c6da457978623c319e180f0f0fcfa54fb170aeeef4d52e6ad32b7a6b5fd4e665633d1bf2f14429fa92ce077943ac2f94e4e5cd871bce862305b7f8b4ab696b3daf53ac8f2fcbc7b7d2eb40dff4db0a33f3d7b3b9d2d08b2fde15bd8a7e853e0722971e87292ebdca477f03cea507a1b8f4df1397fe1fbd0bfa16402e7dca073bf9e84dbec8b4a427f2ca54ca217da31ebc4fd4a970df52d84a1bafcf46d0906fb8e15cfaed05273b2dd268f046a446f00dbb9a904f64d1675f280c9145df59847cae8eb4018918c33ba1be498e06e7f2e3e5c090e5c6ee3cec4324baabb8b583bb8a55717bf7d1b66ddb48bbed7a0d5e7c7de43ef088be0fe8383046969041dcbbc730c8c39e7b77fec2d9cc85baf321f592773713232b44e28e9ea1dce4278fdd8d94a37c33f9427a4dbe49ce44883b7aca02eec8e4281fea275fe9b1cb81c345994cbe50476ac46374fb928f87cc221ee12696d1171ed1f701a36f277d3c460f69ee68863790877d3aee8cabbeb0883b437ba89b81016178037836806ccb6c501b361ec302f862aceae1960d1b32b3f119df667c95879edaf88d7a8455f3363e1bd3363e79eb974111e3d60ac80282a12700f08879eb8ccfd71e1b9fbc333ee3965bf633ce714b7ec6bb6e6606fd324a6fd40aa2e05879fdca69ee56ab95d33ec668eecacc0ac5693a84e914dccabec9092b72c3fe99b076754eafe65bc0d05acf638161f760c1ad97ecb95f23e156c83bddb3154186ac21e3f36d84d8ba9fef25c856eb329f6f1e6ed9f87c1fe1160f311e6a498835625c216bc0f87cb914628d97cf18e6e17ed90ca26abef5653890aa79992feb4154cdc380f16c07b2653e86e9990d62cbbc8c4f0691359f41215be66dbc80191551356f781684d832eb73f84b3d0e2ff52a8f9d0f2dee4b4d41b901e524b2eac197faefa5fe2ff536bc4456bd8b97c8aa6ff1524da5941229b2ea515eea4f5e22ab1e06189a3c918928940a75251d175995db501be804d24c9a8dacfad15fea4b350365f725b2ea43edbe6c73069146bf3c9341a4c19155fff22c8a484346567d7906c57ca101d218a650744ebb7848afe6ade5213187def012bc8e0d09a912bc151836099809deb652c3ad1618cadbfa8c9482618ff9f24d5810aff57086f43437664458f5f2ac4837f372baf22c8755b3f518deea17afeb5e3773bf795ca7c00be7cdbd8f612905ab70b6e664778387532134687c520a5ecdd74fd257a4e1430ee129c8e08512677ed6ccdacd64200caf6635a748eb6450704bc6e7b39b4823c6adbd2f8f6c93c896f997cbf8a88f189fbcf406c6e73b89cc5329cf6b992477d25764cde3d0f8329c5ecda984279f3d9c37d4751918df0b0f5f6ba6f518be1f8f18f2eacbe9bb9b99416f839e071c6ef1f018206c9cdedb50e601b8054300803c8ab83beab35f7639609c8228f21e8aac2b0fed4591a00f34f2050587fcca6fc8afbcac7c059cf230be70e52e19f436e879c87e5bcf7e9071dae5a020cacbe9576e9f7d282f97cf603cb4282fa00f34f7e5f22bafdf5e62bc7c28385e0ee3375e0ee3343776315e0ee330c0094e56ad3c2c62e530049057e632c0ac0c5e057b645d0164fe22655e2f41994b7087cc5fc01d77857e61057bcc2b737f683dfb05630cd8a3af8d07200400d8a32f0faf3c9c0261030ce5e5e1f43c7c61129766b754de0211ca67618fbe3c8061052e7dbd54d6f7e8d8c530c5c99e81618fa63c85470f05abe88c9a8b83822fadbfbc7c863d2de2d6cf2e078d0543dbe1a07f794cc39cda6f868decaaf11ebef2c82e1a87f11ebec82b1860686fadaf41e38bbc5a0143db832f8cc3bc7e77ed98f19773f743eb2f971d8e197f59f9a4fdc222aefd8c2fa4b933be7157009abbd2e5c8b2d7577065c66318c6d783efcb37c9cdf876d8bf7c83f1fac9226e16ef4a0ef9fad8adc8d7d7c30057ced9efe533bef817fbf1cd5e6e3f1402c49b5d5e9a8b82a3de9ee6d6c92a5bbf32030c93b82f77dd65e37d01677cc6d77aa8e3ba0ef385455cd7e90db7281424ef200f7484bcadd6e9d728f06a4c8743dcd0f51ef3b63e5badc7b0eb9b30ca7d31876645589593ad7cf16e95a32b9fbc5c3f5a69fd6c952b3060c4589141c10c8787674432222b60d6fae4e5e13362de309391ddb48bdef46a3e06185aef86de641308b78e9813c6ad97cfcf9a974f6ec1f8cbedcbb76ee6a5c11ef43c621659d0dc8d7e1148dd1df42b8ff1455b3ffb6c63fa08a4ee0ef995d3dc182fdfac05b91c6e51709ed4cdd8d39bca7535a4b9dfcb17729782afd9ed512f8c182fdf6a566be56a6e4839986ca9b53e7639380dc8968a423fefcb638028f4f6561820ca0b788d98940814ec3abd018bdc799a43843fc5a91477ced7bdc96e3228c6e0c59a5073c3ca934521b6549f672b48de426c719f671f4ec2ad179f2e9f6728441a3a7c9ea9106bb07c9eb3206bb03ecf5e88357678cb43aec2f52618ee7c953a95c7e5137d209f2dc4d8c28a5fb87c964f0b97afc31723ebc5c785214a95ea0a97a58ebcb11f078455960392651fa701d942296c050c8988476ef6ba72f9b892add04fdb6115a59a911bd6ccd6ecd3ac902df40ad942c109a24829e67c0e5f9819e1cee3f0855911eebcca176644b8f3377ca10de017baf8beb0459862c317a2b8f8c293165f6892f28525285f883a997c6129f585a4922f1ca1be5074c3ec260cdd30bb09bb5276436f42cefbc22aea902a716384bc61767373e7411336a45d51a6553f27ce9d1ae88a32f393045e18633d7b6a3bda4eadf5f6d3aea0af9f0c42c12646e085f1d5a7695aed80116847d838904d192a95cb80e9c1863bef9952591462075620043164600640f0c9a289580c0912ca05976c0925842009cc0a4ad80c2183112748812a01052708e902fd2185076c40050f8450061cac200a5700218786035988e2044d6080091c74000a5db8f3a65457c5058c20796ee812abb42ef1bbb8c880e921ce0d5897ed8a20e091e0721817192e9e8b06d39c10842b58e0b2691f8ce67217fae30a988b8b0c95ca5d5ceef426bd3307044dd85c64f00a0445a09a13364dca6c5bec14a0dd824880ae3d9c004e10f620fbcd726b9f7d4c4b22aa0e03319f813b28c8de64e9c34718421840f053869f199ce109718a40480d3860831c5cc08923c8cc1d2740e1049c3bc1f0ac9a9d1026ca8d547801e7ee6089ee8e9fb4fb737677cbee29a482b657933ed415e86e12e732b0d160a7edcf4d7ed1d6befcfaf4eb31af04e337250d656982adc4c697a22bc6887265e49e19f0e463c7200cef57b2121408e2810d28a1020e402104324e5009e76481b1aa1bf6eea1cdacc3c1b2846ce967f3eb6f14859e05765986dc18deb28fc5876c99afcdf2eaadfb9b8f5d0e962564cb7c463f997ab10f07a4b87d5a05078888e697b18c91a572b34f7bd33e48abdf8e186918c1437463644d56f51b0ce933edc6a6b9f4ebe8b5ce1655421aadcb900e813e80c56db9918a0fc02e0874eedb69e3708970d65acbfd24b1938148180f176a3764188c63200c63552739c2ad20220d1ecb2261122644af4a9f975c0dee7c284350a37164cb7ca831ac5d4d8457f39c6d10d6b1791034b1458e51ce50487381d031f0f85d87a36f9804e9313cd0b7185ebfdac776853bcfe035cf3cccf399371336a72994300924d288371657f3de4629dd6c9f4eb625b7dd0588eddcc6711f3d9db8184779933020dc327d5ed6441adde707ee7c573184d840881d93356064cbfcdb09777612ecb50f857b9f7bd7ddf4a170dfb87e710de3b8fb8834b8cff78fd8af7ea251d022680e7099e9f4edc4712527eea8a3ced56ea6a4e4b69b2981e171df46dc68b4c168d65efbdac6932d6f38b3cf6f57b8f3dd0d03c29d313c49845530edf5bbe70f2fec57fb2042911891d89c73cef97abd5eafd7ebe523c681b618c78dd3384ca48030f57127161c1b993f38ec0cdcf9067267fbe857cc5acfb3b190a738833755319f4efddcf9ba2a0abb1348177ad64d24d2883d6fade7354fbfe699b04d784fa4a0b8f32f96e2ce47c930564d9ee6c0d05acfe30ddc79585c4d582c06db352d8687c3e5582504abe61710591304ba06d248248ddbb46bdbd370374dfba66ddcc6511269e3362ab558c79a48e7441af69308777e563184e88201426c81a60e15729c709370fb49405efbd087b27dfb8ca0da77e4c66d0191c6f679064421b8a57d52018b1d32e878e1cec6c28c1189ac3983105747a820859d26e8cc23e418d19d471197f430f4d2b7d27679e70fdb45177ddbba19ef5c37e3791f0c6fa3020450dced5991fb1babc8000caef62d08088050f1012457e5462a3e9073371bb8ede3db7d3de6ed6e6c17954aa1ee874df4a9dc92e8d35edab68deb6e6cdbe66da51157da385276ee5a97637b76dbe1c8be6ddb6328019adb43725fe4c943b46936940144f669077de1e8ce6ffbb62ce360da15040f56a48a10715d9eb7d385a504e38946d17277f48952e27acc9bdde08efa66e26278a7d067e8a77740a85ced7207753f843eeda84fe56a0f15c1113a0786f286420f9de6c61b3b1cdc4360282f77837be81bf769dfe44471a14f3bf79d3e23e80d674ce56a0f7d2a772be14e33477b08e4ae06ce18ab26169ef6fe2462dadeaf5d0eeddbb30ec7764dfbd6751b18ee28bd87ec8f44129dfbec3c8ce66960e880ab812103aef621aef6d9f9b0dd82e102ae06060feb902b5f779ae7c5abc6403c896c51e5fcc59e4e78f4354cc8266ce4ab57d2c666ce9aee51d9e67b3cc71859e30ad2def1b31c358ecda17c62323733e3d0257874541515bab36e06c416235c36e9b0e9e679811352f0041f34510407324d85db8d84db3a82d0d11144cae8862a190c893a625ccc08829850ab43089d20384687471be20b28a0b566319036b2f5b203ba7ea0012ad0820d82208405361a90e12978c1090bea6055bb622a38022666a5976308101b0001621bd05c605c64c02c21b32350025433900056871041e804b15d3e0ed1ae0dd4f101e698042440d0021a6430851b1f1996c2e5181666906922108388c707f3100e1822e601ac6a1ec9cccc0cba910a11f4dc30c64c5ea029e2f6a5d43e3fcbb16a1e31edbc735e66d0d258c94aa8e088e8a02fe46ed74e4e19782051bc5d157da0e66c94380085de9eb45c8fc0cd03b3779763bb22ee6e884e7feb6714de7c28034feb92c8b9a0ed0b5d6e26a770b9436e0c4b911d0743b825bfc52b4f67e48cc2934364f6812daa1851dd2a822157e5462a44c0c4f571e38d777b9daff576ce09ea7c6023acdab80dc7166dec268efaed5b886b1778fdd03c833e0f024320eab7b95590e596acb49ba931d630e23dacd6e1a89fa07db5df36d0853002f4f9edeb21eff6fa85aabb7d7b0dbcc87539be3bc1faac5e64b2c9cebed0861e8808d5e5be498e3b7db63d86edf6f8596b254b5943b97ddccd6ca0dbd86d6048f31869f4fc7c0c831ed3a2cfb20a3c3945603843b6db3edae198afa079fbda0131b58c1e2143b2e3413cdf8b9ff173143f636b07054320e28f90f4936e1dcf8f2cf0e4ce7b055ee85de69697c2314f4d1c69c8196b6c9695861a0c1bb7b871c648a3bca5dddd2de2297673ce1f7e3bb41404c35473e3e4ccfbe85fa0d7f092e1efc89d1a15584a267faef4e9559665974658958172e7c88d9734e3d8531c18a6bccac5ce314929a594524e125d9c94b7b1181301a910c1cee57c489906b7b96df472475e959049a4baf4d2081014943ba03c92d32ee97311981302c314b871905ec910183e95f23822ac92e720670e62328a9a8bc31057f25926931de472ededdd408e4d18782107b93248160a692e115c65232627f9417429e84d19ee8ebeb318656298c49d32fcf14ba669647348a0220b486ea4220b46ee0e37529105286e0caff490e3b4da76638deea147ba117563490bfbd0450dfaf032d2a85c64f54b5f175bfaa44f1455dd423f2addbe67ba7d11eaf64327b7dfa1dc3ed7209055a1fd58becbb7341e593adc061b5ed8e0c2c5ca458b16aa1629292b29282839a09c9ce0706262a262924add902a29014b50a80f753afd6432d9602a955c9448a416a4d12865e479289e4874220a854c425d97ea38ae84db36d406029d409a66d2ac2dd95a4935cb46d96df9d0a3f37db9234bfe5da471236bab1f5f2ae3ec908c2cee9090c9a0e7b1280949231b0484910ff286bcfe4972ecc831efc0e6ca679f3db3652b5895edf06ded11721e6bb7910959c3aa4cc2260dbcceb2aa23e3e1d1e16917bf3cc97365f69a9d5f200da4df2cb3057db71fbfb9ca36abf65ac66cb3f3676d8c2fb6a94e4424974d29cf3e19f6b8dab36f36e1d96b1f11dccd62d8727340b2d70930125f83d207abd8f4137d8ec0f3a392da4f273cd0466b95e037b3ee5a6b6d597b03f5476f069ecac5cede096fb361153f71e2c99a770dad198e9452d33e419a94de174a292e7f03753353c6899efea8977919cd28a89bd1a4949a3581a76907d96ef91a7a7f5224afe8b309b90c04aa19383f29a574cacb83aef56bf64c6add8c3dc7ad08a4bc21773bc7ec92a71214d29d76f6f292e646273c9b559cacca197923a1b89cbaf2e686d246029913c8d4be5aade4805c6e8acbc1a89493d3806c999f1c631a5097d0404545650b5316894b446a1345d09bfc662b6516d1944a71cde51803e10ec816f9f8188e5c03929d8339e79c733eb5b594da4e36a976856c61fecfb412480d902ba700128223b7c68d548440c8b5a067d18705e2311163928e3b37ce9c768181893f118bf9782927fa28eaa27bdf184582d5b4b1d45e294b29b95e72a5af9ce8a3b75fe8b10bc22d0bceeeee1afbb06bac8b6d74f9fa2485160c2dcbbbf89ae7eb23add3afa2b37cdbe08028110ae8496e06e714cc53caab5dd3344dd4b5cf547253edb39b01452055c3adeef35a6bd3d95cb690d263090788e66e956b1678dee7356e893e3f3baf6de6e5ab5dec8357f3405ea5cf12d8437a48f7cd23760e61d50ada87893ba98e4ffb50b07d1af4d9668dad49cd29d81579e24c22bd89be7837edd376421aadcf22d1b5f717ca2ba27dd1b5afef7d14c5bbb67341b01bc39b48f3ba35ed0ad9a25df4900abc18afc4e2c535a003b9edd3493694129444496951b40485f4d59fbed417ef76eae4fba33216e9217d3ae1a1d807c37e58758272aef9c1695b71ea99d61e42bfb627c927c952a27c210749dd50fe904ebb99fec9576ff2123e9160eaa80d55ab949251cc2c254b59b56e8634849a2ea5949e6efa66fadac7c63b9d1ec324898222bb197a4eb9641f2de46b5cf4100b9e3e795b7ca659039a803525e0ac6196cc2cb7669a8e6b77b2f254f2575976d27633a47712fa698a9c62327db2ab4da673dd8c692b7d2be120d1dce98417ca9f185e6791451659c8b015330c726c3f7eb18f3bb1e016ffa787b46b32f1c3169c38ed8a6c2d8873e76b9fbb2747faba1a29dfa9cb6720fc1cfcfa2de3f8e3aee676b16bcf5f0744b6b4a45dd701d922e94c81471fd39476355d4d07640b3dfd4862f18a52dee05cf9275ef24762f1f18b6b50e035abe8f9934d5cf9e624bf47c879f42382bb3dcf40ee7c0c9361e7b2dc480519a0b8db16b9ede3807407f5bbef03bd3ff9f36295c4e263907db06a327165cce6782f1e9876b9b8a854e1530dd3300dc3383d2f7ffa0c3afd30e66ce17a5e779df27b00fdc683524a2548bf9983df343b0d4160968572668267d5104344567fcba6083cfe42f9baf33453b9fda9b3c9ac20954af5bd4f5a2b6fbf0778df78785e0eee208f7362235789e4329d4f0003e88834b4ce2be9ae7d03083d745b724397605872c3c7f04697dd8c90d23dd2ed65496adb8bb8db91d8b66b3779bca98731c6637b4873b76fdb25407337500226a9a3dc5a7b2d25a5c527a4e429d6821240018598c00c98fee3743a9d4ea8ff309dfec3f493ff4095fcc709ea26a976951cf5d09e1e7aa687a91230461418638cabb8d2c1a40f04e5e3a5ef7bf61ee8035d520a8a9de80bc5585482cc3e7909d0dce89d48706a8fe149d02635edf29a7c949a263f65539a11e588cc39238bb7fd07680365df3260ffa3da5abd766540fb0fab59d0539a06da40fdeda0a8e222e3c6ed8b6d7bd5abe67e0c8f8e26377337c770129586f2f4e96cd2f948e93b9bb31fd39f9335b04595b0fadc5905bb05183446fa2140951529e5e3cae5ca57246863b77e5adbb1a56aad1e3f4ac556f7f894d87af158638d97c750acb1f295c3004fb1867c098c472a9fd81e10a6aea5965218af1f11a94b634c677bf17112f6e12d44d5fcea632b48d5bceae32844d5fc4b156ecbfc8ad7322f190cb7653efba01059f3bc235be63b055ef61901e32e7ff98cc8cef295cf087bd6e56704fd0e30be1eded5e1d9d7c3bb2db75f0faf877745f2865bd3da5a6b05ebb329563ebd977a0b862b60368514ac9aa31730b45e064e29389be28688d4a5bf3181276d9e9037373d4be085d9cdcd6efa2b60682fc130757b08abe614deb39b6fa7210fa98fe14991277a8ca2b981a82a42a4413390ac918113265b26e81604bd821b38351059f3a4d349e90cc5198f02a895c2a8b5c62a915a79aa4e8dddf9da236fed6b37632dad3ad58abaf3b2f28275d8b88dfbcb4a17eafea2bb0e2ca1959addcaccce388c0f6600b39b71dddb9a715b36543030ad19365af7ba5c36fe72eab2e1d2542e970d57066385c677b3b2a95c9fe1b2e172b95c2e1797cbc5356386eb8bd77ed2e5fa6cb87c61ccd5cfc56377a3aa868c70b815573fab35c81af1d29217af3bc291a2e4a65d164499b7e17fbf2404a24c10e500f6a127e0a00d60f3f40af410187b65c1d8530a1e88eecc2a78f5a09bd0bb998b8d88deb92ec0d51a64cbf6ec0b5543ee567758255f8db04aa248c1aa1e8223bb2893c7a3f4dbe426153c52776ecb28cc4597badd0461f3f5baf5d937a7e055245e77728fdda4d73e9a3bbf1edae5409625640bad4758257b5086b4cb471429ef61ef491f450a6e19c1a387bd28efa3e0b478c95168106bb4700152d363bb4447d118fb27ae40eff9baa0d4d748a22a05ec9ea8025df4f9689ff802fa059278c988be71cad73d28df11f69a807d24aa40dfdae4eb2391057a8bbec205bdab105b40df66ca27c4fee41302b332f629b07b7a05bae8037da35cca97fafcb28b92fabcc94fbe23ec4d7d4be9664c40185eca24f5183ed21980e6a6c046c22ad0b72cd650f344d69482077a58cae99d5e818e6283db4785253b252b6edfb453b24758d53c36e4a460579489239cdb1f350fd7c311f1b89cdbf7b8d8ed7b9d0ece1eb13d968743c2957278d56faf095ec8ddb064a764676a9a565fca291581e2e6f64b3f261fecd27c4a56702b5601c591dba59e862207372cf970355030e186252c6e3ff46e95ad6805992152703ddc32dd7e094fbb42ef97ecb44b54b2532ac2ab3e85b52b047220cc54429982558d12bb8d32e4a2e0941829e129d99951f0686eac3b573ee47ab80505cf88a923087679b364a7eb6626d758bb5082b00f3d04228192800ad8871e65ac1016ac3193a9c4d59cae497c7c72a23d9dba99931156b5941ca897253cacea921d9d76d9cbe7d857c5e2ca5a89a85fc58255d2fa28e5b4ab64a70e69174a10a18b2e0291a83abd920f8175880cca043d56f553bdea9bc894724a764a39253bf69ec8933e95cc2478f22659f604af450bef66377a97d905e74293b25659b5739fa1af3e37bbf64160c2ee04430afc5c2226ac040a567509ceedc7929bc8eac74e47894daffa455855d26155db1b96724a39d6f2f0aa1f41282090e5b1478cdcbe56a335c12e938d45c2ad58850d8adcf674b0b87d3f6334c0c20d35d8edae268a58450d3270438d89db329af474b815469d1d6e8514e659c1ad5885db3c37f48cd0cbadb0942333440a588996d2b89e5ef535302cb9094b76726e9feb6957f374b419d7733b2cd9b9fdcd062e66549b73ced9c49d555622260c012c63c1dee955f6fa6507d06462b55a8336ae0b89bc51462a9950273969eaa44589898b981242c191440c9a2e89492b3bd1c4b68928f78514ab58caebc4aaa851c38dbd8adcc4c61de4468e31ee581eafc70746f582619b1bc619127d62d12727fae8dcf4dcf8dcfcdcf0eb866191a757cc13e3c9913c3a926747f2d897074bd53c88ca2688bc71f151f59c27b5c331c70546e57255ea1eeb34ac5f6532192a33655a8665a48c9599577861d75c7918b6893e3d371247f2fc04995778a18545db399125dfed481a5931b2228885bdb18a0e24b952015eaca20347ea8d5574200a973ba7bbb18a0e20410c928a1dd8e5a9c98721091caba467f24c9ec94696fc1ccd7f7615bc21e24a874bdc818d97314c45eb63a3fc43aae1c64b32dc78e985e8b29f12494b232e979a541a37e299ea58cf6baae3d3d67a3e6dade7b510ed319c9dea509deda34ae8db76718d9c351b4806afe8b9a65329aa53a343333993f598f06454a33abdca3a1b59f3b1035d5845df3e3aed334fdf4b8834e8291362ab4f4f9d105d2e5447d660a160b8f431522d5c4aa97069769776b50f8bac613d7b416065b9f45bd7773d8595dc4aebeb4d462ea912552cb195bdbb7edd33cbe2d53e1fda97b88c0646917da3dd4c8330bcacb36c569cd929b14516af8a1f412e7863153ec6703791a453ca7863f217b38c6783f48bf194360babbc209000e7c62edc48c5088270a9cd1663b482eb61dac870b95125bab8c6a65df3876bda357334303c67330a84bcf3a75d5c5cc99b2c07fea8b05c098644b88c40ae51656078b03ef4d0b986998834425f78aa930b558ee356b7dcc621e5b9b886573327b6f0fb918b791d433f127a288653f61b88a38fa18f6fd794e0e51384e74a14dc480509621787e86a5d0ed10db1b45cd685645665a699b81812792352890b854221b0f361de39b9d5a10e8788f3ba6f235257b2a693e5341ca252974374fb9b893bb1e4100a0ebe6105faf2dd4a9853b32ff74530d47143225c6eb79c21141c7dfb7d1b0c93b8a1cf6e15162fbb68443acfdb2cf0e20abc906b98a0d9697067eea1d06be51eaa620350dc50096caec7fdb8202658c562f0e6c3f904b7f8f3865b3beab4e156ff87c801c13df40e477de8b186b699d3ae2eaeea4fb6be3ea75d51c67a33876b16743ef0ed8772782ea1f7c6719d715cfdfafdeea16f47fd81c351450f891efa26e2b22f74d217afe8312ce248a1d081085df4d8cd84bc4ff4edc81ee21e4a819b889b44f0660bbc11d74d8a2c49a47edf0d87d91c0a2b704d39b4884d2496913443d32564d96355d8e2865c5363ef248266d2916c01826dd247ee7c30b18a9952f9801bf689487137b717cee66969d3669af59412b8116f8337e26530c34008423064c810969e9b2f08a974c2a1c6343a25f9f9f141224f4c2614ca8432fd8405c94c32e7c7d2639a730373ce1a9b5f109e464673741acd9e1e243f2693e90709d81a7a4c23941e23a6d3a9449aa69e76a1f4a04c9422a38f765dd7733a15199548a72153c472e69c467a4cdf4627272258d9b06ab50a3253a9d1d7f734648a584e483262ea993da64fde911d9548a71e2986c4727a78aefc098542753323934904debc89450b76c4a205162dccef74ea399d8a24f9f139f9c989cf952793098532a14e7efa42224e925cc982648ee43dc588e42431d2d36342954828530fb766cf9ca6d7d32bcae4c3e2c32ac982c4f4b1f44c21f4142991e69c736e45e69c52957134b41bedcdcae0cd20d332cf8d37b44d42d264b66506b247f6c81e8ee3faa77fba7f9e100155b7eaca37ab98e0e2e242241623326113f60466666e261c416f77cfb76c1fe47904404706073ddc3ae2cae0e008b7e28d323838d2c3e3f87941941ff8603c83f28a2dee000ca74872ca11218cecf01840a7c89471ce504873f94f99f3292a29943eabb7d7586a51d342214d6ce9c5243391e7a434cb6aadd65a2e06253830989430ace2579a5bc170d65a9570e9d72d322232c22d7909a60060ce24dc100097250f072e4b50c4135941f0425528e229c1995e88514a5999abac999473071eadb55a6be93cd7787d0acba4949990fa189e31c6ec339395cedab5e76378b466598682a1609a06d3609aa651a69f10db26f0423eed22917e7e42efca6cd22c9bb47bce466100054301a981a13030511898b09a4d5a61192c9b9f10edf4138282a180b0cb724bca6a727272424249494931d5f909a9a7b567f79cb046c17a4e1da06074523e0a56b3af6928a7a0618b71da94df90b7654e8c524ae6988517b64e901aa4045ebde46aadd6da181eea15513e2e007a6d9bf668a794b2a3c64b789366f3614a7e548741378657716ed83d5feff48a1f4a21f6d92724d4cdf494a49ac8e24f1c7821ead573eb5ba706e950af209f2623937821a9e6b443aaa1326463b35be7a7c9cfd6db4b785dfd26a5340103ea85f28182a15e2660300143a4116fb5a94158d504abf8219b76d983766e2e7fa3a857eb5c9e968235480d721d4e7ef3d24f88a61d4093b1ef90b443aaf94841423635a8972642e1be2844bbfd6097d1c0d013211b5290cb9f6c72727242fa244a4a4a8a697e51c8d34fc87c366f3f1f51d4b770e16226a354047227b23864c3aad013a19b2a6cb81582762ed730c1a420a426b8151231831cb413694494fc502f948f2ff404aff8a86ea6c1900d10bc306473f9a49a5973f9a817a35e11646ba699f8740b18c8eca1aec6ee9deedb688c517bddb0078362a7317ddce2a9c62b2cc0d50fab98be792cc7b326d6844df7e58df69e40affadd0db68b6f64957ddb3737375a070427d5d5c4a87d5d79fb51cbb61ae16195dc892ce983553056b59474b34c33de6c65cb9f76e617abecf99c51695f69a8b1d58f1156b14acef31238ede2beddbe77c29e6dc50d7ba79bb9412f7cd41c9dcb604844efdcba5a4305bd78dd17383786575880563fab35c8160ea863a804ecd70a931a65aa66040000001315000030140a878362b158305145b9fc14000d8da65272529c474914831432c818030c0004000098010081999248002e3302ed9ebd577decbb15e557dd765d8334e0e23d9cfb577cffc74ec4038fecd5bc35b0a119d0c930f272a3a1ae0558ee0cc100b274c5eadd9c2cf8d029451d1fb675b716600b8e69f4b75d0bd43f911abee1da2143d85186648d5dd10fb59df3ed902d147c06747aa9d1fb85fa822388a0f613d086886f137874304ce61c93e75878a9e435b2c4a0268ef24a27e3d9ef1d6333ba599823b0595d1bad0fb265fd64d14e395dce27807770617ef15dd9755d37abf41bee59875acb0cd824d71b99b76e296b41b06210155196441e8367d01327210806aca417765dac381090bd700717002141f4e8925dda174e7d3cacf0997c1d8c71a8f2dc131216d2dd3757f9e18efdae45b4386bb5593e47679ebefda5aa6095abd63c2fe2162b5ad9c6dfd2b11ab819b242206a59d241ac6986c2729a963e9f1956e81914faffc6c28cfef7db1afd67bbfe4199c52f38435fb20aa8b45480eaf1e708b9428864d6add5f457d0e0bfe8b034cfc9f875e16f33d8648960695b19a74ac6ec49061cbbfdc6d9aee2c5e27c247caea0275414ab0c5b782e5d309cfd73312864f532103fd6ee7a4b5e9313c1fa18e7fd6406da3aa86fc3db444c63513c8ec38534392be690663145ac8b948992546e5c826cb52f63231709594dd4ce411237198a65b2eba253801e660d072257c75bdc74f09ce9338612d9715430d04b44ec11eb3d857b73960ce27e6ac4b668ccaa8b01568e92123e6262123f65bf9333bb0b65f0991098371494356308e6fb31f0a4049872e680d66418dc590a136f06b6e09a0a1237e39934cca2aab107469950bfd0fb62cc0c8b83f9360a609b79d5f5c892632ef63ae9a4d6b91e823b978309499f5d1a5686d2ef3e2887c45bec6efb2a2eef32daba0c7079a7cf0b60dbbde876a64f34f43f68868d33a881ee6a2e7339163a01c0d640bf985ab66ccfb45ae8da65570e59a5d468260165e996ee2b38429f3afdd7a12c738a88b1f4c56bad16b5917e932139f5fc1b02b4383a9fa3b3ab336b9294ac8b3a298181720cbff68e80f825ff870bc665be8d22fdc824059e8e1cfc61d23c67b7e17319f3702e7fec287e36fd8914d09d824a2f4e80e4bbfcde40c9877b23bb619c8b57a0e6b05f1c12be4383522c2107e9a49442903e371d2369d97984cb0449d76555e8cca3b2ac6cdcd95371cbda2b89ab872b06abc07b83a7bad16065fbb71e46f4d30558f36b29d3b0d26e967dc96234930df2283bd5f35a72ef569be5fc0883155e7918d55b009f5ae93e9cd079684f99b47eb985c4006b135ee4648632f3890b870e11ae960a70c79f32999ad2bd0e0b55d6813a60ea742d6f808a6ba741acd49e7eae1bd6f4c103f2d4e285f281678ce89d834ff2199f82c280d2286aabd9d25118870572f3e04d9a36a0547751fa260a3e0f307a19a32623463fbb96c73da88f15dcbe0e8748c720ace0a813d2b16a7acf39f62b22a6ac09d53ce7557cc1d5b31aa7b402dc8465824e5dcf053cf44cf7253299ab061c3c247785b9712bc0d79457260a4ef24c6fe8be797f57def745b0ed6c80e0fb7b42742a4168992876fe1aa2ab1991dac4eea23532916a58a0996849acac768b469e98f765342bd10810618d20b2d96da8944bec79d4b2b0e46caac9d89d932ea4d2f275f60d89cb519c8437ba4720f4ed59504fd25b523e4edb31f63a7a82a4543b8b3eef05ca583487b95878712d3f4da1d88b93e42d54f9af46d6f27e72b113cf9a9b774b518dd0a5450f3bf9edaef17cb7a8bb66c65780af482ccabfbf81d76c87fb2fd3aa51ecb069e5e9ee5923ec52d0189c3b98a63f218dd7c5c7329933c2288c6818df195ab473e732d1d7fe1a1a85e43e25d01f53b4b1021cf8f88817254c6cbac4dbac77584755ddecd60a7b0efef4588365ccf0e067ee0a80463bba5590ffba9da832509f2020ca3c78bd81320902cadc084f4fc5427633354a5dc4f934e2a740ee782ef88e8d66ece1e6d4a63ad00721c3d13500917c7f207ef43435274f33a278fc010e12768e47a52b7154a4b94107b50d92c2e1dea11e0dc11260a209b81cce254f492febed7dbec2212446fe3f22e871a12c46b47d83dea917bef7bda9e72580563eb5f3c4d80e2cb5f5ff1c53eb0d41a43aecdfb93af44f7e36fd72bf612a70ed1fc92e44520e436ccfb65975675cc1f14e28087b6f9f5b26769cb8e6a722c3a06ea18e1e7ec38abe30711b352da7235e23d10c4364f8c5f9bc2bd61f4f52f3a75819c673d8f77b176d325f3a4815abb26dc9bc5448dac62822a8a3d74791efe857a53403cdd3cae98b9cf26baf4a4bcb6059a6e4c871678a5abb07c8a26400c03f2493687b4b20df12e4e3364cf5107274b1dacd2c76765f8b8e8123ad02532404e50166eccd88b6485e2eb9ece1e1989a584cb7c43711e08ea619c1d102a5a5b6561908c884cd7f2b786b258273ce834c34103f4430429a21913cc5ee6ff448ee6b16e51a657aa3500ebf76a55b8e6978f15f141ac857225223171ff6383e08a47b2820c38da23b1b1f0fdbf2ca5c7cd1c055c61fb2e5bb78e233c313412819be9755a071a4d44e6eee334f95f720261aaebd5a6c8ee6dea5834a515dfaf7463d7fbbac8729d1448e522dc1d3582b361f89830a23600b537d91e6a755f8be798e54dbc95a7e84acbfa0939d71909b559d1fbbaeaf05f645070fe0d10b34a1a8ff5d70783b0cf2abda80994588d1dd76edd93933b600c4d3d8336d4742fae7e2369872bcb6e3fa2b88b2a73608727a8debb48d732f587abea9ecb5fbfc2cdd56b1d3362405e78b34690c2eade68c32f68ec88839b7fa71ce28838a1ea142c0c065c6e173b026598a7d24310368f50ec9f43f49844564724e226a47c87e11b14e34566521d6388326052d4c9201b6d91de1c51f8e9d54fca6bc5907f8096f2f7bf34864db1d39cc95cf6b154d645f0be1d91db2d9dbe328d542e1bb1b33c58444d6eeb789fab2e3a285e1ae9663968b1a791fd8ad1b6dbbe9ba105880f6f9b87a254612e51d6b41a0551b40a49ae8a9e87783418fad8d6e5131bb578b2113070cf15d7262b2a8133adb85e840c7bbe8777cfcb2bd0713e2df0d8761dcef7e5901e02d36eec894485a2dae60519e4594689ee33ecf842d2103de151bf02f639826fd3978396c0e9d01bedd9c27a7bc4c25b8ba9d4ddf8608302e80b7296d63768beb71009de8d3a15afa21566d06a8929ef1c5093097667eb6ef2fc97130a1c9c6be18ef22525518629f87f1f1874a29cde7c5a6d254f2476cd2838640810a1d2284b4cf86b164dacc4346d4434c2327b1c16c7607695a13a6edd3cf33274aaaa542d9d85c4bda9bfe00d443ea15863b10030c3cb6c738dfff79ef18f4b1600469f8f8879e62a60c381e399f17b51d311eef1ba81efd213166c06c8fe2704d827ef594a36396bfa3ee2116cac554c4ba488ea4f607baa2142b3ca2384a4d0ef88d26ceac3b9a3d8c7712805fe3adf18e82ba5820e236f7f903eb392520649cd6b480a3028dd2e912a0b44cb3ec33c5c6f3273402c492b3a0b515b452b28a4b5c59fc116c17b90d0a0ec82a448ed5bcf5f4ded9f5224993435bc63152af1b8dd74279f4c175a82ef0d803fc5845e25ee0b549a098ca06ea52875f5c481fd5656daacb4d6d9f2551effcd3e51bd12703ed5002a27a48e0d3748c00bb8624a8189911449ceddb054d02f795b8f5df09ee1b070e321c73db961d61980a2c72023e4080fcf5af51d5cf757c3e788836d921785dbdd59b8c1b4671cf07335aeb72069fc03ad6f60cf9225957842627e442809751c8e0b978529897b3763d707c65f460b9d61d1cb911c24ec114a47d1cc4eec6fb81db4f530abd631a9733536887cbec0f2ecf4b10220ae71de4f900339677aa49ef3aec47620c2e57c1a08ca997907ee99d645e3af36dfbfd31bb4aa06bf15dda0d76f29defdf4fcbe1ad2a917c90443ca4d93c7a2f517ef6f745f9ee9532d40cc76e5bd9114ed209682fcfbdb46b6954fe0eb9f62d69f76726209377e681c619b2b60eb94700590de0c5a38a408007a366f5ff88e5c249bb7af4c16b6348204f6d735ebaaef04ddec718bd3dd6d1fe3524a055b39c7be3eee550ee09aacffb4dcb2f8d90d4a6b6f7df60ff505a6d73adcb22417ce490fc7f0dc4a3d70585ffa438e8a17364246c303818c4bf3dc330d0a000c80bf36d94853bc3b6283fdae380048566ca35b4e55802a95cd906ca9588da56da31418008d6fa32abb626495117f81ed13012347558f1ae270c7f1eeab679334c3b9117315a3b288c40ede70214b15e6c77b580dd9766d36d3950af564bb9c9224b3c66ed7d211b97d3bbf16ed832148729f3ee4cf1a63153f21712605d5530eae2f53b18a96bc67dba2fcd570eb7054a65d872aa4031208aeb135282600c7f6daa950801456a7d01d8d07a9e5ff606b9d7053d22f3dbcb6d31b62f773a5882b0464f3070c8c529d3642165f17e629995259e9b094992f26f9a83da40940da25fae73c738f67eb21fa4c2e870800169c8883e3b5c0442266470cecbd397409883beb04eceda7b92220f370335d8deabb9473442989d3bd784b0b737d2b887c90746fc0dbc0db29d295ba81a9c9a568ddf7acb38e206c58c6b8530a5c001acbb1270c7903f0828961e6b40fabcee8fb05dd94eec40a843620e4652f6635e77d0601a396f4c89997583ac5fe71b1ef4ad4b52f3c271bc9b400a1b5df594db22d978967dbb2cb1872c369653f71255fbd429451ec3e8ca1efbe30a572ee68717591bf3f0097ab070c171f3ee11e6857c654601526667da0488b106476aefe63e5ab23cf9adb8f82eb44ce973784416fbdf3a62cddb258059b43d0435528a0bf841dad7a9118508a04fedafec935ab78e4a2cb29d57dcb4dc4a0ab133ac9002dd5a621bd63531ab07db64c2e689568fe8d1ab1b6700973379fb9968f337a57bb02e120fc16b5d59ed33917e72cadffc80a5320c80193c9909b1be42491afc06a825ad0880407fc1d8db9b6d11f6be3678b2283f3a4db0d00ccb66b2626b1fc5a7d837b4bc921ca5904b63ae4ef482317f8398afc2c4619a62f57bc5602c3eaa3290139f7c83c10814c453bfa48d4534bf258e44b12158661c3d3794561ebdfb223cdf1ef311ff86a9f3637fa2c1b74398661c815583e1a642cb340d00ee8bff18ac2dd499daa1d688e9cff0a5463e9d6d28f32075be51aff268c69181c146cd423fc71324376ac97374f4c40932ec811fbb9db8633379a758c44a671c6ece3f59b957b1fff1b79f885128b1c6ba9a71e8c85e1c8ee0b7592a342e9b6dccbd74f9822b13c805d53e0741bbb1d67f4420e96fcd38d8cfd49f804dc1c80dca97bfe0468d39be6e771a2f403ce8b677929bc6e395d185661c3e5eec21f5919d619a71e4995e245402986ccaf212311144664e6d51692d3a18eaffbf16359154e19568a7cf0defbbfa21e0ab2cde56a83597413a542d14f974330eebced71a72b6cea14d763144dba519473805bcac2336bb416364446da00cc76cd0f8695cce4772b274634e9878654561d5d409e3699f1ff8b8dd1c3e8601cd38f2d608ddc03cf97e00c4adf01e1f2e6b0687e38fda2376fe48b6837dffae8987ece72733008687b41402cdaf4dddfe452fdfa9d80f7732aba49ab5779a71f4e6b8ab4235a83e259d549b3620ed09f9c44205eea6c75006b0e731a65b822a431f658c6ed494b50bc5746bc6119f47a400237ec41cebeaaaa446d4f440b83de30885d67732b617f7b9f26d129a2033e0fdb1ebb66d65aa83a6b5eb72c631097d3ddb587a2adc9da59551e961bd99b3c1f5301516089e6bf2e5d4520371b332b48f39aa6e21dc19d19fea18edae7271f86f6d2878b257a2352046b7280f03b48cad4af38315ec142557748945fc52ad3434772fe2136cb32036abd87625bf107d45bcc95a19f96269b2aa3351a2ad2e22d5ef64aefc8f92f703de1e5219a6455786cf1d688700a5c169f0bf25549cde4e3a9d241f870962eb6c86fe4f6f03ceab5fdbc467a5872970d23414bdc4b03bc175c14915b2497c4c8b98415604f5ea0447514c86cb73c75a4f724093436627f4252bfbcb366d88eca72955b2b2a6fe98bc6b7715877b634b36ab6977a458b955de284ee22f1f0ba40cefa48887f073107bdf2c5152692402e711cb7195ab18bbe38eea8a906b89213f8961304e32ebe80af21fa52c2000676561f61d63022ee31fed75dcb4c6ca877392e392ccfaf8afd474c5661cf6f397b037d200773460cb6d09884499ae486c95fad26a0fc7f2514a09720e0b5156c348adfc90799f2eb86efd4b919ddfb5be12f875e0c00fa10009becc94cebc818a56b75d18e09ae4b9c0427cc49c71460560d1d82a6ec779ae86a6e6105522f13a4ae8d3afa8d0c2c4f70a4f1fce61b9b0d4399d4244edd5da44d18b872dda3ee46f0882c7fe5d190e57cdadec3b216c6c2754e89c5bf932e88ed2c878e8b0ce25e98a154dde6791aaa0c96184a43aa4da2b835db485f1fa4f7f6c29bc5c9e24e70405ac120f44e9fd26a91ad114f6e0e481136b471c683e1db7f1d6c38f08e1dd729de455239042b10e34607fc560a8f03146d227af7229ee3fd4bc5ad80d951dd84e748e29e8400ad43f09e3fe5c9a6ffb402de3d1641be69abc9e28680cb2d2c2b0e324f9a7ed579ec935fe3cc4076a714aabd7c79623176f37b53c8641f57fb11b2f6eaaf04013e7cd68867e09c13bd9bb28b0b60f351703cbeee3ed810b7e7ba161a448695779464ee68960936cb2ca59e8b6c8fc643a03356d42d82aa91f0693b561485ac0f58835540d17c6ee0f1af6c58d35d586face8ea0b162acfe091dee4c46080fc5563f26b0db9736cef9bcf9a72f415eea56b739b7a054281d7b268c9733a9cfa148df2828ba3979946a178a446f109ee2c06a290fdbf1a12038f937022f94e3d7c3341d3d4f2aa8d50ded7630821ba6b4503c692c4779d60cee4660b6f3ebbbeb7ecc4f60476768f43a252650fa8c1730396b8f651e39e86b4e1e3c728cc08e90ddbbf0e090724b6d5e5b1169f371197e6705fa3aa5f37a5b46420e38d018c8acc74d7c7f9a453c53cba4b825ea4d604fc4d1951579de74c0a1bbe9dd64c8202611f8ca9b6c2345b363137c1d88e3ef67136941a3309ac9f7efd7912cf130eebffae2092e3c2e1d2ed87fef4e7425d36081e8b0a5889dbc3c2f3a22d1188d307adbbd0e288bbeec422126f48c3159e3a015919b3e0198e4c618fc889e89c896b0a2a552c4d29b81da6d1846ba02fc9ac1b5abd96f1dda97f12ef1bed92d1e61c475a3d611f9b945f459cf6051a4c3962f70af4d525a44a3106e96ae58b28780d0ab0570882472d7847f465692c34c165793e819688383f182e7d4f94eeca3fce276f6edc3c042b8e45a3ab5ed7a4dee2e01763012d13a1f4eb904808569327e1db50743821af3bc32b22c2afc2829dbcc1b67b8c574956511cf4efe0d519d4fd84cb56cd7b2c2648d845814494559cc1268d64bd0aa13b007b6622dd26415a26ccd11f17917bfaaaec38ad3f160876cb6a6b385b380b8be0bafd51a954a5411ef5632a5830bcbb60a6c82a479ce8595bf6f00bed385e895bd07d238c68c958c537f0f4d6865bc606e85c7a46f35b9b1212a0f03328361d144bd85bc148103e71a72dafe2a70562e4a0d59d924e3bf3837197f333cd5317f0ce5154ee03e6488669b5518bb140bc4d4bf9972f0af14776bce86b1d1b73954de37e62b8003a0aa4e5628bce886dccae018b19ef4f504696d156822d4778f72165b9cac3788ea211d2d048650a07947e94af77d621b416a82de9bc4b8138364c3ad866712cd461c827f819a93f0c472da152a761b58590e1307e83e696fe15791018420be5622d9ab4c31411ba9461ee527f28839f5068835eb08f74eff2214209a4b281381962bae835c776dc52fe88a00914e2297f7284215e0f01e9af60a5df1a9fbaa1ccf9494c2663db31bc57d66a932037a466ac7b10e073e66111655296f50f03c5abe701f127f4b07f1a9dbdd4fc861b023de0ff6be66a25b03467c16d0f258fff2dabdb90ce87252b43e75f928bb6e472af96f1e07cc7dc18003273b928bd270ba31b41a8dba814d764b157303cc8a60fc355392e5c5de170177d7af4847a0ad5da9daa9d42d5a00e30a70aa89441c30e22531b67ecfc5fb2bae5dbaee4256af265cfa76594e538c7d2c8327c7c4f67d347fc5813037c81c30b288ebfe2d3ac68cf1de49673f3c17539070fb36cef801b6cc2d44aa7f45771ac15d9131ff3adbbc71132cf7142d207610e5cdb86e7db35394d757a6903bec303a7adec186d701a87ac08e0696904b61c6e32a4e05cbd5c4f5707107b5278e0e89ea8ac99daede145549d01f3f841b12f467b32880380d27080be1ea9d8373c2b36115595df383a637f48b9a09c7eb65d1f578ddbd69c685c38b8e3e40a85c0bba1efd3cca764191e1d84c0447976e43f11d0dbe61141d240cb051c36d12552259d6a10db39ad995bef9ee14046e70901b0a860d4a3131b0d8a0d1140f09097aa540da3bf9aa9990a8fc420a6cfb64d6330b3a010c114aa638cdb669858cb4a3140352876843b3d8ec59ad8b5207193e09b711f1e42a56a9793a9680ae9954572ded55662a815109b6b858f31454628da666c2dc289ae9e781d468ec30d9780fd6e28326a4ac1ef2d22d783f5d311d70a5e16caa28f0239e73ce2cbf1e4bd523cf9d2529ae58f27f96f3608c8c5ab2adb80ed1c426e9a2a4950390df790aa1a2365230fd60002a05fb333dda508f0dce5d792af142436dd7fce0c97e35bcd59228bb5fe7eaa235b49a6144489ad98aa0e74e9cd76fea1b0162edc06e96d7aed8424c48285bc54e0640854d5d7d8fd6ae76190c50f81270000361aa8f248fc8a1ee5e9bd7b9c9d984ccebbe61003635d967d5355f872e01b0e0e0c03f715fb59cc38af5093221abf5bb7a0138c2b6815548ea35f4f701af4706d41bfe02a48d6f3439295a8ede7f01fabd5471abbd70e55c3f7fd49ead75503ca77e35a2bb419f675d0ffde93c5b4be1beb8bdc9185e9283285e09e451e8d894ea192128cba5fcaf07eb5e2f402500570d2c2abeed44a7f70a24eaf5fb895732cc93addd30e9e9ee746e389f88e4798c73918f1017f8c3df34ceec50b873f50a98829a17d46ce205dfbeef8e5d79ced203a91e75fffc057fc5e873991a2a6342b557f8af8eec5831c4854c2fb608c99646863bf952c5feace752952c405e2f4fec2bfd6a2b838afb5d6450ec3978bc1b38d38b9fb8e6d7bd2f7390ce7c54c4e543ab548f2e7ac4204b013467c88631aac7c9f04352254177c792169c34a51220914bf11824bf09f41e2c6fbdce380a2754c0c5db7f801d7209a2919b0f9253d0ed40cc71ca84d8e53491b793949396e8932a464e48a7350ec8378cd710b408037f82e41c0d3f1d01055528a9e446d60abb11c6df8f617e3d91fe09b847d9e812c4789bc885f67d6662ee5eff09095a8c2aa307411e00172c4c7a8051234963d76bc3b9fbf2ac3e7cf7b197a5a3e6d35292a87cc34553194b7d6343ed4ac8c7128c5bdde4ac987c07d1055530ab0ef3c47acbd1a598993bcd9076a5eb1ea46d996fd06235a003c8efa44090061b1a56284d5f9856522a824698d028592c40286035d93222b2315842eedf83c64dfaa419ea7d3f88839779fc5a6a66fa89f870a79a0552a9ccba0bd0af9b9254f78c3fd3cb5e70b424ed7c299583b9b28217a232aa078bf68d5b5371e6918fc3dfbcfd227f666518abc0e9384cfef8c95952ae79cdc8478fb23f09f77346945dc7c4ba0b69d3a6c301e6c637a33cd56e310c403702240717a5cb0fc01117eb4e8b19ea71a58f8023918501a375db8d55db5944fa9c82fbbf1f44c0aa411c81d86347b62e92d0737cf646e67b12d3396a3bc4b924ff1af4826b2bb559f9da9c4c1afc1a99387a92aca2077d38d49232d13d9f2f13c91805ee2d389c72c676877b4831d2b640813525b2d7e01b4a82e7bdb31b28ecb102fa6db0fcfae2dbe5c1136d16d7523d966695217248a34beca6803638d6aa387d15771aa0cbfef1adfe2973af6e63739ee1d238c0e5e3a43ba4818e626cf3828661d440cc9d8dee2b644469ab092158b90438cb1e626cc9ddbb504d83787e6d13c978a2103762f14c0afbc9519198d04591e65c2376e480c54f712ecee46314173dde3c7e758b222b0f097a8e220b3f62feeca5874dabd799e96089651e0c72775b6011745664f1572d4328697197ed04b2bc3472391d1bae0abb1ace14178f4ce4859def01704a3dd9e0603888a2ab08e2b2f728c970b793eb4bd7e370debd3f306008f287a360ae2182735529b464566cec6077f241b957fd8f99406a02a093ae9e9843f50795a3dcecc234096b3d27da1c1362c30dcdc109179323761e8acd63fe28fc563f7da4a41de6650dab33b68d275d2b4135a2800bf013e803113dc9b4917549301cb9219bc662d4a192b3243d8739bc758a68adfc09efa4f34f0a0187cf8da87d7e3ca5b440131a25cce2f6e4453bd070a890fa00d3a06a16eb31bac39370ad74a02ebb2346492317f3671c0d3e63ae529715c32ef78b4f5f844780f179a1ed7555d2c64e68cb917ff50d0b2dbaa5d19b6b7d8adc402597eb75600518710039038b5931931b763aab718c8f28e21ae1362cc9f8b820774cc12f8483194b04afb51fc8fa539cc2397f067c517567808559282e5f0f70741a58cc8b76ced0319d70ae500b15297f7d7b520f0985f4dc85d9fdaf5ad8e65b0744c507eaa2c01f327f36cd418c10947ca52217ca75a0f4603d84ab8ede2c2b8a590d9128a63cfcf0f055073de928c98a750ebac670d8279892b14ea1849e3202515bb04780eecc7c44fb462bae69e80d6dcf308c119b941f4b497af51773fb108a97b03c4d9df490b6188d00977400ae22ae506fa22b1248e04841f290f6d39b6805067ec2a10020b3824f8129285ee8f1f6630aa2369bd48845900bb4ed529977633bcb8830c9194217fb024c3228f89571543ae80567bb85d7f2f14d7f21d281398d0aaa8af297ebdf7cff385e9a2817301907cc4757b1947c98ae570cf099560c72792f01b04ee7064be2e185656f822f16cfcb620ed857c64fe18fe8adc6bdb7273ad2bbe0c7cf320c073afd5aefefc89359e30b0d78fb853d514a6dfd3fd6fa832ab6f5d79fca3d54a4d36b4d16ab46f44980d42a4512fc2d86131bdb0aed452c61c7b0cd2575b158a65f3a1c3be85b237b8aeda9d8ef0b73235ee68ba43e89c8aa7d5cb0f967123d7e62c7329a46c3349df91123cd6c8c0151c50a0436e104b52f95697a6e7c3054f2431c574f8e9fa0cdf5aab4f94ccaa532a345f6b04fc63e883569920a9f0c1b95b211d9b64bb1c15f82c4a1002c4c2bee69596153dcd452d897f58f31d04db8eec9db5ab8578be8a4b35cb57470a4eca89fad8df3203b11ea0f30e0aca9600d54cdad1b11620497b20b4d5d5668e17b6171418de5d32cf12bd1ab65c88bab223ccbc06ce570542f3d356e41f7cd9e9e78a5b71f660365b2b7d84870e89ec6fb6c9de18525e12f94254064450df0cf837ff9fc6556fe4014c09a075dba1a43edb0ea9f4fabf56ba1358b9ebc301d2ba792220dd55bd0bcbb8b9759f44694710bd3097ff106b6d11c4adccf4e613d05dbb8c10f6ab02b94d83e83bf3a0c0dfa9f28d25f3aa4d7422bc648f99c6ba5be9134958c77fb2f97acddffc66cc99765147eeaf9ea5d9cee3f460b9a4b02d75cf76234acb905c478c08232b3deff7c7bf25df6ed4b508e4657d9bf18ff86a61afe076f9b6ac0fffb7a5a4fae9328c0c91e0152aab47fa147690cf6d933d923df5a015457b176b2b2f2fbde115eb87856649846512ca0594187cc06c0abc5e331174c89e9daa6a07afefb5999cedcfeffd0c0714dd02f5964464326e5d54fafc5f44e7b45c3327ebd51ef84a27d5b5d46fbc4686f23303bf940df2a4479b4eb1b96b3f1453baf391310f7ce27df9c278065259b34ffff453c821281ad939f0875ce296d36d308f1821982ffdf2d1c0a9124d339cd0373cdab1e1ab39822150b29f9ef3be2d99d5a39673b1c9abce0cb064715d0b011ce90ad8aaf3652cbdc6bfca2744087b93b1762fae0730f6289d4bfe64698deae84007c67c15d60a89829b7316a43cd79a9a147a802d1a108a5df8ccc7a576276cc4200cb12b6fde43cc43da4620b957362912cf6fcaf9962701759d48c55beeb13e1941f11b22853d25675d0759cfecd74043bcc32da78c879bd133ebac523666a9d9adabf6e6f0a99af3637621c5e3a36642756f980c3b9ca7c3884e9c86eeae042031cab63c880695c95d6ff72d6f00c8c629da27f7f20508837c04c51b1ecf708b2dcbf536f4d7c05b031072fabde9cc47d11d49c2277a9bebec0967efa72c8e9a49e7aee74c11693ba81491665118ddd4958084c89df49cd1c92d8c2bc0a933df5f2592e6a3a3c5097963b3d2b1d4524153cef46e0dd1142b45fa70798d1cabc660dae377f5be74130476b73b224e4a460742e985f14f5b52294d0ff2fe3a495075f74d904bee48562e65b7981c384f1247dda918ef777e3be6b45e5ab61f6e1b249b0659fd8bcb0c813c0d40e9a86d9820817185ea09bb55832f1db9af5531031f7f2492333e1a823b19e7762c701a17170a1e0217fa7360ee1a511fbaf8dee88ae53eb49d9c8574807ffaf3f1c95704042286df338f089706841da6fd50f4e8e6f2e3c42cca26b98fa3c5d273aff3871600c21a36e4497d92b23a04850ae55d7abf5a4f42664d36c7cfa6b9eff4786a1fe44eb4e25fb8cb552c920fb6483b491b80d987956703880c80463932038487c7e5e0c216f7ec44f7c30b45353f4b4d68be5ecb9274229269b85f66e96f7740c4f6c529ba129de3d662e7aab5ba2e4803115726527b13db78e1b9679aad883b4f848c9a1db77a23860ba33b01189726fd793ebda07afb428882e1f06c788a4f9db59c815ae1c7ffe4c28f9a2441919465f5501ba17d88c34162cffcbc8d578ce3dcd902074e672d41ee0cff969826e2ae0cc4b91b8aa852a8c4afd9a3a2822f99c43c6d6f75d2dfb160190ccd0ea6de18373da16e109ae7147c6d6c6428619c263f3e99125861bfb76cc33cd31d0de7bf5795e8bb8a71313935b094348617ec4ad5657bb78b3756e42081bb8657f22c30e4f5f51495b2c89923d1acd044a3ef97b8aeed1e92d890f637205516584ecae84234c2be2860a159011d175ae6adbd10bd30f2110d10d49b4e6dbb9b3129b8b79930c8033b8369994e667900393295f4411b5142ceda6a7e761f701baf47f6a3080b4416bfbf0226a884db4748d82a4a6175dc082731ac8d76b9dd5ba054a9c150254b460440f99e515bff489a2cf9e0a3a7779c6bb0ec882d3ccbc75ad492a855e4e58f3cdbcd4bd219aeeb1ead49f46301013a0563e4a35b6f8ac4e5d1600baea1db625ac2f928f7d13dddd4f6dca4134dc924a2306030932c62087d0e5c6f1505e4fd2bd9bb59bb01732814030aad9a1c494e6298e1b1c2402af2a5cb99af4a98869b4a23b025022b489d1a85a8834814e749229d830a61a4840b09a461b8054ee000c89522d25f5e4d562b0d4c852d9ed77b316ad853803c4e63248e0778f4a03a13d3e45c5e51e3565ae1c904a357e5b8f4044914bb3753f637c2a0845dced065b470a899a6d5138333b285fb1379b5859cc26a73700b5613ff7cffb12c04e206df8f7fa36e06b7e54643332dbad7724fb56172c00068693ce26b857195d2328a3c15d0f6942fdb5ae7b4900f6fbe457aabc0d574678e3be16f238bd691070952f6a301ba7b416765f52a7b7ade7a5de8593e4a071204a60fb8b8dccac986fbbe68cc874188964330f7f13e707e0f05781cf50423e3f6728d0e1b03be8d56443a3fb5f89aeebeb9dc1b271904224af8d39fc2f6ba542b89de9c8fefd97f0ddc0ac27be498deb68af940e6ee13897eeb469b577457f72bfa9d026d94fb2d4aabf576d883bb85535c97e593e7306178b37eb676ce82f45fa78417534025a7fe84f5bcadffa48b9a6c75c5b8c5df2fd8b3960678e35c8474a65f647f34afe9616e112025c92034951506ecfbfc3db88fcf381ae4a076195a17d54a5554d3ba56a118d009e5a021ee959b3a9ebd4b239a74c590c15afec000fa7f25f5e091754a2f6a925828f409cb51c5a0e1b5e9808d559252380ac7b95a0c3f3e004ffab5f5740bf5c3ea7a6e2f79514a225cc170d95f6154b37247fdd63fbe3c38fb116fb81029c798362fb21a3714b75b34c4c2037f45a635ff831c512b70a98ed2e4277c61945c9364a12cff01b4626ba4e0bc2af77e571b05726065414a8b6876d84cf191ee1c35c5d571f9949818d583a5781874103cec56e64c268a670b55841a873a24bd8ef2987e7527fbeb8aa35cd5ebf1399caada5ef15928a06c16ec7703abf85fa45c42554d36fd98533fa80e13582d8a1bc793d8a2be34c347138f682842ee069e26ca9489be0e3ef19d29fdfa598c5416eabbdd5e0571963069b001acccfc1d2728367c4235928663814318a10f70c2167ba43744d553b0b7bf073fe29aced95b3bf2c4222e5ee2e4dc14272c05e7b59a59ac9b73a90a8e839793ed3b13d88086d09b68eeacc35fe45770e1dd0858dad3200458b2ae084b839d5e66eab58c32d333b8b0453a08462c33a372ed965221dc9478d8d5c4cfc865282889dbd86480ada62108924e95d6a281ae30cf5831d7ce560029f22b6c3ba0a51b1e872fb2662ab25798409bf5b48f04c98037cb87246171224af2e8a37160b540c07e8d5c926ff113a60eb673f0ca9ab6b7219ff281a9534fe1f9e8fc1380353ff45ad7619a8bf4745760c3364152bdda821fa97297dd82806052d1d504fc9574692aa25b2e4e160b48e65ccc9573a4478ba742d8ddb8007435dd581492286fb85a13eed4a08d96aa0d40b82837088f8c456eaf025b910d2733b14abd5a0575c5813ae56b1e000c4a0c87f3ab681792c788621d08586bb49436395d046b066e86f9ed2e113b82cf30bc44561e01b44ba89ff779c802b201ef778d749ab42aec97a1c74af81df7f2654e32eea6694dc2ac39f0bbd1087531e9a75851a7afb162d7bdbd80343cc2814d10c45b04051bb1692aec32d8841147066eab0dadbb848f86e9dae9adf2794e9fffa0c0051143b55cc373dc5bd63fab5a59ce8264891a79ccb9091a0945fc83f59832ee345107329ac43a92bd5a5fa216f57be5e936a28bd006375785ddc75d313f23c3ac4e9980746b6b8d8eab9850f1ca46306dd5aba1a8ea79de6f334b21b6e4d95332958a318e3daab2425cb0bf7e00caf9c8b39fd77da59d9cdf1099127e067bcd87dbdab4cb0774f3f1ce992ad79451cc4f36f94e21b4e00d4541db802d47f2242c6b929c5df512b3fe77fdc455014ed90a0d2ddead6fb8f2922f9db39c12e43749031bfa9561fee61b3723b997a23de9800711fc7168068954d641cfb319e7c2c95662693bebc8cfa67fad26ff58e43fa975fbf2f0d110a9975877e139c3fea875eaf37bfabe9434740444888ed0efb5155818fa3a9a2632c6250f106db1d7a90cd0dadfa9b8f1d2234c1fd94db97bf78b8c93c3198c078d9d92729b09559b1c124769f2909ae4751b031b9865d7195124edd607ce14c620b54c3718f64d18f90f28ec363eeb3e94612b5857fdea838fef91188adc54fea9bd6bd16075b62662670a490a88f3eed222a09775bd9ce501d2ba41fada5db657ec4b85410335286b120051a2d05bf9c1e18243eead860c413257997840da5bdf8deddd3905ae6e9ff3230d0ce0f184483bfb3866b02b389039e07c83856eeaf097efe96f1055cc53434c9e8d78fc37bb6d3536fa4b03032e9696af41def658c05786c18282abab17058421c0e9f800adf4fba34a467afa55e1df70cff12132043a31115d75acd0b94a53afa4871dca75efd738630c4974a3724785c91e17aa0e14837f732dfb849cb0d6141c131c252a6e31eabea3db1ebc5a54a65a2798df059c4c54d3abceaf5c9a0bb116e1fdd40c2d57600a2f4d7ef1b7477ccbe4d86ecf3a5253e151b8f709a303e303467badd16ac4a18854c514673746d15e44590f4682f4d8b3df92062f84e909529b4481f4e1e637c8a7e06b05288feaf715c3a0d189ef92efb4fa6484a6ca48f7c818c4de2e0c6a2e072255e916c3c483127201d3b1619d4ca8c05911949f54337ad357f5d665a0def6ac73228606ee03c22a4c215567df632e4c1adf1f34f68c917158fb514ee57c52f76cd1d3e90528da3eedc630c380058bb1fa5fac644597ff5000a204b97ce2890be2fba3702be6d43449ddc21cce6d19a0408e9f38090e9e6c6c9503d0c97b549712d2f35638991de5f7938b7c9a1999b6f824a0eadb686f4bb7035ed124f389967d3764d6982098f0d28c87a702f14354d03aa69db9a3c048fb8eead5b3ffbcd21620e610dad556c1e86be88298105df471c938e9edc00444cde3b3b8720779d93f95408903be91023b3ef83c6592277a17059996206eb2695b675ae741830eaa1f78ecdfb9d9661f8039b2ae823178329b1b9412ea6ba4f7b5bb0075e28590575b1b5a31ca5a96e5b4fb360c113f679b3bb7df8d46465233b4c98e20672695b141fdd3f0acf6af4a71fe5c790137bf64c9ef3251fca61b2319cdce3c346846af39ad73f1d267945fc4c695aba5b8d91c5061d37ffcefb75115990176447687c19be2b550cb444653c610f4ad0cfeeb774d2bfe7a981a21b176784200546cd919d6e88c89effe6f3bcb65b2e461898201dedc881af29cb1e24877dd174885339b7132d6756e2d9c9b987eecad04c79e98dd8af42063b682e00bf42be8707c6654e914fd4757951da9a2bfa0fe73dd4f9eaae128acb6318e522c1c20eb413dfba0bcb46d0743ec4df030f51f9c4ae0fd3cc13c20eb35fc547897a686f594ea684a51759e1e5d0c4bf7463c28c6c75e295c339c10a3f55aab41540c77086822a728dd1254179f95d4ea72c458da71c5ffd99b74ad94c60ea1ebc5f70e6978566e17bb1e9316a75ffbf097b678a4a9e1b8ade66b44b7010138bb5920bd604b6568cfadd6ec8602f92e6b65ef72b88606688bd06dfd4be002a8a132af0b98504bb33e68429bacd260675f48fe1497b218941adbc285f39bead824d15f3db35653b9103194843e939ad60ef12ec18627c49d31f571fba984a05ba50fe8c25013b0ec702391bf3f0831ee13d21333ca82bbc42925aa32c9e366addc4bf07c6e40510e293bd8d3f79e43e68cab852accdae9c1f49ebd825186ea267cad88997d8002f4da2730b44dda7d9d76c652f1c6b6e20aea846b4bb09d04bbda2b3fa452c6071fd1b38f41346436175618d124ba5d50807241077bf5f06f1c16b7f9dfb4f2e103a86e1ca1d81b86c1cd503f25f5339ffb53492b0c6f3e3c29aba46f4a378d786e90ee2ee7c1cacbc1eae718f460aade1b1b1f6ab0b1e981ab0eb4cd0cc14a17a19ddf6600e8efc69a398178286c6aabf2040112a6226632a5947555e71db21059b83837e7f5b97da2a77f3bdf70470be4ce4ffab0482d8c394aee88b590f97a47d3cab03ef4087b579463e380347ed972466ed7d2118934cef58a431434754e128ca286c70ddecb7374507ac64cf7ee876fbdaeaedbb2ce4cf801e56457bff28b258c009dcdda0b87d0ed68317e02cb4fe945bf69b6dee6e76aa1104b24a097656efcbea7ebbbd42ad8e5df60acae4734a9281ceb6941844e2b7345a382cbc603c1a62cec99608e7b02c6b6e140214c14ab06b2097e99b8b5eadcbc93749c359b99a33b52f6e01fa5f5e76d1409f7135c070d10c4596bded43c2c0615f720346730adc01a903e5f7ddf4b3c0a7d26427a7b9256a995600709ce55f84dc45224f53062ef3a0a5b285890d237116fecc5782982040829b5e70927c26d192772af26507aadba66a1c263e2c2fbe7a099562cfb9b07cae0595b92d7d3d0eaaf2fe9924ca39d68ca762f991d0a5f717960c1d9f924e891751becd6244dc792a7be9e4558bf061001d981c40f82e246074ba7feb44b4ae33dd4e17b59175317d5c001a2bec8952eacfe735b318d07a79a47fe523d17c33b046e4b16fd0d2122c8619549d1a064183d6cbf983041f8bccf298171392aaaf99a2c27c07ace33002f8ff2b7b650bca8b025b214b972f638dc8a3926cef14d6ee698db033aca18c80903a33301dd9c134844971a0a41aa6442984161d8564c408286db8acdc1b441408bacc5c22960a11e687534f6537a4d25851d02542092145d2ac10bf597f2b774b67e246cf35ab1dfcba6f0d67f0d69cab68dc2eb5f050c6a097f1d8a0c9c98b02cd2d6c1481dd985be99d4623de43cf592b4d83c447dfcac0c62f610a6e72360554de5c8e7f6f26c840a4071a229d4222e1c34238797b04738065ae7a06b5c607eeaca55c571ca94d2edb4b6129b8b536f3224d04d0a8c94158bc03d173f62cce4f222cf2d25a49a6291e18f24e9a395c28e0d0e1e1dde9c82a886c6ccb57dc65b6b87a250cca9e96a52849c260d42f16dd1298cf1ae93cfd32120a30909a78ad52d40e8c922b312f6a7444a50ea2c2b25a9abc6a9ac409aa5bd251f919c5ac23dc1e9ee10779f59fa35146fe3635d3cacb816201f9177585c58664f7a0d86270b3d697a272024a5ca2b0047fe0335f3267a7a82baf01910c97a3bb147d6c86a97a5c8c4325bcfd2ca5187cacd9d79daf20b925367d1c47708207ba4a9362e8a0ca1a72d310484feb88eae896de41d347e34160496fb621c0204bd2160de853d47650391e15ff796f48391e2dcfe4693c4f516eb30c2ffe6d10cc54390caf452e54c1579dbd62eb8e0201d8ed43abc2146fa38c1d00d4a1aef500985b9418a005f114c136225ca0b2fc0af7d6c3da0ba23efcbd19a296e7d18a20faacedc8e6888754949febcae025d0f88e52deaa24241d42650d5749c136273b50cba80f59172022921be09551e095242c56898a20175b3e9a1b4f29d42aa070b20f676e797758694f5e53625d1dd2c00ff6c0b3943939a40c62b95016777e13cb1b4a3646e9dd80460550906081550340de5da36673467660ddf92da0a3d8ac5c20ccc436b0f631a0110b391a29170bd099a9d62dd0efb02cc68e0aa2dd2d393cf94774c1d135d7c139adaf3bb55a7dd4f1a4b408294f01ec5d3d6186d0158a178302526b2ffa40b609f5cd8d722b1596c7fce958231a56bfe98dfb89c0028bb78009b2cf0dc1e0dfffe6eaf765a73a6fad71b58e74e130956d755975457d010ae9e07643494bc978efdf38011592c93d3a151f17fbfeb1b3c73218a4fcb7708fcda77d6508fee0d0fd27c29ddbc186f2da9a06e2c25ea7b4698f19d4d2d6d74fbf8ad610b4300296e150792f5dd312e5c8cc41cd0cc72911d6f753ab84df7ba30229395509f72a6da0a07312667a961422c05730f6b320b96cd7dad5ab6aebe5eacc8bf07a6cc510ac1cd426db52e548c2389eb6604ecb4c2f459580e109d462be06f3b2a3d0a30c1d4147ca938427175b0e2ee23042ede0c02526ba1eae9643437b79a8acc4d4dc39c33e3432758c03657a046805ecc89c56a441bf566d08f66382f6f6e5422d54af56577195e932faea9d2683d4d59d85b3264a40594559f524ae304137cf16a89181186224af5215388cfd20d5cc4be970255371a31770a95c4931e4a804caf56aa867bdbfe18bee9eaecbedf7c04d07923564582a2c9ce7fada7d39563a073547f850dc2c4aebdadf2ec5f1d8e1ca653407c27aaff7807743d5dfb01c2cff23f101e66a8933a867d2200932d8d0f5461cea1624771b42144f5074522476eca02fe15e1a3a245846f8a1c2f15030eca53d6e1f8a834c69234640941d0751b0a20666d5a353d5698eb59a16ac88b2ef173386dc88524e8c19678644e9a08c19008092f89f318f39ea0d5435664c67a234187ef81dcbf930c84c1c73e40845617d8e1978386de3f914d8c2a7eab5c75f8b43dfc44d613574fbee170e59c44b2cf2d3086717855d1ae3d272098110d027663532cbfffb6d218f6d94012aaa49a2b790188b14416970c40715e681adf0b7eb771800f2b26d035c7c5f38ef23a99afeb1c852bda22b7bb6a3d12dc6175c26954b323a5d68200eb3ac64302bd36083b46b3206137fb19083745ddead774fd22e11e474f6f0bba0b04cef881683d4369e6729b70d1ce05eb52357b0201e010f6cc5ea5a15e621243b154636098a81d4630b6498ab54938c87e3602f4d109ef293d2473c0a2cb4b366d2deebfb7f9981517446bdaebc5a6c70d910c6b13a63592c26a068575d1fc2a98b4e9ed8ca34026cd937de8ec99efe4b3fb552dce3e6ba52607b126e66e18f51ecb2fe02850aa36b31dd9a1395093ed2f43065e99a697264ca7a2387766b51381720bcc1d6cb54c1cb84b0bd93f8fd6058f8398eac419f52ca4e3371d845f25c3c208e0ac619162d23ee8c519f8caa3c42c0aac332d3f2d5da87a0b18179511a6511a74a601fbfc9d1f9f695bbd828a28d94f00e2330035acb94d26f64fc4eb825070dfb8c1216d8a5f3b6350ae0796fb430e80cdab600840a640f94ac3bb46da733dfc0ec4879ed7049f99db8acce9bfdb03dc5f0a1c7ea6eb59c2544debf9ecbf78eb8ab9464d353fd991bc304fccda028cafa5e937f0a0e8df4d1ceac493b83f8a484a9d283ee13f6a65df0f02b70686ad03f464963d0b56d9119c3beea720c326b2ad50b86e2c828ca55492366df369ca826c47c2b5650f19f421d2f0f2b4d91e8cb7d4506deb12d61223e5906a449ccb5d4e3266fdb55f022cd99fc3d987f50e78cce58b51729f5130b05944da63dec208e219106bd63cfcb9da82428baae272b3ee29ab740e2a9ca2f0c16799453baf2b62d824c8a4ec961d8086f3962ae40799e90b76d0b152baf1996e8da4284a13edecb6ecf607780eca07b5b0262c71e3a402a4955283cf2c4cdb7de106fdca2b248a09cd2ac8fc696a0fc549f7b28403079dbb281cff88db9d6348f2db5757a4d9c3dd2f3410d49ee748961d06d5b61900b713b518da3cb44a54511e8ccf6215b9adff34b82acaa92ab70234d96387ab8b70095b4f21456dadc64bb0c00085a2b24e46ce17ab1de3c9a0e50501a52658c737f08c58d94fb861ca42f23abe2dfd59ba36200978cd135ced5e051b169a78b7f92bebc1c49d6b2e030b169e15b501f263aa7f5d5ee5a2369e95b00e6c8fdb218009c6a71ae6ce65c2505e1985fc8d243e2fd7b18539e230902fd78289889918c70d3e3d1430995bcd0abb5620d715758885e3f2950a9f82d1c0c518bdb3691a8d95d5b192b376ef908a59ae4e6afcd52cd84711791b77ab55e96e521a66b7f6f688a3be0daedb4cf204acd8ea5c8077707561fb6021856b76306fd85a7e1d31229d9e3c5fd9ae5f0c3e5b3af32f5069db8a3af4108e52d1792a56dda65c6cfc539ea87441fa4f0a0920cc97e6524a0cccb81daa5b14fb34ce5668c1ac84b2556078be8e9d55f431f60d9b96974d72647afeac0db65ca97b6e0fb794797654f3560e135fbff54072267b21c57f27d311b4e790e888d0e7b56ad294bdc21501e08e2880359f05f620554813f89855407deeb01228bc44c279e5d6dd179597c96470f7df6c30932d3689132a0791dbc49f3eac7c4d65bcc80a1b0f3e19f594a4ffd3e2bf1600c6539223e33856329b4970335e2e4747086716ba46376762e984dfb4c9d0677f6ca1527a7426c818969010b81b575ed4adceb8a9c64d03e8e8ea56d67aa66ee0681da3b37e7398f551f00c122e610d9b7041a427b7ea3385b71c0f2c3b44242bd46e6ad84fc656b823d9ff849c8e9998287bb6d5858fed93ef50acabede20a7c77ef2e5f5bec70f79c9707aa45d81740135b815ad2703b043291b3f40e3bdd3b664216e230fcb30948abafa1a46164c649665bfddbdb29bf817b87b572a797b4249e22a29d1ed528d6c935c334baf0406535287c9aac9bd3e330b6369e10b49c3980ae9e33fd7016bf87205c60f99ba1d2528db8005fecbe733bfffd4a54f5343050345c784abab321abe5791e610cefdc5f512ee907fe1744523095202eb132ec6ed5976f839672ae8d4e8c44148a904409104d91812205fa5efb0213d37da532a9839653b98b8e9a8b9528ca64d524c8f04dfe645045e3fb79d2d7e532dc8da9f13914a98af93c55e89bc757aa421eccd02fb42c2bba308aec3645508871b9814c2b5a34ff6f8473e3783168a146f6b2037e3f8bbb438cca98e9992955dd35e74bf89c4c4ac1f7fa5980f5bda640f51ee227084451a708dbf3b35f8b73626fad0da56589149b568a0b9fae15e0e672a08bb0341af252d0794bf14f19c77fe86785ed996161dcbdf088c499644386259c4a9d97a7fb2d8a00f947f7f2d937e69c8c9f971a9189d6922e5289dc2f453d6163bedeec3816dc32a1db3a9428bbd99f90140a2dfa715a9a57a6f0f70d365185c601fa15fc21f1494556891e9c0f4d11bb6ef296868262232d16dd2c7150f3ac0ecf30beca6baaae8340d40ceb11beaa49fd36e3021a45cadde4fd565042efe2e48fc8a3c0539f0cd17f024532c86fedd4d5ad8a909deaf3b2c2674a73e73a3540bbc0a2135493917596d701212dbd43664303e8125c15837ec5883f6367c7dcdb7ca8dfb59d58b15b020cb7644abdbf6132f55e882fc1ef47ad02f05ccae2a747d915737cea6a2dbaadef8a2b1d14bb2e6b10b10dc9f1daeb19e8bae0f20fd205521f2c82148440e350207216dde6330bc94b0a9f32ea5f29dfb6c393dd170c86497f1f8321667f8f34bc1b7839b581bc82a140e5cdfff350edc1af77f4b57bb085f1adaba3c6f0a4db2397c8ad5f4a3c15b841480a046b8f0a8669b8372ef389b1297e71e6c92374f0d55178d6f0686ba889ce2f454b309af46ef8d0b032f529e9bdc9053299612727b06dfd4d13f1e345fb1b5fb11d500de0295b2d0d8eb56a4ed70c529286314aed2d27882023d66abad1c4e0657092f54eb2c2fc10daa913669257808d71de3102a0e620c03505b76842988ccfc67e9a84e4137d34c23209751d17756a05a17fd4c4015c8b61a80388b01e4d70a9bbb40149eda9b56c7d0419e830fa394e5333731081a72fa0071183b0a1921e0c9451b70e52220ce1578b2cbe379d4008956423d1edb2f88bb284fdbd010dc959c6c5bf525ba2ca95462a9fe958f8d662627075eb80316bd50c30b8d4b974fb9235261572935c472a9f2c3e1ca9451b8d4c7343e380aad3cc05a09c27728462f0e50056028700683461d50802feb87289dcf49e5719e0f446e3465a011116aad2b6e4bb044cc56cb5841adae2b180044f76525d449cf6bc5442bd6d9871252510b088c790bc9692390a24588c03db06979556e0f72048225d4294956b025477899bc1a953bec653f5edca47cba4542109a8348303431dbe5f2330eede4e3ad2710bae318e9d1d3234238645c4243c91c497b38326c9cd0085f9e7fb16550798771ae2162040c5b131136e8033e48e8145a57c4313f669e9819266a5423f783805461ec6739b2629837a0d25ec54f75c7d4032968cb00af6bcf0274c18ab5c5bbdd761ebfcbcedb197dbe7641cde6194ae58428161b88c305999045f98eb48d3ef4585b5a505b58d0cab5f50bc2ffd29fe877da74b4f636f2196d0120cf640877dd5fa3d8bb95002c90556970145c4a18910b4e08558f9854c20c570fb9a46a7a4fb3ae835919999448926292986a66c3b3b5201a4540758d758efa37b62a3f4915c0c23f198f40c1b914c3525f3ad3298d1e1d272338ff16569bcf0bb8b6b3ddd9adc19b577809920f17049fe1649d44982cd6ff5e738b16a21327415c3b5637aeb4e957dab7712a8dc2b4146362e6581756ba53d4e537bf2f724d8864b1c8029aa8c2dab43639d2262e084a50c98f08f15042ab76599fba8b89d99df9a1a999d5f5966c5ba64f57ecc2771eb5206771a86d78f274434be63624f4b2b92303dc5e47a4bc9c25407d0b85ec203dc0ba815d85a564794594b1cb467c0c11b0dc4b04c5cdd61fe60638edbe4d70ff45d7a0aeeb2b45c528a2e7b306bdfe78ab5140922f2b1ccd344c8d994a22a4c623dbead08c0d8b25deff1385e1cbdf862c8f753103ed32d0a400a0356b4a13ddc3a0e648059bd7d0dc02b5da5bd2540ae1e3636492aacbde2263de4dd778bcfa6ba9a1c4fcae910c17f54be33b41d52e5cbffbc80df01c63bbf05c5eece503c859ce0e01418261245d8f6cbc742635f5e0d2400a6a58361dbaaeb817f59f187b90797efdad29995fde39d9196e67d080a21d5412e889a8f47559c128dacece4f003702f27cd8c984b1efde3d0ed3adbe5eb66c17cdcce77676591aa9fa0bdb54830b13756d3becf749ad20293c0d243853857523c8a96f03a7d3a631eb5acb4544e978558eb55cf10a3aa0a9415562e5145bed1041ad7cc00c183ecfe0c5ce396d3e96b877a4f502bf3b41469b5f9658f6ae47d139afe384af8fd42e6b4ed79815e13d2ec4c6dff02f79efd835ced007b08213961aa974996d19f911f10fa765aa5c56f52022769e6a31e35f62a75e2b569bc93f52a3590262ec06e8d4795840dc638bf8e7c974b26f2050b3e48da089fd0b0fce41730646c9e5a452c37de82e6961ae1a181d15b1796472c80d9e778b8847621f55bc241da6c18282aaa2135045683c8d67bab4153034902765eff75ac3bed9e2fe9a62e2d1b00086cd7f2ddf29974d667087251c89c86425979672ca16b74b74f8c5c95485a4d526c926e2c1dda6245e4ddc461070a9d5fd1ad666e4d882698fe7d37d641f9b760091ad6f4f24bc9463bdd187e860877c73061fded036bd49f8d95620229e7a9fa119919ae1004935cb0dc5349ba12ec61351878ecf08efba3b840017fea813bce1b644fdf061f27a351c544775d5611df213e5bdd36e3aabc0286da9507a8cb576f21e684ed7ba860e7702559521bfa9612cb9bc5915d63dc0bc4fa81b12d4d08a3af0013866ecdda2e0c4a867a5d3f70c2e369983ce5209bb12a9b51b1cd5ae43c18eb24855db5b5f00b262d0fbc7c2fbea378c5d205b785a95e8e20c0a88d367e6988f270de1ed5de10ea1d0c73b1edf2dbbc8e3499caa55495deb4835e84b11c4686f13c04c75010a6aef38029bf58efe4b870a6602f9c2d383f149c8693f797ed4692d37b59a41000075c70879775e0f7da90ba6e2995d21923e5f5ed7b547e464a836c81102fc6fe7afda0baae5a67543faa53e9b23db43c6792601e884191666e8c01f330c0e7a44a0965281f46c3589a8cab922a1c801f9193af851cc410c926d6f9bca4c6ae3217023a94627aecc6a2592a1b60d6fe9d91b3dd119911d9597dd5ec85dcd5da63ee8fc349d275b6b49ea319cd31ede314cb5cd6b5129dbd8553aa3cf04a0af03720aea56a95b51a6330c1f40e6fa4c1f92dc00bf14608fa28c3ad030e919f01144731b70fadf8520bcbf1e857795dd789953793e3c6bebbb0926fe0d3efb223838bead63f1417cab514b56e3a1f49fae21b81576c1dfab3ef0fb5f8234aba6197495da03a4f9e2d35edcf7319884d95658800414d7425ced812bed5e686a5243a2acc0a62afcca20806609e927d6cd11d6cc52aabebe343d2337403fff5bedd95934443d1dedd79e0db008d90135b0744e6d740eeeeb990b28f6d7de3cabfbbc655fe5412cb76f35bca29c5efa3eb0af269fcc3fb9ebcc79d5d5d4a787d577a33128d4d9d0afcc810cfbb401ed2affb718195dc97257d673e4db10614de3e9aa6d8f0e17d3d93fc7dafc369e78f046301c4976fd07b1b036bc67bbae7f72a4dce6567be049ab648baa2986da477e1718ff9a96df29dc231e6f857e40638eb65c7e72f811633cddc7d9ab5b1fedf9c2453dd637c18ead88f704bacf7bb123c2da040ca4258c40581530e462c22a11edbdb6857e41d431e7f2336ee26a7177442a19c6108d382db730950dff38bfddce27dc0ea4b9ff944de31b66a33a3ae2af4e648f373bbb6a78f66441b56a19e98a7800e2b2f1b2f16a69113dd33e136f0b61dbae88bf2d8841f715cfd0b73618a6d6e05a0e1ab9898d16c2aea5110b23a5b6034c6bf7ca049eb0b06695962beea3f192c55ead9b1cb569db89ae208498cc6644d73f51c214e4ca3faee6428fbbb7d6c0a13873d109c1049cde6324dc4e376f89ff7e56f433e97cf9e8ab9cd71e318b4b5c34706348a40e5ba800401c88116e88f71c9b26e75d63a44d63eaa159d20dab3c398dcf23db48f7964f287f1a8783c877bea0ef4cd0313ffcfb03579b7bc1dafd7a3a17e5a55df6b90940ca1443ee1ea35eb4e4accfc07eb98a4d9fc02523df742f8ad3fe8caf5a4bec2505ae183a23488181736a83db66de23152ce74e637b71f22624678764b761afe1ec76744f43723ddacca9924bea5f9eab3e53dea7f62415f240bd59117b2c5e2e2d5f53d6c9465f408f5d986a2b435dfa389eaa72c09df3d25990c8953a437f28a6011391296634158fc5a33a10571f1faccf8e6c349c236568261a729f7756868dfa6ecff210a0688df618f93146ad9bb8087d2a95c5a08d3471b4639777f544db145f0fbd34a505b151553ba434b65a11c428426db704a3413f180bc801c5ffd587ab57f37a7f25122bf3f1b08b3f66f65c053544834b25c623b88443a616f708d98d8b72015e6317fd2b5321192f8269d47d9279e51fe21d222fac114fb0fe70fe15947a5eb0582770c038ec2ec345a2f2a463d3bad027438ed7fea691990b6dee5ffec11ae15b36c2382dba55d4a55c78e2b719738e9d39d5450b5f53034edb96ec374a9fa810cd0b68c0366f17d81fc9066aefeb7b04708e32b4b48199956285085f70e0fcf5c3395b5182561a7736e5a4d17cda2cc240d27c318a3dfc414acc5e21cbe9627003d2c7a4405521366f11cca3a1185fa508d2bd71e860296926aa41a68af50ddf2b6aca89225c03462b985ad472981c52ac96de38b51e0cb89ee2ff1d064dfe070bcaaf62581290c15b1c9f05363a05e74171892cfc0b19fed8e8b0ad382313ece7ddf467f9e2a4fd44bf9c007647612ef610ccbde8aca832504226ac6cf794421d79fd93160187352583377d946849d2a8809827b4669616b4870140afdbda3aac212569b5c7e526a7e57e7d358df0896f8ed89d3ef53d6cdbd64abf054c8026992556eab4460c71b182e539c2438b51c09b73bc68966a97dc1d68cbf1cd6480d51b9b06f256cdaa2f88377fbaf8e9cc42ce37c6515670426d5cdbabeca7dfa188382d777094bbcb4c911f715e0ba37477c424959270587e48875833603d1f62bc98cd57cc85384766341fc6513bf39ed940280ff9a66b9163574a6c87e3b95a4852e663bb65bdd1ca16036c5bec121c6ba1c009730f4a06bbf0e97a1203fc1300860252e41a40604039f65739220d80c1c92e2a598e46cc8abf219ed172eeb4a8286ef4a34b2998bba67047b317566892f3e800e97823a361a69f48999351dfc7e59794e4b0fa9068a33945ed97503edfb4a1e961d451fa551338c7c71eba9ff530067646b8fa4f0b4cd0a98b1fde7d6730e06e3e84317c34205ce3f6d50cf26992e2da42524168dd98088122b9130074009d8c70ce5ea053d69b53c3b5fbd0f08655dab06df4bcd422b7f0877eefd534b15ca577d0b11312cef6d64675509bfe43ce72ca37fde0ae2577d3b2c9a97172fcbaaf0b6b81741004a63e2e8b05fea33396f6fc91f11546d02df1e9b85f6ad48480c7f9c19b0802ece9d2cfccdf8157ccc0ca6be995f901ebed92185e657f17d25dbca4f1aec2f12181ed121977d561305e129a1a0b89bcc4543d893adb874511a4ce454eebfa0ee542fb59956aa60859a6951e274d4bf9dfa61ae1d6c2d13ce38ce8c7dbc9207c58b8323187e1e51348b4837c1e0599f75287cb3f417cd0e81547685b0a53ae3a4a133e7c152f9ee303e3f8a2675d867a646767d2cd4121590718f8f0364f8435a4ae924aed396dd2447b1ab356e2856531dc4daf232d3ada15c9f18f3d9dbf8df8187157f0fee5f004979f056033c34f54ff3572a8c592cef6bb2e74b7cfd19a9525b81932cc63e57906941e3ed876c71e1922e700a306a6612946c72d1a961400896cc99e88d409926bc4b14f88b986799d84611fc368068fcf96fa4822116bf3fcafc8024382ba6c4f3f23671fa4854cb24c5c6b0beeaca3592306104b8f33ea60b0c24d681b2cacd0cb40e75ce2c8a3d27e81ae79dfa7ceeb0e018319a5e9ecc75b193e0d8b9f3ffc895650cf7f476bd0d1ca369569fd4e5a8ff5c034308de8489b0717518b53dfe91083ddb1fc90c99c5ca9f39db0e09d8f785d790a99e7d712314bed60e8ce7a2a6a6472741b61b2383f7abee214eb0be6656e75e6053355791a7410cdf9b4a39de2c5e54a7a6e4d2baa740f0144d4e4b184e1b31237ecaf819d2a33e79fe5f4c804a2037f51345e733c27bdd768540bf2a75df52c0569c2adee029c370acbb98594b5b064cd06e550a5bef6409603ad272c7abd29bb7463b86fe76becfe05ea39a66db18be14b91fdffdd0a70a52ab09b15e3e3116b22f9329ef38c085e0a2fbdb8b3dd183bc78cb1c74e293350c1944373ae1492ba1ce378b581cb330c9aa458801cf551373d5ef9c76d7312b8e71e18bdc188fb3ae91743dab2241e5b00986bea03adaff001303a529550ee18ba72db63c9038c2db5fe01c09e76d5d4ad6567629af3782d2e0cec6632c6446877f80ca1345c3c28a949f54d52263451f36082506482c160c9dad92227509ec984490f3225f7993ac0af08c480b8197788e0a16c6db2f817e8044b62ba1c3a63beb1fa2f8e3379acbc5c9760582664b9dc6e849872f2f53076f461e8188071612c6ecc3078d382ef52ca2017b1efc6b00361b12ea9345449c7c4e857e7e30223d0833dcd75d4a5d02311cb12bc62b7bafb1963b0cfd2b5ad40f3250ae0ab435b008a6f1681cf89dbb2b4a333b57cc9a6c73cf42c847272218340e996aab089252c14b1745a49f3bd12b2008b5ac22bfe6a88803981658395e0f3e3c3b3b5ed2ac132d97f2cc0faedcab670d339f2683c46d8580d8d6248d11eeb61f63d0669b12b8e65e6b40f586a01c702df925e03247f31260a55c4b19c08ca181a259ac3679b1eafa44bccfc41504aec279617dd7bb478c6b1444cd7f2b2e902c557150b146c4811ccf6bbcbb18bfabd008df6b218c3aa123451a649d764e38cbb9eeec60972748652395a3d89bd9b3dbf77eeeae3cd3f2e750be0b6bf856c269c60b4462083d1a231c9f1e976a9db64a262974abcb04b0947ec4262017b008204288fff542c99f50717ae0d29607139ba6a96116bfc952671f4ccbb8cbb2a53659cabad65ec849f3da0f733a0402002c4bfd17db1e48f6bcbf5b86c28b43d57c5c216a106b5475878a17218c4a7e020600586350cc81b9c5d9ba315e1e06b10bc672b022e84144ba1ef8735b1edcfb77e828cac260864373502d949526cb710bc27e65d92a5a0d8bc61d52a45df50cacf7e2bd0affd6e11afdd28ad7229f14b39eb23246209c960b579840c9ebf9a6d1e0305c8356bda44c7b08b5e5690be98c05a02c40444f94b42b1a700009f7651bb8e009a7a01eca607fe81ae92f4b84fba8960fdf328f95b9d7101df1f8eda1b3d1dbc6486a0252a26de3c352d37309f3c9d495abf046998eda35522882d2937ea59f04d1b9339eefa00beb5c3d149fc945f73d18d3b72fa914bc9b7ac1401fd6195e67ef0707350dbf8a8e98678fe6bb4370c306cc1833db7f85c8a0845296d12e5abca7f98bc684f53551f65eb0fe3a825aefa142d01c66eff49dc43ea8dbd1971640bc71bcdcd00f03e2de7d9501337d88d0b0f67fd380e5b8a1d84824cc792cc946508babfaf1f9aebd22cd57a5fb7a56d7d10b45499a309515bf6f94e858dcf5a4e08c755a73f11247af666c1790cad53db202e5ea94a230b721dc4366c5ed7e5c4071e44a5908711b603c49d2d8a7150bf7b10a47dd921f5decae06970731936c1575798dd4b29d4f85e48e7ae0a6eef2b3ace5fc0de11832979511b8df77eb3fd883cb1ba85be8a286aaae054cb992719b3fcd4d54005b527c6fdd66309d5d7cead4ceba9a4a8f69ec6ba9a8de3ecd2ada19e5be8f952b23ecf94dc4373e7e79b9dbe2ee260ce8411c784bf67452da3528ba19a4eaa2e70214b19bbabb9e07957e398696a803988ce3fadc5021b5c94c9ebd0b8818841a90b4715729735a2384db4c0c4d1e9d386af5caf34908049dc106b4d7c299fe2d8b02ea10ef39c25e76aeeab575190d31646775630e66a5a2120309075a79536afc6528f129d5d9689f20a0874b5649f96bcb7e66a0c1c9e2db7aaa60a01ca17950790fc2329a95bd7cbcdd590a96184d6d90db5b8cdd55c54fa809307d92d5ab7359ffb401c9488179ad43b15743531b4a0a902d528f78d844af6d978925ac342b17799e68f0f8c288a37afa41edda1cf9429067f66382c4f79137de65a5c27edf1c637dcadce0bd222286eff2e67cec58b75772334a2ef99774537ea7551f9cb5cc852d3ffc49acae0320c7937b74adcba6986ee6ece571188312d75decd0e38690bf72f7f8eecc01638594e2eb57ffbe86c56a091529149ffc495f26e9c7ccdf80adcc2fe5fdd93bc1b6da950423ffbfdf86e664001711838bc54fe2d85bebd13a1c1b86420d4e8e7a76e03dcef3cc2e0a4049fecedb47d0a47177637c9a04c4a3389403af9e40af87e7d87dfd40def9997ace56f35979b4673168057ef1875cbb35f0ca4ea66372392d6a75c2800fd35a58ed7dfa568f8b5ffc31822f211aece049b011bdf6e2230c45238282a4c2a5bc400c3591b1779edb19d25cef553c74fd22f6f8b89529a890f2e64dafe326b375154f34051bed27087651153335aa6996a89b2fde30d17f3bc996e70fd24fdcb217b428e81a1e20d2a7759d01a94a22ba4d417dd3dcb24ece626f00935e84add505391606121a8b26b5a8754917c9822b346cc157d24e7177f67f8d1da162475b34e9f548bde50e9cbd1918fd00ea36454edf72f30f15a76e8d6dbaed3e1e800ef8b090b48a43d88c6c2f9ab69344730d74ec1c66342f6797075f1bf2b712486108b306c561fc4f05550ec3e9340881438554e61809e9bcf9338e6ac74138592415eec7793baee10553a68e457bce35ef2811a1624abed75d0c113b85037ae306fa2d4033b6f2d014478de91c12402384ceab8da3a631a4ad9006b5866171c68c7a4067c1858e6196102d5bf641855a3f45f91262c0574f020750715b6e15f31f223693a3757397aabc24b3a8accea14538a11a6a2f05fe6bb240bfb32a714448b9511cd5823cbeb44ebf2dbc0fcc313614b6fefdd421a8f1d39a623f8a27b040f987a6c47f96076c401988c6235eac50945a57ada9c620db19499bb4857746365c58f8331ad5f6d2e5d777e09a04b76c4358eb2d6fcde630f8f4a6b845b9fb03e32397f6280079e8e0c14689998be5de70abb91118a8df2f519670c3356c84d5a93b9da1c183dcec07db2a31dbf24a6d017479358e07c811f538de75616a3f4d58bd3696ae37f83d2e8c55157f33e6c456b5f903d9cf00f73ecfe54275a8c56500c360bb58a51fc17d1d77589b672a0a83001c7051f00cb68762a8107e0708671b2007e413e06b8ca75a82aa99f0e6ad1e99dd56008a26b2a799ba40fe3a85fdb2e58d1fd0b2dda2c83580447e2466fd02703d55cf68875ee16083d9e4d4485b735b29a870d49c56917a1cd6d45afe1b14268725a216bf3ad9128fc2c28951cfbe8dae128cb46ed424abdc63e593e91090602441d6d44e04edf8add207e63caf43a7a4cba6e54e4f6ca04467c9530ee5f9c29b6d4e8e58d96da12c61680585997e2d8eed83ab47f617f05efaa2ef650e9c6e87e3a8fdeb8a2b183e0ed3a1f5d205844e4a4dc4b124d04532546f3eb8f9579a412e95e02632af3c90a4e4e28721c849a6269a9a1f3a0073db399da4cc422d6a65f263e2bdd5946e3b4f49349ac45d87a8a78cf7dc740ac9c75c78b61c965c30a2bda91e73d9798b21b325df89acaf7e4500725277aefde4358829d4a82eb7b3f11df728a03cfbfb0b88c56c32dd1c2e4651903bbb3a47f0b4e10be031578de13d881b5d8811f98c0052a90400d64a022e6c00e3cf0811358600512488a6520811ee8c0061e8126eeec29626b426d69b43bc43efa3c6a2ad64dded18b62a8b02dc8de4dd46723a4e2266fcc92e12b688c4a1b05f89b3e2f96b642daab46a24df0fe89e0c86852c05f9b2bfa2636acdb2f5395642f61e9f3dab0c0c67142fdff2b4bed0bb40bf17767e9f3cebc8c715cc2b27dc00babe682b78579983e5b04bbc2dcdb74a29fe8fe69a0486b54c8f9b084bb076c587be08bd167a9979a57fcd1d3f4d99b221fc6e1b6a694c5ce35174e4ddbed02ebbdd71fa9c22f03f6c790df02352880dd2968526283fa28ba5b41929305ec4bca1046e1a480e96ade69c17404509c7f5785819e2c2f49045e8fdd1992999f5980796df8704412216c97285fa9e06668dbe7ed0091ee335a14948b7a5e396f16ab8ede34e05cf36c89167b80afc6acd02c2664b10a6fe49e25687748339c34b008cd3b3da5905087b8f153f08c071d829ea34e54f9929c1d4f0f7328fd36a3766fea63270f2758ae44ae7ca344e22f2fea3fcad6e9aedfc80e01f1f4daa05cef247717bfa6d6a48dc013019f4bac0dc06f082dc754a2112723eff81c2e9b0c023e4011b3d67794133fda76ecb7a522c23313af16e8301cf6ef92f6a27a9c283f5ae98524132c3f60435de5634cbca69c956ea33da46852e0108a7d41ca144cd4e0841144efcd93cf6da341bf50f32ff5af1e693f6bad6230d6c12f4560e0ac63d9bcd7b779a442860c3a518d1a40f1332690f34220bc308f337c2e08a4331e818809890dd9e0a781cadade7cb9cbdfcd97ecdf4cf8ec49ad7f49f2017237509b4cf06e044336c7746406432af9d5a15127f20c0bad0e587cf60aabf71230531fd5954dd36b2560e3169a001f0142bd78f4c2c48e6ceab1851822dc4a9e9cac84df1ec3e057e20971e70b8bf4a2d73e8c87730034d1feeb45d6f8d635ed9480e9d769b392addcbb8bfd7212adb196aff5c213d2771ec1fd46f0c15f6430e37651dfbc754ea7d5e66a3096eee202f1d94e2f4d5759c79553ced9d8c9878b30a1dd0feee0fe2c0846de1512a94d1caf97103c8f0d8d515f37f15d486040a3b95d2949296f38ebf8ba27a53eea7ff0ade981cd6e5d1c1605a236b3163d8d473eda968cc6994a08f8d979dd6d3dabd24191b43c8409d2528fa2a30d8fc00decd65ffe9e35a880163a8a5db846756f56fd87636b12a9ce2c015d563c7ab6dd315c84253c287f48dc5b621a0627c904fac5d5e159985ec966f247108eb49725c37a1b6e11bfd10203de7beaf6868e5f31d081681b2a031bd5522015bbb480db17331ecc663991b85d4055687f412e8312293666c27d5462a1bd2ecce1b66e75c23d64fa42db58d0c6393db855a338d7f3d69523917c1063388170f7e0556cc7020e4a4b16b33619d7768f7d4de913d92f0988cc6e21b62f87b3adee92de736fa46c62fba5c246d27e41bee33aaec4db322eac1ed632c527b0571228929d8507bbbed63c024c380c0b109c51f3edca62f22d02fae1eec054d8ea02ae0d96e0d9b557113870247a651ffafb270322d52ab8246aef6c332f353dca3ac513bd4f0a2c725d15ec1d1273a907db645c68bd2d3ac54fb8574f58a88b0a352745296ad7f74e82843a8d5a1125ff34ed0f3431b897802a6a7f610e4a39f099cf5564a8f1aaf3d0aa299bf07d3fb9f058e524f575f4949cf80998adc7edbdb6180e3f896384b2e61b0edf881ac4d8871371b8face84f4d496e03cf0d80182a19e7ad24393d1dbbc14f758525c6df2a4940e33fa759be4c593f69c537feaccbc91bd90917921d910af0e09840f2402640cd1796ab6150cd9ac2061152b02c40349c0b9e45f12f4d005b94feaee80c5623d1616123836eff6c0dc4f0a88b17bf09149f20e2e42bc55846af837003db608da415523245b5aa4a0501f941d56471a7d7e8fe3e0abe264e87b18bfe49f64056d7de1bc37cd287889207107dcb54a81802d8a42c88437b81e20cdf6b56ab9c90fb272b04a84b30d5703c64401324cc19a184577f4377c4ac1f81144ebd285165ec621d19e805f5a33aabca220645f9e9b7b9825d343f9d2fb9db37d9c815f118aea6d8d3048bbde73ceadce552994073ca30c1effec1c8212231310faa7faa2d274c709c54dbbc53eeef6248a5863d23747b4d9b2da291943838a8ec7de2d5e21a56297b9302d56762d2a2873a49a8b22ad3742831bd2a90bc4c8d52a4634275003692de7ce04f5cfdd0d1e7b3a57a15837e5b842ab55bc3cabd0b26f7da0f790a231bde6e68c3d788c4e14873b4b8bc910a3c5b262bd0bcae2a96cece0c49cc1661d9321a9b5b232380309801a50db60e1ec1c637cb9a5a5d2a30f8e87e1ca7fd0cc128a9b2b8ed68bd435ac013cd11c315963ca92985cf384b18130eac6cbcb274c8285487455cf99c929d5e7cf129201f44d04da9bd3b65720dcd5a803d2e58089d1a5e1e268c0a1e00b970f0602c680c084af7be5cc68b818bea29325461881c6370a72451e0b327be5e7681dead969b879d68c71006b1878422d42a9043f9e6f646b96ec267b37d97bef2d939429b50bad0b110b935237a2088ee328a51f781f6534f061ada38c114568e0882d4240c2c0877352eaee45be721ce7ee55aca30c06b8518688213050e43b2c32a0450b880fe7a4d47d94b940101cc799388eab15880b04018415ef09f1df283369ae3055600a29a4a800153ff83085141698a9a2c4715cd78d3215a0e2bd51e687f7c61d13fb508129a4b0c04c153b3c4184c80e505080872788f4d0812854baae23f2de28b30314ff8d3214f86fdc31310f3b3c41a487f78c321d788fcaa78c46294d4c0008c7719ef789042847294d4c0048ca7b469995f7e8f0e128e3c490177d0efa7dea8bb5c6d1377cc88b46855c1455ca264ce845a39485b8e81bfe212f42aed78409c5904c985013d88409e1782f1624180786fd4349264cc849cd8409c9944c98508ecd920913d2719204d76025d806eb2ca9787a611ba3a82a74436f68558807fd32614231ff024c98509326384c98108e3f02264cc8897f8e091392c91a30614239390e983021084c98b19d40113911e188706cce789580fd2f13b07bad6c1c13bb290bd84334d318d8df2446ab2a8ca2bc55d5058df237ad20e0f3d9741385681346c4aa348a7241ab2a151a75695fdcf0336ef8d00d55d51845790542a33cf464ba78a8365d3c0465ba78c8365d3cb4335d3c749b2e1e8a325d5cc4ea625455caca2474e8c98411d13ca94d18514bac41993022232114db8411b93cb69d09233ac2eddc268ce8f5a2fea12813468464ba44c1fe22d68411c1a60b155a05c241a3fcbdcbb21db3621ba7d8c623dbf806db3122db981669325dfc4d338ac241ab4c4e68947f935246cb24d04c185192d68411d5189949686a5a4a8c607f916b8a8e4cd16bc2886ea60b92099b36ae25476e5e4c906018f6efa255622d0ba623d8bfa4e35f7a425573049304ef5f8a4255b304ef6fa2a1aaf97a7f93ab719c9e039f4bb4524d89cf25db129f4b37ccc46713cbd412a36144494a4ec0fea524fe252554355def5f62e25f6ae25f724255d38bf75caa29d904f95cba11f2b914c31bf85cc229c992f85c9a61ff120b1a4654f3b9e402ec4f82e24fdaf12fd150d534f2fe2523543543f0fea5231af84cb2916e457c2eb18cf0cf2557e985c4e7120cfb93c668189192e9e2e1675216b03fc9060d23b2212da1aa89c5fb939850d52cf2fe24275435b3787f520e55cdd6fb9374a86a6af1fea328fe241a7fd211aa9a1e787f1212aa9a57bc3f2909554d9a173f93708081cfa41b526c88cf241c223e936419f84c22d130a906c467128bd40ae233c97581cfa497109f493052cd67920df61f91d130a22550a86ae6fd474fa8aa8af71fe9501515ef3f72425553bcffa8095549f1fea31f348cf73e2203f6d16d64b3c0e751ed87cf239a0f9f472359053e8f704437d3c57fa445c388988c96505507de7fa484aaa278ff5112aa82e2fd4747a8ea89f71f19a12a227f82cf2320e06982cf2310601fc546373d7c1ed950e0f3a88687cf23d8e8b5c3e7910bfb8ba0505513ef2fdaa1aa21ef2f8a42554ebcbf08090d238a4d17ff1b1a87e85da4834534209f45b5097c16d9b08b6e79c41ab59a4c987642ab1abba809f6ef254effe3863b43cc822849c725c1fea21a4f8d922b5282fd4536a20df6172d1997607fd18d888928364f704537d30457c40414c542373cf0de3f14f30f35f10fe17431ae89c749281373c619cbfb8774264cb70b8d0f9d88b2318765869d46488786cddf18d89fc5669005a0189e2eb00b218756843bc86117b21803ec1fe2e264c59a97159194c08a474860452323b061942336dcf1c2864f4460439d2e6c98e3b261132e6cc8640b1b2a09810d9318b1211210d8d0c8076c48a385f5ecb4ac074a16d6f3a488f5e460613d4e68ac87c915d6b3c403d6a3c40aeb41c2b29e2333d6538505a35061c11d8af29fc2825258b00316744251fe5158b00945f94361c1272c48c4824928cadf090b0eb1a0118af26fc2823414e59f623f9bfd6a36c0b03f132fecbf84bf12620bfba74416f6e7c00dfb2761c3fea81af6df000dfb0b9961ff2035c4b03f1237d89f061bec7f440df6370286fd8bb0fe00085dd85f033664617f1b37ec9f01226ad87f081af69f6186fd3120c3fe211cec7f63d85f881bec7f018f0df60fc25383fdad0786fd81f0bcb0bfc9e3c2fe204f0bfb5b8085fd7fb037ecef832d6bd8bf0234ec5f7a19f6ef810231eccfc30df6afb1430d76127c617f1d5cd87f02b6290a086bc5eefc9851cac49a28f6471bfd0425ad8f71582dc5140ca50ef63e8b702a45559a0964634538502873a5d16a13aaaa4f3ccbd5288ab37957acb5a88a62e75af56d201b07dd6037cd3ea29969e65e87c18f03b96eb6ba0be5171fe5020777185cdcd8803b0c2e8c6091105a84d338c0f72c6e2756176e63d8a4501303bb090998162945b26f060e4371ee943b400c5ed3ab6ff89b66d5c5a5fdc3f9ac08c756fe5079f92ae6fd4d0c787f53cd570d787fd3cd5732ef7f7af94ac7fb9f6e7cb5e3fd4f335f39e0fd4f375ff178ff9497af1ef0fe2937beeaf1fe29335f41e0fd536ebecae1fd67bc7c1581f79f71e32b1fef3f63e6ab1fef3fe3e62b09b08c37a36a7c7ef9cf38949f1bc77eeed9fddcb5193eb7cdc6e7be01e0f36cd1f079ba6af83c5fa8cfb326f579dad8f079dedcf079e2b47c9eb21b9fe72c009f67cde5f3b4bd7c9e37da1ac067ea22c067fa2ac0675a6380cfd446f599deac3e531c1c3e53d9013ed3198ecfb406f399da10f099de12f0d95bee5a80d7e0cf6e83b3697683a704acffb0eec3d208589a83a510b0b4879db407d8c9c34e98a7e63bc0eeb042786abe0e2bd300cb00fb796a7e8c759c43011601b61dc6e2c0c1ae700c600b3007e07ab12ed3766376830dd65328eb291a006063069ba72cc8b6a74aeba91a767eec0ff1e6efe411e18870ee874d26c02ec2e91b5c28f34703ffa3e15cdf2b3dcff33ccffb7e52af21873f5f7cfef3d4ac60ccfc901629e7f718a55833cd44382bb7960ffdba9507a3a9f99e0d9e9fbf1decfff97200b261f91ea769664d3393cea9753a627a62da31cd4eac93918f66823de6388efb195cab949527b126fbbeb23c9dbccf626d8296dc30a1aaaec66423c2c9094331ec6c6051549db5ceb0cb91f94a45094df92f9931a135a961577162c39e039a8174a2809f4db352a44566e0a0cc221cafce9c3af2f06a9d5e37bdd7c1a09c19080a0541c15e2f48465134c61f6473f2986634e5eff99a83d2f3dd7cd0b3ddac5e2a4ae7a56e5d7df032012d714255a0076463aac120284fb08374b083726a0c24a3aa89414eb0839a809850d5acb0538db15c58856da1ac0f7e16e13436cd7c65b269a1ec5ee484a2bc1cec8966221d116de6b6967d2aad3ea93578f2a9b54aab4fa6070739a026d8bfa505624251a0d80dc49aa7e25d1d147b624d844283fdc7d79884ae42e2ce0d76469ab135ba4624e20dfb7bb356588dc1a82a7fb4eae20e205cebfbca926bb58c6bb58c6be18085fa34afac5afed06187ece1870e3dc379453822277436ef27d2c1fe4346031ddc61d0c0092600ee3068f0c2205aad730ce5473355286516e198eacf7c4351fef5826c280ab46466d2111d61f64a84d32222b4276b1c1e5c4549c0fe2209c35ccb57624dacf9caa423d6c49a1fb211088d51624351ad9a85328bb5ef2bcbd3e91f857a79e15a3860816be99835908d0c68c29eb436e7cc09896ca3ef89a886fd7b02a58e219ba9ea8765f53f5f953f415f7f445735f6116ba5cc63a5cc57261d9a721314ec9f4b1aaeb5b2544219fa9a5386f50a22af866716cd30946a85b2be13baea188f86a708c757209bcbc1446faa61f9211fa6e16f4a726a1cd3f3296f6241c378de69a238fa4ed5ff6a95e62bb775aa569aed66b94e6b94d75a00f573cc629dd637ea9fba6f789c80ab8b81ebc42cf68bcfd3f85ca759161bfeb41d3e855f7f7c1a96c887c30f678415023338bcb3be5167ad429962b33b0167114e0dc4c47483fd2bf8d59bde6c31827305bdfae0acb785356fa5a8ae53e18c70b0efeb93327b4ff07c8f8b950ae5f79f07d9709f45389f6d9fcf89d37ade136faa8c50074fd17b6eae2decf9708e3233ea14cacf7f9f454aa8ca94c45443551ee945382e9251557d277de65a35ec7f7e26a67a3a7cffb95380e1fd149fd7a1c67fffdd29beaf716762eacde0735f69e5ad5228bb0a06349f9f54ac9136f064a4460d1afb77652f1560e840be7d2a3e57c5fff3e5974f2f15e455f1ab2281f23fcf03bdf9e3c1dab737c5935203153aa1284e36431ccf5f7441136c7187e10218d601acb3798a4bf180dfced5b8f9f33c7af3e3eef04fac1656f9a3c885bec18d469f43d2763ad1fca9e54e7f0805c19b8780787cfae308be5310bc2b3477a54551dc0a8bd55da1cc24174634a31655a9f837ce33231655f5adf194d6185fac4172a171f4ad5028c1cf1e7df1479b2b4e196d0b8d27a54619e2f88ac6b7d3685f7d5e953572d752f263afcfecf734ac6763a2e29067f3c908e67edaef4bf639b1d2c85365e6dd3c9bb7f3b576bed6db1145cdd4ed438e1fd27f04535152b75419930a25f99fcfa71a7f625195ff0c13159f46d286a0bbf81e2b8e4fed388ae328de518ba258239a57d542391ad18c5e2e2594ddd79c920b55e31c7bd4cfe11d2fad1cdda98fb6854551748a23088a20288e9e07c5d1333e0d69cc13abc68735c21a618db0c6e7c7a1e7a37efe288a7b9b67f08f589e8daafc47d3b365cfc199b4f9c4a2317e3eb142908ee253f1a9e7474be9e8e2f81ea7e3f8e2ad308ae2fee97f7d5114f7a3ad4828ea9ffbba85924342553654d52f6acb158ae2be76a1cc27ec6df314d72316fdd0e62bcf16da6ca10d73a0f7c438cdc3af61f30caef17394a9a12201fbe4fb60c9eb437b0a14dd669f552950fae183352ce9434b6de3d2ce18cf56c3dcdf546c372c8a2a3a14a542a328eeb959df001d0aa5533a6b7c8fd4adf8a39dc1b486f8393cb4a21755682adcabe85015131f5e79ee55668d83c69f5e458c86a1f1dec8447e68acccf06c9c67ab3885c6ca0c1596cfe743ca426365060d4abe0af929e487c5a63cb51d5e4979fa335e25e5c3d02833e30ea04c999142bb14fafdc96615276456a5a8d293e168cc674f145569651e9d5854756251159d22f879f233495a958f0a0d73ef2c8ae2beb39599b4b130f7a48daa3ccf3db943de9ee4c6f1562994e3e7112bfb6cd207c3ef71871882cefdec1bf4b3a7a728eec471dc8aabacc267c01ac5091e8669d3d45665f50dbfd94051147def8c406b30fd1518a6bf32f3557d95bf5dd77d2b2fbad219a1ccdf0b16fee8f31d7eb1588daf4ee2778aad08e1e8cacba472aaf1152dc5530da6a7d82886eb4e34a71645951d19e5f7271a3aee381919d15014c5e2cf710787c5b123a32c31e8434fc71d158b2390ee14e8bb53a1f771876371dcd18d027d372af475dc41b138cafca0c1028b538be529daed38b1aee8d637e8cf390299312b1ffa0c3b45fd950f0950aebcc0d0a1beca9d42e5ebafd82966fcca97af98e8765ab1eda994951f2bdfa304664cfd1cd474f3d5ca53b1fb3a4f47ea5e9ea2624c64f3557dfaa29baf549e9e6e2e3169ba4e9f2cddec35f1c53de9e6cf23be5e626ce57277fc149b4f2d4c3fe43490cd27d6c995dd76bfd1576a6c7416c3f4bf97afca17a6af622bcbd6eaf5ec375fd118fa3dae5055c959256a0b971f12a05c41425760c3fe5d90f1e7c78e4a3d5660f47b6ebc392587d273e3e9c79b4ddfdd95d773b7b682743f821ff218bf7b3afaf83c7755c8e71eecac0af9b9fbd0aa909d25df2b7de393fcc71ee28ffff97c37fae0ae4af83f5a95f04995f0eae89e7cf2bbff3cd97139fc10f9dce880893d2f5e21a00f3d6e7c80e73b4b7e8f1198d8d33d79bb3094dd83ef79f206f1fce70621fdf89de756d27bfe2329a5f4dd8f27d3f7dd57fa3ef2c70fb9b147067fcc000e9f1b73f01c32f8e48f971b73a098fb6ea682b474e4e13dc8f1c1f0e66eec56602b30ee2b6757602b2f2625f7796505c98fcf8f48fffc38faf060f1473bfaefeb46991189f4fc68c1f1bb5107f921387e9e9024c99b85783f8ea3fff018ddcfe770d2175acf01c4e28fecf83d8e20f8642786d2f3a1276d90b187f318fff3244992211547db7824f3e79b98c1e48be3f8a0ed96508e9f4950142df9e1371e795ef4a1ed51c713369cbdf75c21b5853f9f471feb2fb2b9891af63ee4fdca6b05097d2f9dc8fcc41ebb0fc307cb0f39eb192ff89c25491de37f7eb63017fe98673028fe98cb17a6df63975fbc413a1f65beef6c27f3dd1e13bbcb24c155bf0f233d19a17fe82e777577379dc9cc39298c524add9dbbdd6aed461997e99837ca90350ce5e3e0f48d7e2884dc16aa11bc5ae777dd247b728d638240a795b7e1a067e3544f9ea29eafac0d4af1ee653a0de90b9ed531393aad8874fe290cdd72b862c761edb8ef3e048d32ddc44e7698f6f7e96e7e556861d1ca289515a5b2be2e0f01db5b582d45ba96d6c7bac20cad0c3def71de79baae03bbd0d3751df87d077e1d48eb0a66778d3ec9f9814cd614e52a342494140965778f324b843db095010fa93f311d7b9ce88794523c6372d5dd41b0fba07c4f444ee4c4285dc59d8f669a7d34118ef82117be58ebf875941945ae082d62abdcd7ff5aaca34cdd2973d5c1f2e0871c128af2bc2337283fcf05c1a762cdbb5f2827f72c3687cf09a92d2cbe694615f075d7343f2ffc289f1bbdfa39ee3a65e0a4ba64c0a414c96a9c8d0cac80b3c8f661cda00b9c4551c0593f9a6946fb6854d579df07065317ad42dd20c4c8111c1081114f2bd54ab6d22df5e2473c20441327353644505183050ae050f4407381fc340cd1840e009820c061baf8cc0f5c5c11c4936e62249c19ad8acfa41a02dc07272610050fcf6a616088059263ba7812403c211bcd76a8d9906880164e0a994a3f72fd08b632b2b9f93c8ad1388920305dfc3da107765c1547163094a30e8ab9568da238282a3c90fffd77a950a94ffef7e4774cb53e46391a116b229428d84716f69146140376110cd8451b767107bb6c86703f795b6663c240a06d4c2f4c5d34ea88ce939d521423acc9d537fc8318f1368ce067165e08f9b9859123c4704212254c9a3811e251ae9f5a04792244f05464f13564e02db0050b5c006587c6c891d6059e060f14f14d84e075d082082ac6c8c212264e72747e781b43fc2c02c56ba0d5c3574163031c8ca2d01c41926402d38a209af82157e8303322634486264e749e4089620a1f3a5005059c30722489922593c80e4f0ce161f4032d80000251941d1c271425eb1b9e68860a90ff58ce763948480207725c677392c1565840c019256641059c0b80b358133b30c4450ba602cea5cecc869a00837080a4f404cf11dc1294e9c5cd259b909b4bb914651ab9d9944d3447dc6cca2623aed36729344c292b5101fb772e29c1d3757329979604b9b9743345704b4d6616b7e4240337977229676e71736966c4cda518344c08496906d8bf3369074f0fdc4cba61274529e2e6126b86e0968c4c2d6e2ee5d211226e2ebdb097905071730926c42575a1614230d217b07f6712130bdc4ccaa42643dc4cc281e26652ce6c5d924e0f97f464d2dc0bdc4cb2617fd20e1a269464ba380fac689850cd15d8bf3389665a7133c9481037935c4d5c129279c52525d1e166520d769292999b49363fdc4cbac13e7232c5cd2319f6518e0f378f661db8a32755dc11140adc3cb2611fed3871f3e836819b49426898909251170d13b2191161e44514378f704677c484871bc5e7d19221178acf23254fdc3caad9e18e9010b9443e8f8e607f91180d1392f9ea8617e12cc249b959a4b372b38806e4aadc263e8b7666dc2cba0df95c009c4538a29b472c273e8f68bc9b47ad79f3c885a226c02624426ce94909ca11b6c4016ba23105a9e199c51626234c2f5c9202154a4a4a42d852939293521136252d8828e1607f2a4aa519ad1483d20ca0b0a41e2c294ae902b674c49690dc4a2cec3f44132517f6d7a1344150aa2175e10b3396f483259172483a4158d293188924c3feb3086986fd354022d5b07f15d646da010f48344e58929109581212120f96d422b9b0fffc00e985fd31408261ff212492cd0dc90ad215b19193510e1076f4640465b4435123d96886fd276b44c3fe3b8c6ad85f8ad1e846c48e8e8c908c94d8d1123b624251a3d7685483fd574636d83f65348ae18c84d02efea32edac59f08232f46345644c38a764439322f5846ae71c4c2fea2e886fdabc826aa617fae03672231dac5454e70c281111c39c201122ca1c4088e30f12a41129f5d5c7091bb1012c4c5c506bc1001122e2eb8c85d0809925d5c6cc00b1120a1810f67ddddddddddddddddddddddddddddddddddddddddddddddddfd7d20e8f17842310c4531013608dbc64e39719e6977f7180ead66bbb15a2e5a637313c391cd6c3756cbf5a2b0971a8af6b3930c67ced80deb6b95aed3eb61a89a971bcaf6b513ad9ce1f0827af76fc678ab5b36a3d56caed70b06aba9b1b1b99975db58edeaee8e691c7c45fb68adbc793138e080030e3828e1ab9acfa6bc39d92e46095fd96e94d5f21a6ed6d4d4d4d4d4f8cd172b714eb2ef268dbe7c3569b4499b34d6d72ae78bbe5e99be64beb2bdd450b49f9d3e2a6bf9eae50585fa3f9dca92cacaef54fe09f52f287fee7e51afc7260e3961db58957ea69ec27c95c3cf20fef3d5e8e7e7124f2456def37d172a69bc4a8a893483adc172430d002040035e0031ef7203e600dfbe7ac0afbe7de580370003f00212f0edab1f9ec7b7af2af03b64be7db5c303f9f61506fec747e0db57403c04be7d6581f7a1071ebe7d45c4ebf0edab215e889f3be58ba941d4b80a3f7f90ef0f83d8cc55a733c68bf97c956a01748ad2cf264c3f041bfc7c513f880571b65f18f18f7ac101899b8f30c27e9ed240068818c20a712f60db53415c202ce0c3adc0a5c0dde1667ac313c8fd717dd8f654042e042e8fb963cadc06d8f614036e8c6d4fe19b696b013753177dc1dc595b5dd52dc0cd3326809b27ce94ddb837dc3c61a959c305c0cdf366869b27cbdeb7d3e52996a97267d8932ddd40b2f15a149edfcd2f38e019533bf6f9a43f8c5e6829922a6376b7f7f7e8c3bb1bf064b8bb4182389de9e6dc226c1bee343afef059fdbe9e312da781ef19dd4685d7e7c32ad2cf771d37ca749f595b96eb4ed5977bad05e03fda274e6b94e3ea2fde6e5477dfe01cb4fd85e7f78bef7ac889a3ad1fda0e835d18caeab14b54c1c2f5c350ac9c57f73ba73f0a01fd14f51d535f54c51de145bf7b9f5c9d5e9d3315c5fdf1cacacc867b06cec719e40f856a09a30651563386be2000f7a9002d53f4ef45ccd9c0fade3dafebca78e19c9211860428bfbe4998dc788d2b498283a793260eabcdd9c273d21d3ce7113c678dde541cee8632594275f09cd4e6e62cce455994264a123c67f79c945277afb5c66ae59ae0e87433cf6b32a1e03939239d91241eec88913995e0d9c2b37b4e4aa9bbd75abb2ec7cbf96a60ceec9e9352eaeeb556cf5b1965e68373dad831714c669263a51fdc2f36f5e9dc87de99001d846d238b688d6b1340ffbeeffb6c14d501283d1c7c5f91d45775813507251315675c391989e3837206c7aaf000bf53f23318b4dfe7d928aa7e2e8aaa9fd716764b71ad60286ddf5796a7d33f0af5f2e2361287744255df4da2b6489983efa30c48bfa04fe90c0eddaaf0f0f7debbd9dd6d14555d14555f64e3293094d9ab929a04d78a04d79d6a04d74a43a3e0fa6eabb5abdf775de6819e67f31539f3929c7d2d5c3f5287aa9aace1fa9f58ca3c551f7c2594600b2b4a94cf6b618d350a652667df27361543dbe743db27b4e5289f7b7908e8f9cfba7fdef71ecfdd7bb7dff779ceb785d25ffcc670e694f343b79ff7d9d4cd3fcf73cfbd0f3defc1ef3ccf3feffb402a94a10dd71975a7ac367246ce3239a336b481e579f781a3587150924fe68cd42169b5a5d532eeb7dea09c0fbedbeffbeedd7eef59f0f3ce1ffc3ea73b74d531e48cd4b1248da2ea572d94ee0aa05fc959a597f442e933116da5060128615fcf9622e473a38f1077640972a112ec3e5dd8759fb0fbb8f243e58365e339ca900d7e2ef8a1af7bb27c92f4bec4654b911616f92d4548b2bac85b8fd4d715caea1f7263e0f96317f07cf1c359b1470a787e38c618ad1283ba6d4e5cab358e4ef41daf950f02bd93ef1f7e481059426006939f197ec2e7843d9e9b5d0c9c4b4c5b8a746d61b5d01469695da16c69c1421878ae0581baf153419f4f98be675ad0cb5419a2f8e5180afd0702813e047d17ba79625088413dee008126c8bacd7728cafd6685b2fa93f663eb13caf1f389a2706ca041e1d8c04a95315dba5baa0c1b58a95baa8c4fb4426903abb242ef7410d20f4916ebe6c66b6a6c369fce798dc1512ccb8fc78296564b913a85d29fbec784aafaabb70457ef06576f7a57acefb9d96960fb8abef75e0dc3f7783eb4550aa5e7f39c1ff7a0c7f58c794d18fac4b0fb1353ad1e08fef7e1e8c37b0ebc4328aef50ea1f8f36ea6b8f415ac5fb220c7954acf3df8a5cf41c71a7e2aca84f13ee45032dc71dfe7bd67dce1792fd57d2a8a0dac0a7ef79ef53c67c1272bb68106e7541913467c8f96483e463753cc7d36b0b0572a5daef4b9f43d9a3e072d79e28b37cca6db837bcea6ca982ede55111ffc8e246bad9f8a3261b8f76eb68165030b77eff1984a13d78e24b9074d5d773a716f02fff3b837bde7b161f23fb62bc8bd09741b1c7296c3a2f87d5fc562e9b3a928d3c57bcf83b67e7e64c71759f1bdf2a7e5c4fa9ec779e2f59c8e957bd2cafc3ff49fafe8876ec5e4e721b5bb333a3194427c86c9db7d57bbfbdc8b1e1b0c7bf7dc0fa9f87bf26f17865288109f850f12c99b5138f5837c57ac32cfc8d5eec5300c9f1b65428ff510b4b3c3a09dd8ab5c579fe33a87eebfebd92c84ebb84bfbc4aaa033a387a610aaf0f09fd55f341aada13573ce09e4a3d128a59402f1dcdd1d4867abb502e1388e0352bb0e88378e0784764c2881cc18cef37dc1ddcf53f3fd7ef5fd298f7afd9601f7b8e34312f69c30b7a9dd5d83f2e449ed5683f2844669adbfdeba9f7bd2ddee4a646cba9f8bf577fd5d77f7dfa2ececdc6e51766cb5f6735dbfd7efed74b72dd684099358ce4c166bc284f644e786e39874314fd6fd1f93eebea9b5eb5adff7ea07fbc1eeee9e94bad7ea79b26e998ca7bbbbfb44a97baddfd78d3ac984dddd08a8a161fa61c8f33ceebf6fa1f95a8c78461ff5a73f4769feaaf538cff3ae87600e5dd79e6fa16969dda837bf3beef8fc375b582d455a5a54e5fde77dacaf6b61b5d04c98aee5bdcf415b8a4c98fa3404f9286fa62d2dec2055f9e1ed7e76efffdebd2af5b9ff7accc1fb79330fefa7f7f37bff169aeb39686e69e10f1c77847ede205fe8f3bce7a1efbeefe3429cdfe05a6778e777b6850664c72fadf8a4adfff9b4b0c8f08629fdf97def7d3fb91ede735785cef73c04f4781fa44eefbf9eefdefcd0e3f9962213a6b1a7c39e372787bd9f2d4526ccb3bcf73cf0fbbcf99e07cfbb03dacab2eee9c8d0af42a1fc9e35dfbb3904c4dccde1610b8baa8ab4b430ad8a39e339a80a21fd9052b29bcedb747e772a12f0be7b201d43838577bb8b05e7a9193cc71d93be97b57e0ee8db9151569c5d0c176307a9c283be7b5df7d941afcfaecffa467f7f0eea2f8871ef2f588dcd4d0ca7e59ac16a2dd70b5663f382fa53f97da7f24fa87f41e1d0f216adc6865372b68be1626c3506a78cc129399c922bb917adc676335938e587cb22aefea982678349e7fc15ac600546bcbaab20ca576f5584c241019f4842f03eb8c350010f3e1177182aa801e8c11d860a5ee0093d1e10c9b461a8a0083e91ba3086a2f0919125498691828a49b8c348410cca1109480c230521c0a7695340239a818511480c524aac74aa318d28388948bc4811e10e03090b9f1a9cf111c7b042c330a4392845d1399150247e83e9bbdbd4ea4930fdea48382698fa8b46a9d76a1467dd6a4e83290dd377772532b40917eb6ca8122f07d3f79c604a6b6c75e766e4bbd62dca0e7d5124b65813264c62393359ac0913da139d1b8e8b7939b219a3ff31a1b4eb683c9ad6f7bd666bd2783c3309a693d53d27a5d4dd6bad3219279f1319f804a4c93c4ec2ee3929a5ee5e6bfd3e907a50989e64c61d1307194cc130fcff1dffe1531b3e1d75844eaa3ca12a4e4547854655eee44f2c5f59f264ed89e5294eac5183734a92fe537c9ed4c1926f6fc7d09bc7174110fc7c2a51379e14f153b75419356ce36e94116bd41f01abee543e91517aa7fa6586923dce897bc1fef279d7b8ae6147f9eac506c7d9a028cf7a40103cc2fb107c8f5dc2f33cef2e01de5cbbafa38fefabf75fc7bd0d8ae2ba23a32471bdfd83284b708f3c483c5d4cc2b651863297d84b7f0a8421014aaf370441320c5b9cfdf9397ffc6fb193193f74b1ea701cd6faf5dd0699eff3aaf478fafe65e4e014dc3f98d9e0d07dfcd8aee3e76355788cf9f3e3cfe0f063f38ec64ad0f74f823e6d4fc7c4e3eb98e3d531f1e77347dc8956c671776719306773155680d3e6991eb30c98ebc4d07e9ed73d787bba7cdeede952ce8cb3f341d87d48eea8efbdf793fb9e651865be6eb6fb274f7c1dfcbe2a442aee5f82c47ddff333d807e5be2f91893b4f5f951e354877874cccdd1ac467c0494b10d6f0343414f9626a7cdbc8600d35ec8c0929c9e6ffa1b1309ca5cff3467f49a4eebd27ae230f920d92455154f72512a9c4228ac052c771f9be534afa928d8f5883ed981ab66368b01d43fafc82bb27bdf75d131ba4f4349ee5fdbb1f348e15ae867e9df1b57e57bfab1d8d6721b17c37e36bfd41921cdc48c0fd83241ee0c492e57e857b1a9f83b2882b760912db2f59168e23558ee3dec6a5aed107fdfae18d1a345c5c5092d87b961a33c6c71d15352305754ba4008835589a1943c32df231347c0d5be41443c305a37e6843a4b092c23e257dc3bf7de520507f1dc8c2fe2c9f3b508b568905d2802dd2d863ac79e2d17f3b2aefffdd6c54451a7bf48c1c667c7d128fee6be9bbfe649e5094f9aa63be43f1ff45b237dbc0ddbb0bcafadeadbf0489eba7a47cb5a44b5d97c8c4dcb78c3a0230030bcb8a7d50d9750e5fb1d45b50e62e86bb265415fb41e3c8f4ed07a197c6e7ae8b81687ca5f1245b9fc536a6f1214a64b14fb2a51cead3f8ff94a72faae26ebecf8d2c57a5c70c7ffffe0c3f471db5e6da5861795b02c08fa30f1acf72894cbc7273fd7f318703c0c655e9717f869fe1de20ff4326a6f13968a52a7b33a92aa12a1a370809fc461ea41a160c1da0fffba52752496f5f8548c54b4c1b8df78c3ee8932e91894b171c7db07cdf5c7fe573b8b5d445517f557a8082acfc9089591e7483703f64be88356cc754aa72fc958c324655346eee5779961ba43b1caa5ab94f515d8f193cfa9e28aafb7abb0f5dc43beee07ec8c4f455ec0c5b52548ae94fb78e3c4c5f5fa42ad3155d700500ae2ca42e368e3aed82126365dd51766217f3d5b47de9e613e9e6b27cf07378632e6441367f60ccf74f56defc91275c759460e75ee6ab0f8c4ef907e1d4834f86fdcbc17c82c1aeea74318a1a42e95ffd3fcf1f664255dfd79befc67c08c0fd75a57f8404e038ae56b0ab62ceac703744a141abc31d060d587896180c1b184fc0c8c11977186014019fc0889960091df5b9d73175d4eb65a9a356991d7447f783068b7e2c66df18712b2b366c9c4e75dce112e8beaf2cebb883ca646fecc828e953aa83eaa02139b137eaf840efc4ee6ecfb747ab615a5bb5956beb73792c8da945ca6cc3e5b7b65aefd1bc5afd72ace7c3aeebba9b6738ee3f9b5132ec75dca92c5d98ba2b07a55f5ae7d5ae725eed3a6e7ea247bb3084fe6d23936878ee94d97b956ee8e2628b101801c107b4686551040b9a2b3c60056ba60a2aa690a20351dcbc26bc219e131e11ef098f892594e040121b10122423718411384af93373d17087c185007087e1aa01fe88d19f59e3287dc845ab455a11b2c8b875c13606ae49410788b9f8fc6df1b90cc1e79311107c46e1f981cf2f78865a7cfe5a9fcb2c3e9f8a7c7e2c68c42b3e7f3897d873c29ec71e14f6bc604fcc53f4a928a5785c51507c7ec14f7c7ec1443ea3b0e78437c46bc263c2bb2df1f953e273c981cfa7243eff063ea3847c7ec141320a89cf9f4f461451fbc0fe2b1b012e0f7ef8122717afb9c56b86e0358dbcc49f20b819353f30b5b8a16cb66e289b59dc50368bdc5036b19834e4bce28aade9812bb6a615576c4dd6155b33555071c5d814578c4971c55807ae188be28a31289eb8f985c8cd28276efe21379f9ab8b96462893bda2871471b0edcd126893bda6ce08e36423eb0f183dcfc72330a899bff889b4f46dc5c16317e60e307367e60e338ceb14787f30c2ee2c397af423098eb05e3e205dbe2050bc10b66e4050381cc57a1ef03377f65793afda38a84323cb1085bb1ef2bcbd3e97fb2c4d68cd8c225153797a7296e3ebd14373f0a15c5cd2fe28dfe68e3abd1cdcb0bea899b514fe4e63f3971f3a91c7273d98478fb98b8f92b97b8b93c2971f3e93970f3a392b819b5819b5f701e6d30fdb1467a1172f30b2ac8cda8cf37ff09899b4fe5113797468cb5af3cfd07161313827d3eb00fec03fbc03eb0cfe717fa5e4c68f67d65793afd8fde452e9274912ed245ba4817f95584f37d65598a70ca2f9f1bb1e8835820168805628158a0cf2fdde886f4a4273de949ef8d6cf44935bef2bcbca050ffa753597afe72797a77c19954d371d58bb85f70fffacc3e345fb93cfd4fcd572f4fff73f355059e3ef9f2950f4f9fbc2167e4cd57403cfdf255de94b3f2067af96a88a70fbaf115114f1f34f355069e3ee8e62b0d3c3da170689c96f50cd536500d794e1aca09803c657346dac8d4455f0ff84c6b32cd9fd90c54f6b999da807ca6372b7e669fdd26fb8d06ac67c03a11960e6129062c15c2d20bd8590bc24e20ecacf154bf05ec6c79aaff077b84a7fa7db00ef0547f052cca53fd3d58c7f1543f0578681a407ed8f6940f1b81fe1c7a8547efb0ed291d2f63db5375468e05d8f6949700db9e3ac1d8f6140e7b805e5995017a00b65f6c7bcac572d643137d006efe6edc5c8abee5e693e86ff0d06cb81925fad4cd2e387b68289bc918492b5925aca48160a01888166261fa352c0dcb6257ac8a9d6153ecc99a6cc992ec674661a2cfac6f387f66d306e7cf2c86e97f6ca633d0d5389dcda0c9b2654a03bdd76786a3c2a30c78debcc42c0376fa144fbff3279eb73fecfee88c329f9dcf8d7e6814f5c475288a92ac5186a4a128fa8942516e5dc635d7d5ae733a3bcfe733eb1bf49bc878454a1e9485737d951edecf9f3708763bbfff28aaddaaf4b841bc39e79c73ceee7ec1d90fb7af42b35968460bcd6aa1992d34bb85661e2b34f35aae0ee6ea6a5c9d8debc61573e1b86a9fade46e27d6b730ed5c229ceec5e26c5837ac180b872563cd58b49bdb0dc7bae15a379ceb867bdd70b09b1a9ba7621f4e293bcd9e86aae151b561ee3373358efa2f37289b7fbd3e33d4cba34e5f7e66deabb6bc182e3413b946acd1cdc8e6bd3eb34abaa18ad54caa8a39e35c114c4079aa3eca5178deea2a7e758481020ed76f5982ebf79001ba2ad1ea1780aa3a8c254870fd5a5f6aebe01ae3b4a4aa5c4f2f85155c6de05abd8bf95f111fd4b9cf41fd43ef3329f3ccb0db915094085697886ea80d07931125712414e5600c014a514c46549950948b48197652e699d9ca0fcd57e4cb53fedf5796a7d33f0af5a17d688dc3c320265f950b4eca7cc5bd77cc6765b3fc617a979865f84a97358ea63028f34824f3ce08b4bb69777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777707a916a8442c17564d102a44230000000001e315002030180c078462a1300e0359b21d14000f76a24a724ea10c8431a694318a18220220000000000000081200eaa783925cc8b116f54a700b934c688af5a58bc9c0854604435c07f68e83e428c0c16090d859095a1b4aa5eda5f0d935ce861c41baabe47d5d9aefc6d139f4020b82b710044fdc28e99c361fde3ad45d154e19ee463940a847658eedd754064271d47fbde75857528fd37d04ca6e6c81658a1f8a36d6e8574954e3ebe759ab4765fa5efa21ed6732cc5c614788e9c70578851d4ff61174cdb2817e3cc9c5fd484395403d8d45cfc4d5ad32df846a2ba10b936909915dd34a519647f7f75f29504114c3e2c90c63034e3eac9ef59e2174936d0f2dc650f71715cef910082803aa56fb1f6a976674b381f39604dff6788b8b2a56460c31fdcc6339baf7b5b8f642a96ebcc1430736bd56eb320468d18086f390d0bfd0f5708661b0223c9ef45af9eb137d38518bb0b9c1fcbac76b1845738327196ed2b917258b36dd570fe30cd007e22904a3b765923bc2b4219bfaa4dbc02aa930baf0b6134c682850026f31307b04d549dc4e1fa467710d73fc5bbe0294deb7cbaa13cb348d6d262ff165c5570f374407130b58c65698fb192fc56f9b59e0a0a6cd73284a2a397b5a8af913010d8f107598cee9ccdff03560b73578b02a44720f626599ca248840e69c6820f88d8ca0b3f3e8a53a3d3d86adc12d790f29ce0e2a453168920fee556ae45fa912415f3affd85641be258c7ec14751c023505acc994b1ab88d6208710c4bb732ef0b1f8f8ade30cf9ba9c42fa0983886637c93338d0373a5a5311570e55d2deb4df4701c92d6df732ebb06b848d09fcebdaa89d9477fd1ae5745b95ddb50707e8374d3168765d5644ad39ea26300b91a3d3d1681320df03c65980e2a415a49b35bc1e484eacc8c2f9085fc522c09c79754cecdac5e8d1889fc58f4ba5d14e568cfc6b5a2ae1875fd0da966d4c3f2a5e2aef8404b378abe8927a56db9c2daac7b9f477b433767e975d248393fc9ba7fa4c5fb9cac968a3b1bde4da134eb29beeb2a947a9e92270904ac66671977ada18708e064433c44458cabde84c2449c2f72614b362f1955481006671213e7745690782dea77844608ee0b223c956c4eeee0d57d04956679895da6748e20815cf10319ca38e1998fa4749f4ec6486f1b18ab95b0a1cc217e6223c50842c8fce95b006edb84cb4a801055357613edaa642ed14be4ccfdb7d6098b6a94c99eaf003e13a5186ddab8c387bdd28b1d4c7cfc1bab430383be007a64a1bf08abcdb73874dcfe7c435964a8c3c468187167868365d50a2f03dfefc5511b6fb093573b77cf87bbbd9cfd847312dde97cb1e5492fed7b60a52d44889c8af0408301c3a2dab28011a65c2e87b2217996a4e160d622553df19129c7c279b7a6a2c990c3d8f3b3a635b95c74588b7b3ea11820dc12c593e2019e0bdab5a6484c26938567727c2f1fe0843ae0423595e9b697df10a6ce46ca0cbdc05f86c28246e61999f7cd41cf8a218a0c75b060a711d7dfc247baef8802ba1cf9fae4c225493abc9892a3dca33243029f254ea05506ca068917397acbeefffd2bfa4f87032cee319613d0be3470aa183431509a1ad004ee78d223c80b4441dbfe3e05fe68a6820aebe3805118e269e6c71da25896f9c2778b642a570a765b005ae3709d52cfd5a7bb140a2ecb15a381a8143c45b443cc355d3f022935f984ce452cb0bc098744a91a47f62747cdf88c9dd67521d50dc539ab04457dca0d7dea6750e1077270b51137d37fe50c5e687179fc86c138ccd49bf90583f7a6598869534a49bd1856782d7d93dd6713f2f3e9f240644db4c76e684b34859d7b1fa418235c4e2d3fa068de93d6b5401fd84e1bef6e271a3a719973eaaf8cd91bddf109256b308fc015319c581d874f35ab47c3c44dcf04f849746545b968583aa4526eb233f2266ba29d075775fd2084151f79fc91b9657c17839c784384e9013fe3afd384bd098e364fa75c5c43d4e7eda6f8fd425eed8e2762ea5624383817d5972996cbea05bf7cc00fbf219eebde7e9c89d5f3836d0d28d33e1b6b4a0f8867cbef1a21cef483d3272fc08125408fc173c86a5e3179b57b56c918bcf8443aecd345bdae0a2c4b1d6ab24de388ca1e314a8fb6f876771dcfaa98544710641e817483638c77e2ca34587441ccfe064461d6481b8036dcff5eed8829229114d329d8f40b50bd03b26e4da738f7eb2afce72b00007cb0297917c509e945db4c52451e38878579b69a1ad0187ef76aacc8c548ead1212c7098046198fb6961592755ca4faa018dd3a1a2c1704d2e6aaafde000f45cc9c5413907e4bd29ed778484cece3d41b8ed845d341f24e6939f41acf48cb6f36bab296262e5dc298cc03214446f53366a80d8a5e0969a05da876e2041a70274082e894fdd09039c90ea6311ed135d399336a04948261402421b30df2721096864830d52986d5b9e2183b8267b840d60e5a30ae0b5400431966009083810cf0d5763005a95ad80325bbb5ea34e567154def40d1956590767ef41b1beb0d9a894dd23e278e70debe7a8a02baebab91a2890e12349d8c35795343cc57de41ef31079a4cc94c0e845c160cf3d016f748a03ed204413d6074bd4018297d51ad56d7a061e462de21ed884d8ef492d64ee3fdfa6733581876a077118c560de57dd0dfb105e92f4c12abe8addf45bee96ee8baf2428bd78832d608f3f2838390c0849569f6b74ee96e40d2452edd4c423e23f961ba6e9a29c98c257a2f0e1ee588b077b47510aa1a362d1a14bd4142c04887032c12a08859a15ffaa6cee5dd35d9a3325d00a1b25b0a2d180c3ffc89a0b286a5556efcff643344be66fb4d3a9b240f67940ea5b522b7c1370c15fd195ca2df85a61831211808f0b7025689458753583956273f4f3fd8c28f60068ee1ab81c0003877b4ec977feb44bfc5f7c451dfb060f1ffab34ef70fda776aa74f06acd45bf9b9bb276635ead6f25f92e1f79aba445ff75e41cc8cba003b8077f64905aa1561d7428fdf03c1a1ee0a62561da016061a8787d5d653e7b01cf04a1fd1fb549c3c84d84d4ff8a715618e200b28c13f727f73edea6dbab461061cb3001a56128797c620a71b4c36b90bb5e8d2bb181bdd13afdf1ca5e66f87d817008e98107c57ee587e84c359f7abb171636076b8c25631c8d907fa6d25a192a85c10825fadcf36f05bea5dd9c4d0cf2439f0a426528117a24ab57007301f4e49281f101bf97cc3349c3968edcbaa01f7c96c5be052b9f48ff59f7f68516b981cb135e29b52ca803eb16f6e30b4e7feba9e4ce8c0d4b19586a8b81ffdbaaf8f175f31a5a2dfeb53030dfb70cc479da02ca634e35330066911374c7427b02c9799de12dae203d61a4a20fae26efb69f577c66a7423e1a0f13c9bdcb4c7633b302d20eceedcbae1b0522b3edb7ac0617c2a13a37cdaa9eac446cd1a3712caa7239544f1eee9d23cf42996027322035970ec22b32821575117c2e8449030090ab1aa61a954c0b42cbe500b1816b4246e20a32205afbd40b8cdecd10de1606012438be4fe2f6d2545932466b2cb8b93fb406c5144584224604dc0a4f9dab9257985bc8b9f25ad1eacfd737e3b604f379528de2a7c7e7c3ffd48cef6b78fdcd1eee62379b32406220896d46044b6821d1f0b434a3af31f669fa32efd614ee8984872dc6a058cc3a53195613847a04cbefe899d6540b116a8cbe5979e82e509d6a9db56a09b2aa60f4d330e676365dab51cbc1e82e04f50105cbc627f60e5fdee81a897fc0e5cbe420e54b11f2fbc42c31f169b125c56ccf59cbe2efd8323586a8fea4bd93cf3daa45c6a3d24545fc4f1bd64ff3cee92f1913897a48ce3b7a46b206a49dda1ce92e8bdb4fa1243404b53899f9df77ca80c162bac62788415c0504871eb029b1d642dffa4f892b9be4eb061642a832dab10a672adae7c24952985c5daed43ad397742d82bc6b13871f1a8da35e358c7d8a1b071c8cf30708712f3e2e7adba40acdfd09f30959b78bc1ca737b9066ddd9888126ffe0304925a805c9a7ce04b703397c237b6355c0047cb4552dc888fe0e31bd57dacaf8f21b2443e85799fea4045d809dc921805ae3ed04a6f5663a7a86d6ecdc6507eb01707b6d3f6ab61c1638a9db5568521adaa73d67735878cfcbe2c9ca3d140f1ad81dc4a913b37f82a93e361ac5afa96542c3e88337fd7cf49a5ea1acb7d4d8babfef792cb880afd3aa4477eeb1a2bd6ab5341b4a6ce598f7026658c00a8efe9dc4e8275e9ea784698210ef173893e5d5c6f8fea7931b4e8584b9ef41575458067076120dcaf636d15c648b21a604e66f4c67ee022fc69c0c82c3527500c47793856d7703474df967076f8ed24ba9154d45dfd414f1752fd400b4478106d04e75cb468bee32a2ac9ed1b408c2cdec5487cef4b7ac814c13e1985e5a44194114dad2fb003b9e6c8da39f5186f2d730d4157fa1f92d41785f226a23441ae43b5a3833269b6e33255981c45ccedd3af73c4ffa87e50903827da4f7d8967eafd789bb82be1efdf13c3be78ff75121cc1cab68ef712f91ebed8dcc58406ba4cdf88a74b6137194ce9391194715e5050669aa6c68c58d152fbb911e897c18b423da60ee8dc40e9c4676cb4a0d687dc5b4621f98714e320df5572a94869e2f188854a931969ac34d3946ac4a2a71a6108285563439504ab6ac4aa63d528c36bd578b898935523cd1c920259d3ab710660c9007dcd1a4b0bec19ceb935463d264a396567d7b8deaff1e08ffe89991c85fdf4bcf76f4a710dcad9c75d88cdcbad87534228f6a1d3dce3948e452c0cbb89ed9e620e1a4197b1fcc7291be9724ac5c13fb647f8c6cc9abd7ac4979408b26c659e77af53be723a4634544f699898874b6720a12fb06e93de58c270255612a3d3a871c396918ef1321175442cd0e44017e555b09c1a4bc86fd1ec629e0e13ee9cbd601db149c61801344d55ac5bc9156524a0258c22c854c4a1b2aab84245cd2de9a54a84a1fa992b124538dc0ae15d44dcadcb18d37146bcb8613e31db9a0848d24c5d1d8c00c7713ccbb876124d1deaaac51d859cad98ee96710a72ad04a01777b47eab8b11016aaf4cbd38682e1ece984d2d060071bcf53cef31a5b28a2c2638ab223053744eaefca4c895e1499380791ebb7bd154c4b9d464513004ae338154fde448b67c0755cb67ae7d1eeacf8228db452f5582eefe0aba6aa1384c4e7f650999c8a2eb622f27e3e14f7c66cbf1118d6e8b8e8f9df82374c8c75ab13d53137a8f4c02b5e46a18d2af6c822b39b3ed0e388d9b010709d25b55e130552149d2013b2334af614a947e45990ac417584283e0e20c10d1b27c3f13df47e4461ea1a71fd45875d6a9131821355d3279c498321fbe3b8fdbd5dae6511ec7aaa37224bddfcb913aa8361f984a0920bcc6675c6a608e0dbfd656192b4ec150baf301ebce1872e050c4e597d61647fff8d66a8b0f908786e37bf4cdf716d6db718592df2fd80d42a9e28b6df0443c42227aa6c4f1853d128ee863feddec4992c1650cdeebd02683382f8d678a13beb5ec48427c23ecfe8a4d341a12559d6d181b86fd28fe6b674aee5bc1a13c132f652df1d3c9f4426f64fef970c5934ce5b0f68a0be9a110f52639463e823679905a749d71e62efce31cc386559120f226cfe62a4099eab7cb1bfad13a4ade2134537736f8034c0a0e20360a693630375815fe24425af1cac7bf4708825456e784cd3a1e5522ecf25a9639d304b11b44bb61b4fcda6148d24e9ace44720ab482ad90160764f959d9c6238a332c21d8c197e9a0a1ef183260badb4c7fe86768c6466a325aa14734b51af0df8419e35b55826d40741c44c55d5582d80b9ab61611569540bcdd8e837dbaaa746274f3b8a7feaab2166751038759558e648064ae934e2f569606e4fb87130dbe19ff71232010263a2792aa5407277da883d3e1cd1a3da1288f0bb77bbeb27aa18396730b567ec8e133836192b5b80a57c327370c7c9e1f1c03e9a91ae13e2bde7ab9462f15f00eb2d61c877b0f9e7ec108eaccb786cbd6979e089427f2561d976337ba4f4ad5868bcb95f000c0919664e8f50aed6fb5752f465a76e1ccb2068efb2a127c7c1c1c9acac109edce24fdc427ec0db328787bae14622e2611e76387d693370409525e7dce42d4f610b209258600b3ae3ebc9d5c358bac8d98605934a5600532e833ea5c5360a0817b537724d8bee104c606c31b6d2a012b99702f7852c5d010e01d3040dca86daa03336f7dd48461cd63686acd438535ba3d3c52dd481d9e35ba73074a73f7f97ac327d9936205abd9b2a915de79d1f1310ba565bbb23503d4e14903d10fddeb74ff05db00b0a789355d5b425e099bc07bccb9f3267ca3e33446947bde0a52c3222143761b169d507ad97117438a697e7b7b8a4b9c4293552fa2dfc73d0351777b71c1f4cb116b56d3a1a92ee27b6afdf83cc132ce1721006c2dd58c1997f139b82aa9fa002b2cdea7b47046620c3ebe2aa91ac80ad677fa633dcba274d2e7d0812b7e80d59e765ccbbe2a4fb45c14f00a1f78bdd71ce9b2d68a733d970254d107aceff4c77a9645e9a4cfa10357fc00ab3dedb8967d559e68b928e0153eb8fecf01999dac3fec74d7ca260e4d44bc2e9f0c2e090c6de614f0b15a2b20da4bb563c6454c0eae4aaa3ec00aaff79a235dd65a71aee752802afa80f59dfe58cfb2289df43974e08a1f60b5a71dd7b2afca132d1705bcc2075eef3547baacb5e25ccfa50055f481fa5f3ed87c62f964d1bbd437733811627a3972507968b8315181c6d55861f13ea585331263f0f15549d54056b0bed31feb5916a5933e870e5cf103acf6b4e35af65579a2e5a28057f8c0ebbde64897b5569cebb914a08a3e50ffcb079b4f2c9f2c7a97fa660e27424c2f470e2a0f0d37262ad0b81a2b2cdea7b47046620c3ebe2aa91ac80ad677fa633dcba274d2e7d0812b7e80d59e765ccbbe2a4fb45c14f00a1f5cffe780cc4ed61f76ba6b65138726225e974f0697048636730af858ad1510eda5da31e32226075725551f6085d77bcd912e6bad38d7732940157da0fe970f369f583e59f42ef5cd1c4e84985e8e1c541e1a6e4c54a071355658bc4f69e18cc4187c7c5552359015ea7ff960f389e59345ef52dfcce14488e9e5c841e5a1e1c644051a5763e53f7f4605967a86b8db7f2d54ae1aca3a13848cc9ff5e26fc5653bcb0e836471934246725aec2c9d9f723ff5250c1d86de758eb959792f53213676e93440ef915cfcbb2f88d8beed4678cc661939b4232f4d774d503ad4fe1e7a79aecd6746791102751c283ee864739d7c689b526cfb892ab55b05ca8fabbc9c30e36138ae7a2a08911a3d3e008b212ef203850ec0ca84f121a358be45560a90870ced6a7b329b8d5f3130f306db5d7f4b45c27f694a48b05ebf41062e80264ec463b441a9d623f872f4048374583cebcdbbe8000b28a4e688bc3816eec3e593db64dbf0782f692b078738c355f5b70778f71ba5d7d0aa5944309fd0254d32faba07d1ac581166c46dda2a180ea39aeac5e34aa729dec4805ce3063d926d5607bdcdbfe7a51bb8f1f6997ebcaf8811240b194b887dbb8070d1770301ae921876a93cfce4e36cbc10f2cc74342fa18077cb6d4efc3c06689752a25aa23630722c1d141fb800a9f0af7d393a12149e730ebcae9f5fd5aaf22b22709a7a8adfd6da6a8b8b8b30dec8bdf7d1ddbabdff7341fba89e2655de630975225780f38405e2dd92983b01e962a255e7c710159ed6cccd4c054b11a410f7cdfb09e5f3e5d76ac85db5a97531f558aec9e2811cf1db85bbf4779e8bb8c16c47b29df7c84522f24d2bd27732b11f46bd91006bc7e95778dd1dcaf2faf3169fc75820893d15f9f63300bf737aaaed1c95f7e2d362e4c2582a70d453fa2ca6ea323cacc60dd80875f36e623e0702bea35250afbacd3da95ce47f59437b9ddb1c0b04824dde93996c6b96c21966523a465ef67b90f5e3f40be8d62a75e7891c438009e9bc302bfe023638824eb81293b09a6742107383653d947bd848dce29fe9aaa51c19923207c205b92c1a15709e5e31cfc400208f048a94c295c29f9bd033b68e653bbcf6edbd2ae956c1f4a701fcadd2769a6d00e52a89e5491c229a0f2013850b66b04865f7555458145bcfad4542451930b76908f5ec81e3a7ab9b8cfbb6f811bb0a5a0c325aa8c6ff431d8e0991c433e0fbd4093588b32bce42339bc4cbd847829206a2a6a2edb478c1dd8cfe20b697d93992c6860aa199dcbb93599888c5bd7c9c30cc51319a1090d684307a18d0df7289b6fd667653a720460b81f48e2aa663d074210555e8da05f112f8df61af7f19aad42eb8d417cb685656b53e1d3519107defcaa4a6a24a1577e2437bfad92cebe1bec3a16182029c402899a0ffa8a084454c79001cd6ced891844100bd174aed2297a1150b51e43d927fe6d64626017288f7783e545a88062534af4591385acd8a69b48ff0651f1e57f7467045a696130b8ccf60018281f351a7326442de4b72251439750d301006285b2a477344325b6b81f6fc0a9d47b031f50d2e096af0055464d6061aefe61272e8b14c60fc8b6210e04581f44073ffd30501988eeb76b1c93bf2a5a18fa6c0f8fe2c7e169848fde7d6493d2e2d1495bde93673f740f4418f5ae332778312000dfe9916fa9ee13f5d3937b9727f3ce6006559f05678f5cf4ec94e7b96ae659a3f2f854f564c1f4ac177a1e359136a183a8cd3ca92ee598500cef80cdce8229e06ca1c989dbc2197fcef170fe4014984fd54ced2b673342ee170c376ad37a1983554f10ef3c8973e8e54b0d9faa891f4b3de4fec02f3e79c5c3c2370d8d240cdaa0918eec0d8cfc70c3211ac131aae2b018a12aba716a93dda791dfe016445ec0a0d23c384ffed6a58bc5408c3ee2de7278031dae6ec1d2d982a207a4fcc9f36e19777e105b8acdf6fa0a975f53a41277e23fb7a3c7d67192aa01bdb037c5eaed2049b633e7b4f347a8852665a6d7e2fdc98abcad01f05094ea9fea0fb8d442d9bb1e8236b5b00b7bd2281a6debcc80cb6b5a826640e5b4afc10ca83a5f3c3b530be7f9c543d2a98585f36267137b6271d3843a011a75f42c91a73d4b9d05e5c7c62d96f51a556797b0f39f5740d36bd5483f010d35acf3cbac13ff41211ad53abbdc5be7f8739ded4b0e9ae3072e0d8cc76d08dd20b093700b3b736e40e5de46dd59840d195e260fb5ba6672a3903fdb571599cca9d788871873a9ab3c85f8d28c8c78a93675ffbfbff312ebfc19519e7959e7079d45ec32baf1c14c24e64a7b19a3b49a8cfde6aa21c75c136719837c1f2a7b6188785ec8b506ca1d49dffae9e68637a3e7bf2bae36da2c57d143aa6c365fe2dbed4f458f256a106921099c898963eff3b8470ce65f2b3d300e77fa6e4a5439449e0e68b8a1ddad5608b434a387cc340539614077810572b7e5ddae6c903888fb27c790de26f13ef440afe61a66cf6aff59443825510db93ec21cc20e8b8a321c4557f6cbb0fda207a3f047a42f77983537f5745e15c1e89dcfac80da89acbf14c4fd1ddfa0747893108cae8eed6af22fb3032ed860c3e5e383a7d2b490fc7ba89e245d3d2d88834bed679c20b58779b0fd0cf02b286886f4e04ff895b37adc0db049c7f16fa16f8ac8fb7f8c8db8bca582adb3aff1e4e6d741e5121c2e337199043492e3e6b4c2a8a1bedac120a3f64e636b09a74645ddcf1834dc4d7db71c00b5ca509816c2fa2caa1166ad252daf6912101dc2365614b1612bb98f4d0ab48d06b77b0b5d082ca5e3f1c508e78d003bc370d821652df4eda49505fef8a662d7870eda1a04f3674345a521b0a2d1483f533275973d2a454f0c392ceac28e31f635f717d3ecb31db734da55f59f9e4202a1da31720edfcbb2e416a15b8b307e3f5b3381a48a11439966480ef01956d50e05bf485cabbae61aa80df0ff292b0f2e0484d1f4ce9a483acba580588b6a46716acfea886f79e4035dc7405aa5f82fa53c266f04d339f1625a8812c4c4124bf81ca2eae2d8ad89b0a3e307f8334e593f50c62089cc854ccb100f74a921899b91411140e3191033d3c9766766dbe7750cea74d3310c8d95ddfccabc02c7c5fff278f1c5e5c082e34aff787f22c850e320b2ed98598e69d11f856366e164be2f168d4121b2a8f607ccf7a479825c7134bf6a9546482c89e0c3cfc42021b973da107900304abf08006335de82bea2f618e8d832a85cfdd513e60da859416e158ef58a789030a3cbe40f97857c84577499c5682a185cf00de6a4ff8d4c60b930b765ce1f967c4fa3e337e540f4913033f2eabf0e1fe6ac88edd68ada5bac5932609d21ec04d2ce755f5f5c49568a334d5b32ebd0cd30a83c0569206d4741b794130fbc9f8a6bc00db7c80755138b433acf8a6c5f53f70195479c5531ab6df990ba65a587c9b86fd0700ea1d32064b2464407e18dd604a6e1419ce1aad74cf47afc9799c5a3401f97f16bb08a7fc00db7fa062d69eb1d329927966952b69073a443b8cadbe75460a8b2e1d68f2e6add4ce7bb0b237a68281fd1766646b13c44d88939093c74d48c624a86b4825d53b0eaa4501a40e6da837c5d012e5e1d38029d9583d6f382bb7713af87ad039c4e01626da84eceb635007cced026127586da1d00dc6ac5c732eab7fbec0e48d53e3d99417e8237a06f32dc44946181cd400e6f15e3c40a706d1f41861cfe16833479a8e63cf35fcec37f37d37cb92a0ad446cdc35bb54eaf9fd879f78abc01c4d2941621bc87372c146870a0efd32df604b5d5447512f123406085af571163208af18d6006c3b0f3a9c88c2144cd75dc9d649426dd20f3f9606d67e118ab6387c125ef8bde37df0d12484dd1409793ab428b1536c73167eb6ab45fdb6096a8731a69e53cd3a5a8072796ac59a438a5820c112370464edf98256a6cbbb0d2fd369be8e252b017e3719e57a7d4d04b6ad3a55d57fd652106449a60a03b34a2f21e1c9f89daad270f9a22e85f927f5a6e8beb65d452ee6a4974de927b6f7bf8d45b4a3d32e136e2592978ea0c3203fca58bdde3330584306fb0402c9aad3be9d68ec9f05acb9e6feb71fac984f73daa7c1ffe0c41257100d77e10510883d8724db57a29d2606392a332cf8895b43ef540d5f19e0c288109e1e377bf561dd67f2e0474a4bbf42c024001c76db8f1b7e153b20eedc67f0e3986503f37fe0c5195e7e1445bac60a7e31ce62187675677d33e3eb1b6a4a216d8c75921ad58c15237e54ba522670cef8ae2f490afbcf204a2634a843e6ad43c3accfcdcdea683427000c2f02ee2e71d42a3298dab2357aa1697b0f222872fdc61499e611702180a0e6f5bda8c3ada403a69c8e3fc8f70fc1a313f5a01f8d35ee89b21ea7f7a4844e414d2d2f9b3dd84d1450c0dec45ecd16a788e9d2a8d0e7b0774fb18d2b30b3aa70e14103705b89e20c812a09a770afcc76922952ccf6ac14826d06fc32fb2258a616aaab895885a9a587ef1d71d22c37ae257377cabd77f5f5db095d1a006a96e5030757d8ba51612bf077aa1c127be0222d0492e9eac9d54202c974ad2ed4da5557a7888bed54aa4a60abfa21b3575fbc5898dd79ad8cf6ebc808777e251e01a4979cc13883ca0c50658bd9d9428e2958eafe5bc8c54e7bac5eb4847ccb84ea2f11aee30a8da0663edfc8892b3ab528a529d92a4402eac340d7d9d057bd34c1fa16f54e0d38f94c8d625c96305d89d5e423de05e78fc4ef11875dbed05ae8cf20dac68210c343c69710781d2fb33bf67228e5e26202f83b07a9a6bde1c122084844a08df22efb6e083b1090e1c01355d6c8521dbb0e0fe603825f7f621c17ed2f560937aef022224ee6228a4947d981f49e13060b04ad163c6831724174a68e57c0ed744b26b4ec7e67b8c58d7e80130b1f083a90e8bfa00d75cc09b3c1424273b2fa59d9f05cce062a87452780115611681d01845aa6898bc41adbe11c7d157631e37d9ae16e3adf20339c79c7528f28b22318dcfc0b8d18197637daf0855410cc037b4c1002519eaa8e8155f5c0aaab387a7346a76e0dde92cc052a8cab8042d3768db45afac78697e958fe1583f182a309408e98736d8c03416992623014715962891755f31b35a387cc40c175a1160eec6c340e410fd9435ed9e84a28758203ccb0060a9472358e8b53144d38d773fbf55b4c2aa9b45bdd7512d7de9f9e0fc98dbd35562518b850bbd0bb4219bff832056ad0f90a3b9fc2c7b093c4d3becef0f4264a540a371554cea7f790a3b28bbdb6302a177b8c2181e09c5a842c3bd504e7eaf3f08f41722cc947de28ade01608625d2ff1080ac2e115e200b159bb7b2c80dd037d5ae2c37e4188843703cf38da5c28bd2439e81d10f94c81344b86854a74c908c769cb53ae28bf23ff0184214450d6bad4718ce37257440b172f0133e4a2db3629f7ccde186195186f32ea47e19c75db9a6d1b99c5dd467ae4ed3474106c29c00d48f3da62e46a06508b10fc479b442077adf68e33597b3b2baef3e00da08a03b9f3def11dfc02c070c18f0d61804da56cefe6e394e409163edf73907fdda1c21ff9c7e5cfb6ec07644b0d71d04633b112c975d1f42b3fcedf9ed453a75acbdf9a828db57fd284c2640eeb976ccb12cc3028bd8f3ab25751a3dbaba2f9fb26405977ae30d2f7e5c3bbba26de88c28dff31657b9ecff047a7036687ea04efab82de6c10999ad65030f7568c63d511700805a8ceb5441ce94c92183ec14e4226b0c34acd7dcbcac7070f9ca2ee5e4018423f41ddbba1d7046674fec79cc94d470f459c35d77609622130e6c6f101274244409b211fc6cac2bb0778440c6998b13736a21d1f0f905d82e6b6e036ef9fe742e8053fc2f15b6620e7ea8a6dabc9e340ee3e9dd88fc4381ad854758abdd480c58866fdd88758c2b698daf5111ef67cc59ea6816034257ecf81d2009248e0d81eb34c3ccf6ca40ec89d7bde6123f7f9b83e832a1e919a0cb0c523942ef9d096e16d7d9714e7f08dead8e5853e631124c023a5e99da36ace6de2901aa49b94cb5a01deceed19a6d6d76a4549b96bf1217227ee5063fd1748839f5e9e61036b916775da0617d4f762a000a1825ce2e938affcedfe9774001d0ff1b4e60e5b98d2073b37a38bbbf67617d0df21d1f17b1d63c730c07d5679c82677e76794e675fc674a7331add765874ea32474c1a16d1c09382bd848e93eab9eb5a01836721c82811a037f330e5cc11e838453d5a32a0831d17b8a94dde27210a025db458201ce24f832675b1fbf71bd84787f7c846e3f62212b04e5b3894a8c92f5dab241186f51e3b958b4f309421e35bc287e937a4b8a93d89db9673386069d6a23017661bb99987ddea1c6cc8eafb58177ab3dcd3902bcfeec81703c8af6b5042884ef0bcdd9183658dabf912d07421a6f200ac34c8cc16b539025addde5aa02e7bae349a79b01ddf69bc8ad528b3f953ac6de0808fe946100ec6b658f555765c4d3f0f36dc35f20123abc7ebf5b461180e30bd21c65f58c9378c1a3429f78ec1d54a356907aa6d61f7a0d2638200f4257ddb799c96cfed6913456e95201421dff067d2077dfdf4417beaef34ce6781073411bba1684f465d9ec43ecb69b2629cdf04442393f93e6b05d170399e63f34f3ffda703cd39302b563395ce40285b11ac7704ced03be053e44e808e9cdb07560e81a45d055a54c60a2953f237aa157b685e67936d7a44003b8d75eb178c2c3678d943c19e19009ea2e46d81745603d58758257c2b7c997ff9d82d9d9003ca274a45a2c4041198f7d22c6fee1878a2865e5e3751afc640e1da112bf284b59b3456a21d51c7c677ff43a6c5b428a4b546b4933886c4e8efc284af5e4a8eb7abc4dd22876940a49d94b5659e98ffc7a302fa0ab6d7fbae2ba1d589ba4da7bfb28e087afe06f4ca9cb18ee8f67becce9bfafae3325ffa99846e62c33638d34f18ad0f1d44fa4384655b859bc2ffa62390033dbe2301b025c187340fabba37a00dca5b7808f1b09f8eb99d6d536955efcac370f2b235078c8e78f6af7b2e92fe36b978223a5688c2f055cc9f6769a0e08ba895e4a9031890f31633d9e54b58b754d1f7627617f02d052a7e9749a9682ecb54da79268147d001fce4cadb76ae2e6160969840083daf30b1efa28bc9a40d8aef64bf23ba7dde063ff8aca1275310e735fbbde6a3a01c0a7273803fa5093c74772d117d75cb1c03dbb010e5939a2c945716fe455634bbcfb0bee6ed39ba01652b607eb936022452e209f3d0568bb22d12494cbbfc1b5cc4ca2b1a39162c608dca8532d0d4078771f5c1bd4aa1463bd46e56b3058acb13dec4e3be22a3226b64d770ade759c324fdfa4a1231fb5b7e232072573da28100667644dc39366f4ee995d7fa96c30c595a648bf2b0157dde760efe0fa01e7120422815249c36169ac6f8932629ccc343508cd26bfc31b4520f98998639133fcd4c3a4b747acdd92f90e0c8e0a052f6904b1d92f29163693032070e174e008855ffd16c5e6e9c1e6e72e33a24ddb608ab0d60d555a81e9f6019d099efed1a99fad9479d3b6de81d7fe0488e442e1bead09054505b9a114bcfdddc5b8f25ba11c8fa63754863c5d22cb7f7eac11dc17438151779b3385f2fe012eaaf403d497a1aaa83f4c65bfb7f3749b0e357b45c4b9e25de22ea6730b1d1b428c0fcb3adc63f6df19095ad270981c7b3cbfd5c7502fc05767e99fcaf0aaa9d90b262fd2028d95cf0008020c86bbde4aa900979f29d18514e5dac38197a46c469ce593b45397d40be60facb9d2a34897f4fbfe6c89d4294750c5fd6addf1a0ea20a77ec75713498d418c3675588900e454ae7865f79b1f1b3c791182ef84b3157611cbbff667162982c993cd5514e590d328713bb1d85ce960794064395ccc314ed5acf1facc3c69ad24a24afd3487e26b5a31144ca08382ba55e5ac9a15e5fd1af627116bdf3d60c8758b194d22e85b6cbc0daa3b1606a1236b1944bc348e892e79268ee3208a5781f60bd1268ada18583aca033ae87713f64d86437fbda16ac02edebba3b4b9b4ef32f8b6341e2d4f6d17d49dd24857a14bcfe1d85701e3138e86e71b1777d61a2d659f49f23f36356e839ddf1ccf86118a8c221298a408480caa521c635540259aef9050177b9033f0a9f30d20f0a34a260b4be45fea55f62fb8312e4f28b0a2fd7517f099caa33f52aadfb6fe291960af597b8b999a8e0e1168b7b12b57a477abd02b8b23bb762a6067b8a6cc57b6a4b0da3583569a0e28f45e17bc8997ba7dcd5335c7e4a739f4945ad094872a0ddc6b4265e21ec44f0949a12625f051f8044a8cd40972bb0a32cf1e420dc620d056530790195cade34d7b8a86f6e6982b2493018c5d1c213513e1a56ce1d4b1859779d0eeec03f9190661e0cb298133d623b796f17f47ec8d58cad2ce05ee7e17b676ff3666a760d1666481543479ae3a68258c130b75110392a0d43c27100f3b9a232416449d7a11be4316cfc52a636b88b05891e645b877aa9f653165537eb6619452e18d54b35309bd0b5c8ae155565e85261b6169386a3ddb934247fa647c48154f02358008903b64f04534912f6f950d763fb606cb1c50e40f5e8dd5885b94af898daa666378da148fa24d28a85e6a462ec92902f7a3ae13096e58d74a528d35ff5f4a9fba7c57fa7e3c0b4441d9d1e5f4b7fe4acb82220cde64d288a8a219bb847d15dc7d0df7c0e1d408d1a50e9b2a6469e5379ac5897ee98999d4c7ff2d98b60717bec349c5737b7e926beeb029bd597fb43ba959b6da5da3f9ebee30d873917d2576d4bc1a6fc3f92c4e2c275a272654b642b616a107d118d48af2c774f85e7fa42a0eefbf2c97b86b38b20795fe87511f16ecf98e697a2e48b405feacab52960f33854c5b34abcf4159f7a89ca35c98de9f92449fa6db94dd6237c3b011604b80a47fccc0cb4bb639f37c126be8ba281ed0c408367af805977fa0018a88d78826996d9b94e0db692ae4081e62f938d4237b5eb888c4f8bc2591593c864d3b31163416e3481faa6d2a300488b4f56df6979ba90f16b456ff2572f4235935ab4fa39ff2f5303afd49bc32789b4d1e5799c299d3d90b9ab40b1c38b3b9d4592a2860912c328c8e514ffc0e5698228ccd5983427d87e4519f7fcb27d64143f3877c3e24fbfc6acf0169a35b7bbe5cd192e2a91d68bbf27749157c7751369a9f3f8b1ffa2c37f3de52e45aef92ae2266540987700e3dc6a7dfcc8b2211b1c1a085b0cfbfedfe80e1caf4756419b982eee91af58dd5327bae7eb8fa690cde97b4f1c58e971b04ab9c885baf7212285a0b4aef200d9aac35d19d4c2a23105305d8aa9ac95ddf23e7ef20e9d86a1eac727292257db7752ba071a430ec77d0c3fccb03008f20a6b0cbd81f8ed8a703f0ee5c24ac4f6d2d5b4edf52b378b11cb30efd134e30c6575db02ddbaa762d546ddee956a9997ab9a744b3f94ff72a01177fbe3ede6db85c7070ce00c9e69d2f877670571090a5dee02f16d7ad0bcbaa60e875c2622036321c9a48f81e27cf7a3f61b5ad160126a26490d1d5feca1cb1ea915e19f824b45dcac3fdfb19e433aa2768550115eb3b3cc4643c295b7092e559abd50a2f717fb17e62da5551ebe760615edf430d6fb25139371da090f478bd600f4e32c7fe1d6302ab44ff2f31e4749aee41f7f6d87f5d6274be83fd319bc045bf38c349ced0db6ce66c6e8f2ba7a0e0bba78346f9208d6284c01ffdee4bd0ad9ac6f55063ad9064fb92e1b39c2479bf16d8d2e9d4edbd2f363c5142e40fcbbdff9dee57807059d355e60f63802727f0883ee09c433dc2bd3b1b27e3d12855963f6df7e7a1fc5cacb68cbcb9101989d7041bf138496bf86235c7b41bdb094d3f5e7fa0570a69d2c59b04f34c39e335b5f9c886b6117934c7beaa4f766f17db01cf00031213c0685e1c3aefee35424004a73250180ab5eed2893c9f8af800b710ca6e63775e03cf5e944eb2088e2d1545122e7fe7c3f6ffeaef039b3885a1e83fa1a8e0e5e27e835730f3bf2948c9518ba5df08432548c70fc9545369964c85ec84d9e061bb6e4d1d607d20d5dc0bcae2260ab9aa01c9f86e03e145912be8b2d8b18646dab1eed1a831276ae34ef317eaceb03cb7cf2c9c71ff341f8c0df5a20e0bf1cdce7f1f1139f7ead07a7b22abcc0951ec5ecfaa0f50995a90801e168071f8c219d5b8965f718ea5a7d71f2b83028ed446dc9bdf980fcf686a98a7da488f2cb635b0d89efe0f8f105d217bc72246822fba4b601315e2d2bab45de92f9ceb65201b1a69c5ab34088daa1e60364e55972f54e5daa7c006ddc732994202cf1a841ccaad27b7165d61062f26abaf2e3479eec7ee334a25c526a5946e7936eeba03f0c42692be04b29d9fc20df07ee775af981ea0b3739b9753add7e091801959e1200791ff82eed2e700d4f0151123448c5a71f04766ac431b55031725ce618269cd0a3ef83373f2d56c87e960ebd1e006191f8fbedf30430a26685663c766f9070c65f2d06f859751fb8396de5c47d1a58465eac2305dbe5d2a55424d530b7331d9e5960aca1cc5d3f8c03ad2e947e825ea9da3f937018a8587d667e00b5cd7c8a740618ff4f8e8c43584c111a986e1575d3858cd8b3090451f9fbf26069f10e1337a63d6b0102c03ad1da936c6e1b3621cba016a0cae63630117930a8349ba1ae1fb30ca610bcb959f60ef28f5f6e761a17b2fd0bc3d5d9a30d474f747e850fdf361ade5f74a6dae058007666585443eaf858c99d33d85488b53635506df929b40210679357967bfa39f3ac8594a5a77d561b2e0f347ffd8b7ea896913b7c1d5379d820f3fa3216683c611ffcadc001b9f5f7c161fad169d2c693970e559c87cd6e2b72c4aea58c03ca22b6c7b91d6b1a47654902adeabcd5cf6ac76b4c560c58d017f5d821b7abe21f78db8a705dd126e2e2e50f43be3cb4824284c13592c6b20d09064a28a132644732c0c37a59d6ca80f5e5668c2029e208fb043c81d4cf40b9297549f76f8730a37f24bf272ff0700a7e17d16ef2ae55983483cca59d66b0f255a1ed0ed4186acb21bf7041eb2db085561c42132801f093d705d4c4d2367f7286025761d2a304e499b703a7c599bd29bc2669d04cc2187e7c595037d015834cd41dacda32b55d8448b2c5979595f43930aab7200cae19f9d97cb5a89509866488da1f17b8d65b85699f943aa7f47660c946df04173367e12c5847f8cca58ba78427281fc2780a5e1fad2dc2bead183c834ce5f3cb9f40063d0cfae7727245646ee36eb347c77c9b7d65acfa9028d46e7790c4e0b565260d669d80f58a96ce0b77b22049e8ddfecb0fcd67ab3faf8aef979467e10904e706fa4ff3d6ce59415d15ea994a555fe44807f3f0b01f92c118348705da1f6daeef86c1bdf25d317d0f0a08fa5e217a9217a76080f1f8e9bf78d6aa64575c237baeea12c491bf9644ad833005a5727eccf57f419f609a93fffb99fe451fdc4d6a7cd81dfe468fd0678b06bcd4250aecdc92ee1762904522ce222ae850841f21b40bf5f0e14468466ba2783e1fc5b2c8fb548b23eeb31d2ad96b92f452bed4f818e8cb406eaecaa8c98c88d1bbc534e5f4b76dcd2352faf819f6f96df30140491a971de2a23467bb6fc3b77b590b2de2e468992c39fb5315548bd159c5d7310e9f49da122aaeeb2f7982755ec09b44b90ff4767a0c674455cddb467a8a898a3f460eb8baa826ba834004d0636105d2ae594435a2bb5084e061ae6ab5a397893b94ab0abccc3260d2d2e41cadff8c6743732c6b35e76ed67d54fbe37f256af1bd7b64e6d19d6df187c3b063839e4ae698d98d8ffba03477483461d287348656c2df11c93936aed88d819e0145291aae2df0e6f989ff2287742c0068817ad67d8e901f068931b3a052849b426aaf04653a548aadd81e2dede22afb3824500043680653a0651dcd9b812a2442e6a1fdfd000c70d5b84aa8fd66538709ae38f17026d6f41e31cb1423627a7a1e73f4f137dea6d813d72d40dcb7f47d2e5cc666f45a107e52c458ec308636689a397a873856664c0ddce8d207533c19b97f85b95d98e69258691a48b2a2ac8d37a02020b03863a80d365fb3faf59f5e8aee6bd733a1b5103cd55744c6f7627a1d74278ecbe05413cd47ad006f2842a9ba906e17af17b532d03726611b797006633132d1da86fe46091a8e2969301ec8de8d43e3f3337d2e9a65b812a8467d30a999a0945adeb35069b4f067a5b4f364e1f937b5d20bf4a49d91cd937e7b97e82b52acd0da959ba7a81a00ab520bdb9566a86cb0f5b9039589d9bd68cf44f4ccd8a2dd917ef76b530acfbc53586267c888ddbc6f0b6b1ef07387f0810afda43ba06b9ef4ded20643b9a242ee1426b7219a9983259315ca2c24dcf0e7c15a48c71cde13d0d4bb1380ca13074647d56b9e86af7dd2b8a6bb520108d3905814374e765c57ca3eac32bad383b5454106cbcef614b02c4ca4824a2d12b1c2c905760ed9b4d20120c472ae99e742eb9ec1ba4a8e8bea3653f784b9b39564158dfff163a6326de2b24ecc74cbf9b14841df10cde47815f8b3cc09f0bd23f238cfc1c205f5cf617608ba790f9b783cb0079c43b238e89e4598ffad3b64227f7404805edc9aada58bba2f641e60540e714063521fc7d78cc3dbef193db6c1017952db1e70116abc01641954fff0d697b8600a12b1571c9d8290d64754c648fe50e0be41a7bfc7a4f691a9c1ec55824ed610b6f3bce1c3827e20bef117c8edc0e3fa81a7674d22a3d9946d8fde412f8fa9476ea15afe70ed65ae0d3a7e18061f7ac1754178ca12cc6a8b8cde65c7791678a87ea80a635257c4fbf0ce2e5be48ffe43d827af2b184a734e3154d0cd91c32fa8547138b1a0b1f0a2b1144523c10e75a989f418742c86d8b7caeccbc02c94f411cfbd2e563e84caab71cb50570ed673bfc4ce4fbba72b492ba63bbc82c0fe8460ee24ab14379ae927437d3e5b410cb01dd1ed590bd836f8502dd48c990618274aa39586331cf1c457d7a00f0d59a0780b5a652628ec3f787110e603862ccc30d828750a1637c720c1404f24a44faeff7d079429bdf214c802f527f14ac0d36ebdf1bed1c9ae78b4aedbac82f73c4a10a530168b24670801bafe2cf770d78ac316d8a05c2f6c84a213376e3fd8a9efb1de313a8101910a4412e1e7576d630954928f1d4209854f40a8477d65bd4ac730291488acb127504b44fc4fbfc1a7654acefc6a4f2066e66cfd3b1684512a0dba98e8fc99d73c357e5bbf6863b3ac156807a9aff99b34463c392e282a01bcec42d0344c9b0551b8c1edb538b97e83d26001165da7d8d141e6d3494ff6d009d50725dd54133c6c25058a2b611d576692ee2b63c0771e9f0400c944d7459a061858a5ed477c59a1b12b8d4e6d2378c711c0ab6f538aaea99dfc682a849f27b553b5ca139547a359b9c17878056c1acb62e6d09a72a9c7e83c73ddac93f72d7718d83cd441dc43c92379fd8fec9f369db74d20d0c8c17831a0fdb4163e1ab237c0d1c2eb4c259ad24e8740c1870b5ac20d1df7342b104bb36e99184eff5c787348a704884a03d3b789114922a5eb15c13b042d194df13440aee0e5017c9ee8f7a33f05d41da6f81a2dc3fb9faabba0d13b30983b12be0b6a78b89fb2eb31cb6317e443829c16c1ba3c26d7936ec84de4878b9b0e8f27daf15037170e838c624e8a3f836f9e06b76c44b8151159c5d50dc06e323e741d0665b544ec1e763fe8c17866b5f89086415f760630c666340b8b1a92c41a813177f96fa602f0c540e667bd745c476018c4cf56daa9a360908816d8fa54b10416436c3ac5123ef6455ddd0da899427173e26d9dc5609ac944d1421384f568ad67be10ef6d07340fe644d00236c24c68a69b01263ef32e63aa3046c09fce533dde600c037eca4346aeee56065475660ac02c2ecf46518b3cbdf41b1ad27e27bb4129cb1cf5a052d3e61c7a7d668455f90d2a483cc95c644ea1f55d5fa75009da88275776a402fddd46eb3da3611e8cd0faa1d89d3d254d97e9485119ec603ad5539cbf0c817dde61e60d1aeedb4990a878e885b105c74d261b986b7c82377c26b2ed5b2207bc3876c431a742fb0a0c72c832af04644c53c6bdf2c90b04dc7ed58b22e79c6ebd25a5039905d5ca2fe4fcda193b2a93b48813e70c50b116fe6099d8a444e77371aa6309db97a2a2bb55fb050379d3d5289bc63fc020d2080fd8af56a36b057b8bb09a28e6b3cde1470df4f392cbdc8afe63fb2c948f80e3b7f7f435cf0f6596fc706dd89288ac714e0b7ef75eef9642f1ac96d4032aac6a9d6a3f9f0eac5bd220d37b0691fb3be586cfdc1020d762f47d9a718b291e0345353c93d4da9b51a822567ca88c3e33c0acde75d9b546cf4d67515eb4f2b0b1654f77ae96abeac24bff92cb9f6b6747f13623dbab70a9a659bbed322a39c9cdce93446f1ab49b7537bf23b5b5b5613e0bad31507f3b3168b442c607fb0dbe7cf5d3710be9bc6a12c054e4c49c01a3145a85fd7af4bfb6dc633b019abe881f2ab0411569363a68749b8e3acfba475b85f003b88c1cb5b6e044dd29dd034f15f75cfc8b04638e620bd66f72423700892040c656f2246185d4c5d04638d3b017309df39c670a47ca8941c8e3339f19cf43abe160bacbf8d173ec4e9554bc3ec145fa4b4871660a478c55e926c9096a993bb4a660c414a621b3d8565758e925d2d0ab574baedf7756f117bd6492089cd058601f9f2346769f1e123ec3880a9ed562d39cf36212ab53ddd478dd5e4be0aa7a4af6da9a1e90c9760f7976c2346e3569a1c0aa17711b66400b6a4208bad751693b87c3b7929ec0c058433f4b4a91780c7377239214f5a07cbb5642c1a792491780d97162af43d5927506771e6013680d8153fb4c93e7adad0fe3a79b807641e3694dd000e29078734ffe50a6bb06bff25cdeb64290c420b5b76a6ae72110a4caeabde753a2efc3ad32173c0bf60b84a935b5def787aed08d2f4ec75bcaec4b1025860d1c7367ee08271daf49764a62f12f6686eb01881b94e83a22f5826957f62582ed14e4d19c007cef8e312186b5d1bd966f59c8a65a56adffc526d1a06028d28c5dbe291ca0a9de084725e5f262ee3dd615b4b0a797a5d813750a325e7abc571cfa84e72d7e96ec198aa28ee9efc3d01383c7cc7aee117b4394eed94bc3d1f57abd2381e2987ac0189f0baa0e23a631846839bdaddb697e4ae5e79dff770c49a9636223fa8dbe8b2703bd730604af7a437520781fc9ecf3f2c77890ac9d3709fa12b99858d5785e38092ba965122d8e071a4750b2bc2e253b08c902304780f8040acfd01a2afe0f4000a4eebc4da7e838a158f255021a29f855a738f5b43ca7ece0342e8a4259bd2431a2265bb46970e16f6b72e37cb4534b6e13c8c136cd54631b8666075bb7a6b7e7f834800eb380c6dd030198a6a442ed974697271fc06011d1468d098b93c0f2327f6a030569367a83f9824e292a7bb3e3d5baa53bc408c3b70583464dc8023704b4dd3650be1b9201008cbfc50f94d958d5d2641c0ee6a1eb30dbd035f69add83b64025aecd8158051a3c5c3b1163e1b40b89333ee14c27ad4c10e8db4e1eb5844e5a644ac6daee1a876e627d7163a6e941b59ff6260a429bf01cdff912f1f9a1b56ce89cad8905746e3eb73aaa33fabdba582e49cff51e1c690c6203c246b02ada9baa0d58f2fdb367fa710b982ac0d146af92dc85a367e241ada40cf593c99c989b285de88b810a44b0a0e45fc59da34ec0a808a2634e618a29e715f5345539d55edeaf7db4c754693d29462201b9e3950858d7fc4394840ff04fe63a75def7fa1f464817b223e0e67a1bd483cb9006649ae4581b509c06e378072d98bc9c3b5a7c01071878a01048706085320b1e9ee6836137bee51d018105a7da87384407619a52c806a66e5618bc0097560c0dfb7adbe1091ee4eddbc6b61d2ce06650540c299f4d46f667d5f83098b9c022505a57059421ced88a485fc03a7826823dc5300a7a78bcf37d069478384472cac0c02cd67c38f87dc160078091913e08f5abbc7fa55b648a3b2643530cdedcb9d05da5ec6d365d5e0483a044065bf1810b5ef89ba25dc6971ec41e343e23bed627e3292b1199b46b3f98c9516923c338804e5a525e828b684924ead05b030a2e26fb5600ff486068056c654b3b0f70d8b6bff66c0ee32f7503342aa0a5cd2b7f5fcc81c4a3f46a9e2dd40495709ddab81478a5f001206b28cfacb49195be0f4140025e0a7b6375d4ba245f9a9a80c53f97d1070e50b376a1413c4add6c649c4e0bbaa4aca39ec930e482ff1ea36a0757cc6c20673bb352944a1a27696a6da2726e94134f36720473092cb6f0324cce185b324ac229de4d2ab14a70a13739a1c890bdf6ba1c7e35b5229449330722e35c299366d43af6d40e566f9a7fea1cc9892128dff49806755e06d93a26f1a988ea5cb7167933d0c7e96a116a47bb21af17399d5c51b5cbb4418b60ca89d776af3249d20df60f38f43af381c12a30e323636a2429359aee60a16161983e76a22cbb5825d1687cbe64add3f817cbfd01e1504b4ac3c79d8aba682bb63acfb4b5f4cb627b74a7c89bb7c07e38f8c61ae2db674f710f3d1a1356e2a3aa4089398e86611cef0117821cbd38488a308da53193173de6c601d38b891b825ed244fef6c6b1f3a1dfd86e4c00109ba0cc9d3482813188f657da66e85f45549d4188fb919e86f89fee77eba431220ae6e9360a28471aea75dd9da2892bddff49636f9481347afcc12d69a15fe20c5e95e6b017512705181f1451098a77c07ea85877b85fa6fb4e06af6af660ff859ebeea6280d147613d21bdbe91f1d34d2c6b06f2bd469451d60522ec448f18d4bd8ab700e621488398163960255bfe5f561c101d1556697962111b482298ea895f9d7be0fb84fe5fa2b3709195ec99db1482cb3caaf94dc4f5881dda33134a70400fda97e3cd5a453559cb3f207c50b91162bd3903ef4a333cdeaa752801f347f090d4434c1b97a75fae63074ea4b1e5cd93e599138855c899aa6ceac918662bb584d8cf06211cec2b927b2918c2827ce6653ecc6d81b5f49ee1c753858c2c8e382810a58504603d08c4b88b78c23ce5ecb98d740a5ca5180a480224d1baf97b2bfeebeff0b2adce55446955ce0b1b9073e84753da60556901ce6cd6ffbca803ad585a42c3532c1b494a2c1f8b6adac0627e6eb1b7823f98106486da9cb96608bf9f2709eb88cd38d1210a955006ca2c3d83bbd833a85ccf5f0221f2f9afbf8c907044f213da16122190059c4a7ec8df7d26898047895154b125ac7eb430ceb58bcff24b58eb1431bf000305720c323e6f97887966a04556fe73b5e3b8d05ceead3dc8c712bc387083c96754210fd50a2820d686d62a4df6d29d1de784ac040d84bfe48cce80f0c3466b01fa177dc7b8efaebe4e569bebf780cfcaa66f254c15f23e265d630c76b5cae029de0b9c963a8e01d2800924837b989b32a5245bf31e2ca92de55894c4982e30d5188082baad63235a7cf573642c78476bfaa08d3703d7a17251b123b4dd10595e20f09b5bde5429791bd49ef044e7f1b28a3badfa987ff5e94eb2be9ad6b783a7ebabde0477a5169561c5930e1b375616d122eb3be7188c93232cb9faf4158e59be2122552b05a547fd1e9fbbee673ce39ebe979a635038ee22fad68e3e8003d247d0d217349bbe45e0c828d04b9fe392be99da14d0d773ecf3a55e7dc6591fe8d953eb23bd456b5d01a5f8177f7bf80dfa330d773cf13ec9f8f9fec9c31d66a3e7963fbe7717f4f8093d290ebdd744cfa9456ff44b6ac7fc64952bd096efbedecfb74d53a7f79025bfd0d577fae6e35a7cd2de7b8fb5e707b2c6c49f53d037989fa7b3978f225f9f6f1c743d279e37d43075e14f84defbec6d7d6c3b9fc41bdf0bfa9e57d495c4d82f1f7bd04c65cc35f231dd5b276051ef9bd84dd2f33c57c0f133f28d128225e91b8b9cc8a7fbaecbf8293e1cf2123ec93400002024b59e4d071f319a9ea6cb4cc59b200e2bfa2345177eb845f058747b106d275c4a34ff2714a4ac79da48f00bf1cb78bb56909ca2e6b6b57c9362a19f013fdf840feadbe3d3410993cb0429405f1f7b312c9ab256c1a2ac3d5d380a296122f450b7ebbe10f8500860bf7db4bfb50bbff0922899484cbd414fd35806b3e13416e5ae57ce4c04dc54d0830791e0e049335a3a9e4146de2cc13187a03b983ed4e8b79cc1a131026d0ebd6a8fe551ff694e680c4153fd17a83c58bd3f2e60c96a1563ce86bae7d23f1eb74dfd7605b0146faaf3adf8bc689773e167018e3398726209e210f70038cfe426977a503bf4edc9dff8a5e638cc737da1fb9ea583c5d3f80ffd61177094d2d99d3bd4443b6b70779477b081971bf194913725f3940819dd02127a93a9c721f63adcfb037cd08be1137d575aabae3f91dbd937f2fcaf05f29db61899d47e2dfdfdec3f5802cc2b03aa4414c59a3c827a6bf5aa056fc51509613df4e4c2196b34b41c45db71f2f745ac5c998f8a7deb5ceb2eba2c1eee9ead180048df1542f8df08c3e2ed535cccbe20455c167c44f19ca9a4f37af397abed1e63a43468d0150571995d0ec208f46dadd92cc023487bb1b4cecf216d3660ba8e7391bc236d9825e8e07c25d2c41a1f10393c26cd0969303738e4b54e89101750354eb982e09251756353550eaca2e63eb2c1c69b5506108effe1b062e51a30d68d276d08c3b806ea66e3a11bb94b060f80aab2b06a3c319f79483606766ea936aa11ac4837ee9b349bc19129bb8f0de9d7c6cfba62189f6a6c4b8ba8fe28c6165b34d0c456fea1e8a3c7d60c7647b34f0e808ab67240031fd81c8725c125982191eee72f178703ca31cc8911be7de3e05e7e00cac3eb6e837337a4787947ab810d56afe8f1af14a3647a517b7c6e3341495011b637c6cd91c1da47214517e6a70ac3af547512d1a31af805c950ab60c663b5681721df8447f5054baaa65b841228544a42ae41ea382c98de95ac6ac8d89b6aaf40b86cda5f041b1522232d3048b645c1372dc878b8e2a14185f7b48c49ecc59cf9454cf64b410663a59911f056b402d17d2f36d4a82e723bbcbac5806d62c792952482e32cbe4e8385871fdfc8ccd452bab7d8556e81df77ae34f0cf84fc7b553ea4c54b61a55705c2e2fd3d868bac900d97a502278c8ac301330e4fc8b589097e0a9d96237afe58b27ac6d3dc32017471b058ad0c1929146d1f68126c3124d40cd3b8ce89fe4a826b6dfa671e669d491f33bc1e6bb4e13481d67a8793e1f54a50e2ca0dae1356fd446236792d4ca131af19ad30fc62ee3e8d7bc6f410ee1cc26a4c1d73cedd9d608fd189a082c8645026244c998da88ba496269c159ecaad3880aa5e9cacdcc83a5e7c7109904645422e81bf47f03b8f6bc510ebddd9c8d1a905b4d864258d44617bf4598851534395f3654e9f2abff9055c6bf2bc8f6a098d459d030fdaf25e95fe6ef12b62554a2766433508378154a1f159802414d4801a6ab48123a88b1b9f0adbd9508c282835264371303e86514c1c3445b9a241272e7d2322695ac9759a6980962127045b3626ca59d4b890930f4ff15be4e1fcac590be4240e757d6893b284d73065a13776fd288843a0198dfddc34091ff4ed733d6d01b89db5d44ca61a81c06b7788bd745e195d02c2d35d5183bc4a4e715d2e9bf5c1888935e22eced9f7f9e176f01349771adda6be0acbf68435f760be957521fcec2ee9bd3703e366e03f21a3072a96d033a81eeee238f0546b742d3dd7be9d95ed0db79216a9864ea59c3b3076aede77e30a8b3e71a316d0d8e5d996d93773def1f23868cfc88286c723aaab41e9920c94888008229a576abd018a00feeee37853e4a3700c1fd21171f7c2d227977ee01a0cde8d9866b77b3399f5bcb758e6fc620bcb9ef35644c66764a2fcb9a80a1e06d2de29fecb081dcb9815dd0f4a59901b9df463103bef6866398c31604f727d843b19575178d8dfdf2d38d6aa7be7d881fd617132d687f975e1121b357cd9452465a6d2c93eb18b57421e0b14513baf8466420c9b38a85bf424dfa4e582ddc1dcc3d053eb54792d1c635d708147e4b50710b5eff6e0b28d4cd18a71adb0b386a73fbc5f9d4273f5d3d1e56503a0d60a7adbd003d9eecf0871a45776aa00f745e9aba05c3ee95bec6946c3f605630f8e5042de83e0be9c7e538d22dd184baf71da5264e08e553491a9489ea7d7f23dea95b1275d5c4418a01f923ea2a89fc6b52568a580f295fda99857cce4ee5caefa5753ca1710ebd4ae06cd2095c16b2bceafa17ca04313710f2f433cc1bf1059b8a52c0f5b1b4881038da545012e7ea469f066bbe465994bf525f88b37335669ea782b1e05a9b92fdbe4080e710aa226a91df45ba69020db5096169b32f4c469d4c0f12654f5be93ed7c7a6c96e585e2b897855b20e093b28bf4a079bdbb9e66e91ac4ae811300c9a0f3e521efe42a338c8703cd76982034eb189acbc62361af7405c3f61e36fc07962b3b3833d3046ed77a4582b8a51a848bc6572e68cfdb9309955e7f42de508692872c4926b8e69a7187ab71062b89079cc7e74b0bc0e8bde1e30deeb70da141db6f31772488ead129a706424ffbc4e68f8c2d36b5e8b9ae2b31077735fa67540e405e08b46433d7d451b1436b107400600b958c280f64df11bfdda356c9a3bf6147706657468fb8beaf06904ec9951431a4b9af90b89a20f53a903ef547f01d89e420f1ca7e7c2235329c75a4190167afa4c7a8e39069d2983bd6be9eb223643337ae0aac186aa77f5e7cd717a98e994c4e269850ec9ed070f1df74ec7ffd53fa254be061afa26903cfcd5fb1d2bda3886ffe7f03cc5dbf58a067313a5d843e01056a640f8547d67e8f092247991eadaa7dfeb64a12d5d06a42708e5c7f87e3a6cd6c33980c3a8bfd03814fd2ccac3c2e0fc4e3c04d7ec7dca8c88be153b5eb0b4e559a212bf49e206538423eef4ae41f0f6b3182c0a58c2c1b7de41fb9da84b17f25732a34df14b805a242ca0917983c1e364dec1274dd4f2cd6e132a0ebb5972e9530e490a6be7d3deb16ef07f04df5bbaf79c0908fcb9664392540bea87d7e73d3c8ccc2e1cd577ab68eb7f293cd8c302968b50444b1ff0ee97fd8845e58b184fb4721f6ef46b121d2db06ebdb4bbec5447a8682fdb22f6df2094001889313d9fa7e4090f2338db0efdff06f510b9d291408878882c6f57431987cc5a931430e6f24942e1a60fa6f9dc2ba0c1c40a8f15ec56724a6ef955dfb9252077a19661e41380c6bb4ef435094b8e9314862a2a251525ad82e6002c48544d01828ba32496a3edf791b7b225e6ae16746df1a98e596d607c720ed1e1c78732b43e10aad657965cc8c502edc3b4ce932f093af16db9a588a8c97342848f27adbf6492ae6b4919ab617c22191071ad022f4abdfd361e9b843cd95207c166539f68831e33f202c343d86ff0e3930615ba522b73d89ed29b4c6ae20764221f519419a54b453c4d1276500e94d0117c5997739d01ade73b6f1b6225c82481d94a0880bee5d2a9118d14295abea10d0a7ae4a760e48b74c1b314083a29522748258d7dfdc350eed5fd96b70f6fee62de6ecf8ca6b69c879b65a52bf11a0b4e92389c612c0ac25fa16e53081f622af84f2942208ad1edaf6799b7e4fd73e255fdef41264261dfc6f0cd1484e7e5999756208bd905ff3ba8f55fbba1571b45021ff178e44ee6439ed601feb37cba3f4d7bf22b3dd25037fd8fa26484004d3aba2883c6ad00b82060119e815c61342a04fa15a43cf3c748dfa3aa587f175105c75c20403291c8e1e75f05eb291a38d0de6921c93af5f0542e6e2c6fd09e71b7a86ac8920d5d4e2eae19dba90c7b0c466f34dbfe3f12841e104bc9ea159626a0b5ffca717bf0cca474d536868c8d7cb22725ab186900ac40589691cbcc22dc96e1f8154220d3dcb186a244b14934d49b10885773a9b3ebd7396a05f5c2bca700bb5649e7a3b3b97a0cb5cd77b5b0311cdac2a20346975a8c4add1dba2ea0b7a6b8a40b3f5120f5ea159eb04786efde4d65118fc086f4a5457bd0d0576f25dc047cebd70e51cf0eae4e96ce38c2bb0c931f7f460cd9642e57275e77e4366368627677ab19c30fe671a019049f705f93f50d77f322113c9c3112336167dcbc89ebbe9d1262a8de74ee34250a586e1dcd53d29742ce3e6b014b4372a953731f3168505354bd01e21e65a25356ba495de44e0507a784a8fa07b23524ae69a9da5c254f584a37db1f62b015dce38dd851f78fbb84d0c3d574eb981c740bd5147b64ccd06e1058ed8bdbbdde03fa58700a6b0068e8b41e5e873a9eda7ab3079dcec000ee19baf98ae52d19c9d412315a77531518d5a9c6978e36b6128788860671838717d7a8b91b0b3b1ee563b26f88518acaa9cbd1e71ec55563bf7744d46d7c302c78278b736998d93c25c93f55a2e0ea32f280b82a59a2b9cc161c65a507822012fffb6c3203072ba727081e54f1bbb6be3ad2e3e7ad5c4a5f3f0cdf5e103a10a74e187e86381c8a344ad6d8dc357c8c60de518e2150dd8610d19dca38461bca289d84b5e01b8443e77ba2a966f26f7c0aedfbc1e2a60c84dde040db9cd9af27c76c9c46615e912d6f097cdd4ae016031578668a388b3c4c808c9d8737a03c6a5fcb3bcecc886baf314be36dc049bd37d85c5f94e75f340b4621094f490e04ca55de39be99944767df5d262ed0899f3a137717ae986dba5bbeb2fe115d2f1b614e1d852ec915de1af4a731f5c0e37ec7a7987a2718798278df804356d85c0064ba127ef49b4347c34913ef0268d7cd8a7ce9ad7f98eb2b1235ec8959b9b0b6db6c69a79a1980c5607f824da17510b1d5a639242c597a4ea6149761b89d622a61c766412d806785f4eb27c8ca408317019decaba9111f1bd493231f6552a04b1acb774b3e5bb375f9342cdb50013817bcb3261fe7a09f9b6bc67b81de49238f5dbf7f65aa6b16d43539ed8353390d7a08d82df154e703ae40a48f222f64fcaabe8d85b80e0233efdedf2a3ab50dfdf2ff88f6415965e8bc941d7a332238f46f0640a681015b17b0debca49ff114c6561dd9279a6b0bf9295a40e24ddc6c8b91bc6c5fab96347318e6fc0159b9b0725da829e24d37c0f19398be8344838d06635fe9962fd5a38e713636bb204c0fc628addf3b84f5459478e513fd3f8205ed439a288526c95bf850cc166d1039688d10a3ed14fe0923e85192dddcd6fc131c31c058c10f4c3bcbdbc27651bd3f570da1faffa4399d4a501114744376100c2f5723784d44d46e2edacb9f0bbda283d64858050fcba7323b776896b118b20dc07ad610491d29410a421540dac6f2227ad232ee132a2f3ccfada0d25d0eb3cbef4c6ef7b68b357c632164bab0e584c89ebdb7570d5e223ed6e1531da2ac84f9c415539a487d0062e97abf7e44d8874675d327616e55954edc4e96eb2ae3ae1b0d37453dbf0c868d5d5c1b08b5e685a9a00467ad53bb94d2bf05159a4527ee922fde35553c74ae277d11dac9f9f2adaf18316231cd394db5c34c236adfffddbe67ad53f93e2953da2679f45d8dd2ef6aed170559b48a650d1ae683295f298dad7657a87ae49f7a420531d2d65664de6248c2cfc93bee46ca884e1c2e18d299f1c679729d328dbe2e554bcbaa48f4c01c7d79adb1636986604421450b0984397a4c252f57f1cab05ff38cd5cb8e4eae7cfc919c5dc74013f4b6b32227d91b708f81ec5427b6df1bf45375247e233e95d35e56b56388a771adc9ee2057a2998f69b6768e8b6c7cbe848c09d0a32a450737de423a1e298e05ccd1ebc9ac3114a2d708e48a21066d737012ac4312c9d5948f375cc996f67ba0ff9c7163b6296d45a938a38944f5675c4528feae9bb66734c41ebf336770c121c0529a410a08d03c8bb05b5f1841570ac56f3d15960ce7e18b82736417348c741af45bf00117d3f61d934f5a0b2ba065bf70bcf701a2379f5b4ab0911d9f622ae615f36bb85889aa6a9579df08670cf32642260276765b1efa3161e49e80b4d7c72619f9d65063a7a29b7ad9af18826c49d9bdd108401006b19f8fc1df20be2be1de6b1d481b68c8eebd7dc00b9740d7b110c1b9c282c1a4113d223991bcf9f2d1630c6141681f8d79e056e7b328a546c8da54477772879f705597c20857d6ef094996a20b873fa837fb15a80081ff11c7063f58f2032429498cfa1dcf664d006084170dad90eaaaaf1d908bb51b94935d87e3f4c1a0770988459fee53331d441e77430a19f7528591c5eb0a4ae39d7b37dbd87abfa402a4463c1f6dff8f3b0fbcaecd1ea153eff8f1727db6227fc374f68fbf2889de93844a1c54ae09420c6e64e453ffc80149c3d99ba98013fd8890d521b60861ddf5c8d7ef7f12191925bfaa5aa5cb7bbf145c1593349dd26c95fc5ce2c78771570a2ba9c05260a3f8db0dccf32220a47061231d6312a19457c5dc552b31090e156db4b987c6b0ec59cd6f2aa09f8be72edf59ee4ee87d481f092d7a20d9b06f309f98574ad1aab78a080b0b86e4b88a95a0b0de65b32d80c1565ba415960b1dfeb6c8a9ef01cce47ad7d503d69f56938a81b4577a1080646d5bf05a95f6fe80b9631117dba983a2099829bc7d3e8bebaf41e533ce76f465d327a8674302577a6c6c665114d8032c2cf0aa687b19911c8c3aa3bf29928a3c16fc80896ea15a9d188410c9834ba7d86a75621a0d13da8a2c84a30b1a881244e3fb249f0ee5460637617c018b4cd482198fe3f20c5f9d2ed059c03537ff118d02e9c1f6053361a506ce348a775eb55f4b8fff79112b4c9763a58fba07645fd942f4e31f4c50cd235f08fd05cbd6b25191f569272a121ceb70dd0f1822178fcf5500edc80a320dba4ca885e5df37c51a400b05951804fe61117c22119d1c3656e01d45b62a36c1397c55db796f8ae37fda39c18dddf9a37d411755cf0013b6c8ab0fe8e784f00ccf8620d4a1974d4567d3bccf54bd6687c344583d1ad7cb541b222959e3f7023c509e1bb64f1a688544156f5e3181828cd44661f07d472c39a049962dc240402a0270ba4eab9e39c89ded66dd4afd090a4a5feb4c5c3fccf460c86b773c194880e598c0dd6c73f0f81a6776faa6b0204c1fad554401369b70dec0a9e24036865e51e5ac3eb3d9d5b24558e5e46511dffd218aecab5dcc0cead763d5fd65e430cf1a37d4f4ee26de9d4eb723e60ed28ccf549b8a5f3187b9f3ea5710c2abc5679667a291bfb36aa52bc526d127ff641d767b3915476cdb0554d12ec1d2ba792c287ee53e2515c099d4f699e0755293a3baa339f2c67779ce26371ac91d58cbd110f42d79965f92b7fd5497add91e601e344e956604a3f4788b2453fabd97c05f488e0d50676e6b10be7f8e75c650df159300a31a4dadcf20f1204ae00867c25d4de1ae5e5b788413bc5963f09d8f7380971390e212f5d6c81a8da9bb15ba037dd4366606852c3b9622d573389adc304a6b85b60e91e565ad38f0c359a100a5a584393d3d57b9b4e2d2c940e8ac478412278797997e9709d3ab3928b0c906b9e69cb9262080853e1c8d0a856f9d3d571f9e48908e8a930a579a8c52314eb82e8d5e1e7662a4aefccb7c447328226aff9f4ca67e594ccbe4b38e0a6fc9c24459067b3cd839081e538b305723f4ebd37e38e73e571c9400bf9d1c73ae9101ed95a6c112ce330ad16e4a1933e510784684b31f86da0c98e335f1faea06964f9fdc8f109f14e89be56763c14b2833892d15bc97a44355adadcf0a624b73c69ff2a523d00b4caed5a22b198c3020d338ff7b5b079d227c074c7e3557ce0467b692aabd9112a6e4d2b2c8890f0e697838d629a6190a0f24dc74f4bf1d7437a2f10a6ed711437a79a763e4cdd3cba2fa8eb28acc2527b9c78ff9acb08e1072418ca9e1dfffda99bec6e0bdbdc8fd016edeefde8db653ebc30178dc5f8ceade90e1c091c5cb92e059bf22664775275c1ad81fe75e70f675fa528e1cb067f872bb0952add0ccaf385395583885c71139cbfb4b730280913baf92d706aa28067ff7b4cd3c3ea2e75dc42a6c9b6d1d0ea02cebb16361a8dbcfe9ebd73f901fccf040adf33c25520d65d491be1f5d7aef10f6581056dc186a4128d9a7d5b4f7b4ec5464bdaa95c7815ba2deb154e9093946a6cb502e0127fe986cc6b4fdfe86ad548fe595966f2aec2c47caf6ba7ae44f6b746ba0959da422b7b73cb8b0b4f0bc70c51272a4949a3f80aa5392037142ae42b08f7e9f455f395e94f3f03b74e534aede5cbea9ca44f29955452ddb9c964a25a13ea934a0d884a299d3399dea4692217e91395a2ce91fa92a252fc222464914751c75dd1b5a9fce5abce8776fc28df399977a8e2ab28857c954df11596e2ed50d70bdeeab600ee50282c80ae023fc15b99e08ebb095e0f77b9145f017593d4e49534a3d7e40f835b5e7de68fafb0186fc78fc078abfbf1e51770077201aeb4dff12bbc95e9e34b25b81395e00e6985d7235ae1688c236c9d546a798648537e92906a6053a6d1d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d05c147551173d72a74773a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a373a374a3d38fa60876fc79c4d18e6fca520b8f8b8ff3eff89afc62c79762fcc09d077774328cad24bb6085d74366e1e144b823bf4028db0b3bbe520d096a6a6c3061c3460c4bc410430da01a356894a0a1a191040d1a3390983143f5a352cd1c3133233302191919229021232606660c189854185229941828d4f665db4e60389d4c61984c180c8cb5254dbb5fb837fb22cbac17acad5e6ac5ba8061940b94ce2dec29a5d4c2eb1163ccc2ebe1ee58783de00fcda4a1b0a6599ba2b845b118dc9a3a7299d1dc1575a04ff4894ace196173201f2fc2d649156c7e84f847a28efd9814234fd4b15a2ac59baa958a52b04d146cf2670c1a944a5ce4c78f01b7fc6b704b7a7c4c89fe8cfe9e4352d7b68a3d2aed08dd9deae71c7d59e534c146b54a6fcec1230fa7089b4f39b4df0d26328c884c643442c5cb9c73ce095d62d0e7933edde7f439e79c73ce391f36b1515ab7397d7ead6ffe9c73ce39e974edb9730edda5ec629b0082920bb647eff3d594af6916da9e20cee3f0e12cf6d820d6da5ab57dfc36de12ca9759dea8e96bff3e0c1fcb5deec916f0f6b50fb34cbfff61bff2448bf1c48a65b899647006e13e1b9d1b31fc036318d5608c50ca5c717c35cbb28ce21fda6b4fb59b7f9278d67d1a0e23845aca85af4415618c576bf1a27b47d967b895d58b4d7cc36739becd1e730d3c9c938f651b9ccbd962838f278455014cb6bfa6f5c0209e69d9bf8771363dcc9c73d16b3817a3fc9eb73dfb37e9ce664b1d31635821841056582b849826fe83b807e21eed637e5bd352ee6b5fab9e51bc1ccdb778490f290503f9ee65c32124e741fc5d4ab813777c97af879c7bbe4f72f7e8da11734d97a579bae0ae33686eac38eb4fa32d40793feacf2db8a413aa23e3a37432e79c73ce396974f4a7cbb9a893645fe7633848f655573d839c8b3fe977ee7306f96a4e2717e31686decd4f923d0d0a728e3e9d4f27d5158a2dfbeda78b6edbd3ed793ccb343fb7dc3548fd4d7710ee5b357c1cdddbae7f210e12f7f6d78588637cd9f5a510718c3196d40d1f0e7d19b59a39e0bbfe7d55bf8743b574754ca493a0f9dd7caa839cd349e667b2cb5eba32e992aea8135df567746199b6afa43a483a19a2076e89cdbac4270b368899629cc35edb1896510cc35e9391b32c7bcd4a91f1769341b187c12d4ccfb0599ba5c890218320b4a3cb5752647cfce8240661693fa32028ed4eba60340c7b0ceba62ba599708f49462661300e736358889cbbd68905992fe79c271c44f6c8c7fe426d4693d8d70cfb194de64edb99e65c965d95265df283e25f2969a412d5c6249e317b48a5135fd160ffd2a5a71396c985fd1582e6ac39975dfa90e24a1fdb8ad2554f3d37ea279e534fd4cf2c1f02c11d6b53735a2d3578714fccd77f30ff64208437704bd60cb7647ecb18421a70060d0d1a1a8f42e94e8986d210b53d7e0a21fe8fc91dea339cc26f69ead7392bc401b76dc35abea59153afa21f1323374c43d4c36d460a8592337407231e82a2b4456d955bea65ea6bea9b54fc0eadc5a9b9c1c86c0fb747e5b7336e6d305a3a072337cc8c8621965ad65e6a52d77933ede11e49a5ee2c1eb2bd9c91f5537276be639ea66a4c0cfd6e0926a19438d88f791e4ffdbd9af64f0392dbb6750fb97b56dc23f3f5797c255f9352bed6a46a1fce6931f5a89cfaf91ad0ac5f67501635e35133348fa3ea867a38e6791c5567fcf6d27554d6feb432460b99d2a23ef534c87c3964be7c0d08eec89732f53215f7d498c7f090d4a4e9999f64d31d9c7888b6e7d3a464d4d4a4916ad12bf3b4d65afb31292da10b05f3aa2ce367327d1919d4f378955e5f563ddf7ea7444b6921566b34d9a35ebecc43ab53ffba29234bbfb131557e8a090db7689665b8e7dc2feb9610e6b34c2fc19660dd126ccf87401bf551befc1a03f331f36386c07c7d18547dd4a772c764a3b0257bfa6c2b65f6b40707c9b63a6b8ceee446bdcd44ec09a33526e00e148f0f5fa15ebb5a131ee734ad6df8e7c46ad69cb0698fafd4b29c42054694d2f56ea2ccd9325f48efda78cc9c1a5daae89a2ee99252da504929a518a55677f94d8cd23a25955352576af274812526212521c5f9cfa6db7694ce1961f38774d27f72734ed237e1fb8473fa43a9e39194aa0d618416b832ec16bb45165534bf41dae4436afdd57f1a53482ba41042e86e02ec51b0bddcc1a4188c12db0264c9371f7dd8c35e7d533fe9121f7938d1e30ceedbf7f46f3a5e00ecf1c71e5230369a20fed8fb3ffaf3ffe54efee03e1d5b0da2ec6754032739fb1905e195d4d5b92dd59de53aae46a8bb2dc69bdf5cd4812cf99bca68cbbf795b065080a77a46cf39c7fed5a84324c61a6bacbee95f22b56e9b148c8b3a44de8dfc5ab74d059bd834df6e0459b5cd572fc9716bab4e00d714534a299d26786e04641263fdc293c902f9dd128f790f5b5447fa9aafb443edd108c9a4d8cfe085b875a396d1b7ffe07361693f232e28a97c25c5fefc1a56a394d22e530cf764126b23542c2ad83fd5f60ce33085cc0d85cc5d2b74f78983c0f777c71aacfbd137e979297d2f348918d319fe841a9dd5085e4f1066b8a51c723fd34b7cc359ab5ee23bfbab93643f9554596bfd59ef09eeebb089740d0fb9bb5edc837dad4d5c433db3cc5e7b1008ee6415bafc99803bdafb12dfa721f1a43b9f5aabaf4413e20f7507278df6f26fad0f87d48740fa4278718f3fd49954a26d976f714ffd24a75a5dce2b5906a7869fd13765fc36dfc7f283b0d6aaeb6b42340de14f4bed4bdc937d120c6a3b63e241261cb7aea66519ccb409b1ecb444fb9be1eeb97fdf2918fefe57d76a99804cc0269e99c0d313b0e8653d6fcf3c1cfa4c98b1e1669e082ccbb22ccb52daa1b0bda22b4945457c459376f6f47ddca01aaaf11994eb834b881edfd967dfc1a19be5f9c4b66477f595683b7b228a803e0f710bfb0c671955c439d3677907e74c99bec139d3d32fea5f99650f87ae5d82dde7ae7eced9c7aef45596b1af36cb32ccd62ccbb2ecdea7f1e11cfdb783af34aaede01cf5c1571d9cb2e9532ddbf43cf46956ed4f274cf4b753d69ca3ef4ed84c0fa1c0285067534c0586a6bb0dbb1a53bd1b8ae56014c32cc51e8f8d15e5d4ba512eeaf0d83cfa188659ecafc56cc69aa6d98c5fcb40d8bf42586badceb256ebfdccda5bdf7e56df39ccc639cce3c322cd4d45d0c2cb3eed67a48514f643dd379b6c8fdefb3477ec3a1632638cee4ff3e37d7c04ecf83227799a26c80176fc03d00489638c01bb58c10bf2134cf7e91e638cdbf423b6a95f8331cea9242ec528837694414c26ac4fc0f7b21d6acfc697a094c86b463f024412124a2916e9a45a32a7af094310c2128e5064832934c19253903a478a8d6a1dd9c33f127532cdc51bf8524a2939a83b586d964a29a5941e4192740409162ed8c1c676c759b727a0c2e61b892f7227f1b36d5697a32848403ac215479002162e70c211998474040976440809291aa194124897a02e50a691d01196b14ae4e17225462564132578c0842248f1841596175c6820e7c532f7c3c205af24242245549d0d8dfd8ca4bcc05d4a29a59452ca5ab15ab35a6bcdaa75ac62f48b2dbecfe884bb75395be6d427e516a896524a29a59452ca9b65d9fdec667fb35aed575be5bc72aaae812df8c09c9c1a6a1134f41f8bca7ce9d2397f88df399963bf3d709187e3cf13fd7efc1a2f0ad531f17851f7c2e8afa5aea84c83c1a121b86dd74375a883ae5a9fc7a193d7033ad9554322a8c0d8b63054051d38147390fe4815638c9ccd9678036db2acb5fedba8bf40dcbf2ec4bdf7def75c75275128ecfd5e0cf5f71d156f4445faf2a7e0623fa329b2b813fb1951b9b2b140140af5b766215028140a8542bd670ce5140fb9ef28f928aae516690d6dd35d7cecefcc3cde7bfd33edc6d872ed6356a2daa76ce26143967d7e807440b1cd2b9bbbfbc04421a194521281014516c12d63d0e9ef918f79ec31bd3dd657e6f835dbc7f6310c8bf263263f6679a7c4b2af4f62768b2897b6e097735724114b94bc2487e2dc8ce2dc44d59a55999f6cf279a2120d26651972e1dcac1a6a716e6e714e4a1da340716e4e29667c4d8faf68fa2e067de4c2579a1740106d10e68338323f466b1f8a412798dcc5a46250cc8e4151071ee9930ebaf3bba8657814af8db162d895b9ca192516bfca39a38e49cec52e5b221751c020e7e013288bf6bbc124069a6670e73f9b0ebae615e8a3026373e8d01d3a74e8d0a143870e1d3a747777ce9df38a61980442fe9d42482925a63bf922fd29657cf933baf4e85086218ad2be27cfc1f67607a3ec30aaec67b474b479ec67b4e4d23cf131bb65c6a983a7729a126d0f83d3ca9d556c99dd02c21a5f8b134aecaf948b69ef2fd5aeb47fb394fa595ee2bb65f5bc346287d8dc58c6a6a4d3dd3fc97cfaf2e98c5309095fef11ab8f7d7c0c9352ffbdf79594faf06d20d51cf760da088d45853e1ceae00b56d9bee77b213177f015e57c52cad7f32e7c79cf2b352aaf4cddc1211ecec13fe299587c3fb3aa53edf778e27b3fa1f69e7c53bef752bef7b4ac99c68687afe043087fbeb60ebe12ed3a7e33c77f593e75fab26cd74f3bc27f7e620cfce29cffec60f3efe097ad677ca3200055d91e05c21215271eafb3e1c7bbf3700ec20dbd86af608435ee496ef82edddd1d4a97d21d42287586359c83bf6916346cb1c98fb3d3584ac50c903e470451ff4d8a416c3169c3fde73810eeb3e920fe976113bf992f359ce115bf990f4b4f1051624fb1d410c8b9b9e7852fb3d882903ad62022d4f095c5d671187f12cfc24f034619a56029a594d18b3da34c815bacc52dfb6ac52494724278058b73b3de373d8cf0c63799200563332d21c2b9f9f7067183983e4c88357cf9cd4bda7ce0b179f9eafe7ca9e00bce28b621e4869a73536cf2a304e33e4bab8cf56ada57ed4ac17fa7fc7aa5604cfdc88fe27b9628edb52a6925d56ab6d23eb5d6d2b7bae21eedb5a9c9c71acd566bce254569a1f64fa355b6fb4ab46d67c5aacd6ece2aab3558b336e77d4db36fa5977da52a26f9cd95f551ec049693de373dcd597d556327d809b883fdac28d40935e70943611886a9362d443ead3e843abdcc9a66fbec524a5ffec34de08efca92d7675d8b5ed84f98a87d818b97442e92b3f9bd8d3743536fd4ef6cc4f82d1ec7e6ae9d2a7a1bbed74b3ada8d65a6b3d690ff7d8c74d2c769299f0902b93b9fdaefa86799b3becda198cc5d94b6dc7a478735ffe53615ae2501f26264d9dcd37c9b8ef43f7effd19baabf76bc64de00efa5996cf136772ea65b2951193bb989b63fc5beb49bb73ce39757dd4cfd34fd4fd53bd9ae6f4f4ad133f7a17f530df79936d2fea377b3f096ade87d1a86edbd809b873b5b72653f62ca75386bb5a5ba73de5b7277602eed098a469dade979262aff92afbfa9bebe0af5a894ab5af490b997f2dc54ec01d54e31cbbc41bcd6698d5b223155b1793ee777e849db8f9ed13765d6b3f72b9274a35286352f4e2345d46402659cc23b836d3a34bf920c4fc3d3dc3864dd95e739dd7a54674f7d7e3de6a83c18ac7bdddc5bd1f8ecf82294ea49452c297923ea42dbea4704e0c4ef81e841305db94d3f6bc3da558d5cc0f90553d227c3527e5d980706ddae317824e1e1f40324a4d312ae7eea55f6ba514088ad9cc3e86518a515b316bbf52aa65866573764ca08b4227b38bd9f789983fe79c4e6cf73bd59c526c3490a0166a31bba453c36fe6cfa943e5dc9cd813617f5afbd5d2aa594cbf492955ed4955ef66523ae9a4934e3a298d94f2504aa976efd5ee6bd7dacc62b4528cce0ce225262c09a20b1390901029e203d2fbf8fff7c10384e71521212111e10182540489c812228240ea52a7431f2810420861ad7fb36c24be7a7fc4395884c7c70d72d6faded6f3fb6b709dfaf373301a36ccd28c52dd699a474ead5b86599e38710bcbe1f18a64cd3ccecd4702c4112fc2067ffea447dcec9fcd556131c6a83bcb45c543e0cbaac559314d4a29a3c681be76b166b5482079d281544b8cbe945b68b6affe7dab255a2e30ede3f3b8dd02eec0c7709471d3e46f76cbc6a4bc8ffda6837a08a5122112fb88699afbf5678c117bab05eec8c7f4b45bf6cc700f562106534db5664094527ab56a733c3d6adaa8bd70a83d84f1d6aae9635f35ac8560afd578b1ac69eecb9f9d92ed3b08b427fe4ef6c44f527754b2e9533665e93755774aa2ee34c0c46e61b7803b6fcf1de3dcd8c6ec167047e879b193c49fcf0b08e445148a8f4525dafc2a14759e16dafe53db2de08e18e9da8479729fcdfb220f4b1122ce41f964f3413ed9a02bdeb87625dcd86f99b3c7ba2addab2beadc20a2ced59ae260c3fe8873fe98460214751e104a7ce55dd5b69223d1fdfae0abfa6e8790bb833e49e477980c4273dd622651111f6a8c1ab502c2ba6463efc5a864acf61d2fe06d1c35edb5d7aece34938d69bb057c760bbb654b2617c2a48de53887e59eb7913c1ceca7945be10f5096af07dc32d8bab71489f05597b3b13a77ce0e40f8ca1ffb0b9d388761391bb3af445b698f7d0d3c7c05e14dc53887fa9b55cea1fe694dab7f65b5f5de0a0476b3ecabf6cfb92b9db3fa6aedbec6133b2635b3b57e9635205bebbd99aece699aa629c239ec1f74f90aeb8ec9c61767e8720e834ebc48e52e66a39e082c4e273614928b9a526cd8d3b05f1f22412d1babd57a94adcb893a9adf608f61183663b055ec8bb88fbdcdf48bed7ef6f6af5f591ffb1d7ca858c52a56b18ad55a91fe48f58a361e58ad58cdd918177588c88937d8d75ab3b7c9f23b574d18e37fa6c71988ecefe3373dfc4308ac336b6f9669d95ffbce551be7eab3c1741298a441d31086768c71cd94614a712bc37fbf8b663299b437695988394daf6521e69cd344dfe7fd89f57bbe54c3b98333ade94afe844dab5d955ce45391cf63fb34d11cc3d3bf1b548a4adbb67a398f7b13f42ebf6d7ca4d886f62eda959c8b153f7d932b79175fbdd8fd8eef3e3fce4588143f9bc982c595a494524a15c4d34c8d07e171a6a8e344783726ed479d1f757ed4f991ecb25d3b101e4efc2e5a61c797514a698a2dd84ea8e7712fce41dc33f32eb13cbdf84d97a2d7030bf4f9712ece2e148a8bfaecf854487bfa148aafe8d390d2459d380df2d5f3958dd7037f8c2a8f7e95eedbc8110bce7047a52faa6d8bdb7f297a38b18bec2245b09994dcf3e9b7dc45a33804cc1b4c4ade7719241cbaf86ae6695ebe631c54efb38be169fcd4dd0c2da76b1ad3d3985e6a9ac6a47f30d9a6976fbaa6d7bec7f4dad55ed3524bd367b9d3fef432dbf057f9cfd4d86cbca6b9efaffd7c9a69ee9b9ed25cfd83c9be6f7a0dd304b99f7dcffd0c679f691fc436691f638ae16d68fac5667a7f98bf18c50a88e1afe91f5e80490789e14d3a490c7f75921a5a0d21dbd3afa11f8dea250dd7dd8c999733efaa99f7199f6efaa9b9e9e9cf197fe95afa945d7ce53a521a9562971fdb5f7db7bff765e84be93388c7bccf2c334dba63b283d8707683b554ca7457bb9897f94f4964846c4164fe9a5e4646a3549bf493c97292f9f76ff6329b7e4ecdf4d264cab499efcbccbff3be8c39697ae64b2d657467c4be7ffad3cb681999bcbd77447610fbf451c957910b8dd4a7f412d9e397c62836e970fbe9976ca84ce99c74ce39279d94ce49a78f73d1579b5e22f746a16c3441361ae579fc9ab27429a594746e196e29e5944a01357f44103f7c3671909aaf7f6bce7a86ad26d7d4d4e8229508520eb9e68f640ac5dbe15f24db4c75c83402993a71ce7f0b639f82a2e95d47e7bd53d029a8550364f30faf4386c017c93e7ccd70e7c0fd35e5774e7746c039332cabe1446fa018c811865bb5e61b906b3e876c73113be4f7b89c6fad21006eddbb613e6a30cc535424d76f40ae9f43aed5e70745320fdf80ccc3e790b17de98fdff85bdce2e12f0f0fe3780ae4ab0c54a3e9c8f0cf573d7ccdd7e88e87bf99874c7d76b0e5efe84f4dcde7a8791cafe1961255abea9a9aafb5561c5f9395a876c691a98f7339ea5f1a362339bee6251e52f339f49c92e9175b8d91e79ec72f97e7949a7fdbc81b00b78ce8193423f80626bbe61f0e52c3e939a5e6fdb31e72e4f81ddcca9143e6d59093935b389ec7e94fe5e1fd29900e6ef1c0439629193e258336428543b571681b78f0784076c0670ed03f327c910c770332dc39647a376033963db06bdee32340fdec6a597e3ae41ddee6077c91fcb603f2033e874c7703f2db5783cf4df963083ccba65680986b73f1bfc43d64683534ac5215c90df81c72ec38b06bfcddd4c73848dd38fe62ad48cebe0139fb1cb24f215f25a97fdfdf01f5ef49bbf77ebdd9adb93536e0d6bd33a6981a13deb622397f0372fe1c32dcf903feb8ba71fcddf21b011f0e8ebf9863f049537e1e9fe22b1cda3f47edd6e839655f06649b27926ffc90ac3f8514752e5210dcb15b3c553fe847dc4f7dec16b8b32df974db45aa760b03fe4626f23a0ff9cf710fd1d28fc3c7e18e3c823b4e415c8e6d29fb5e40a6581e4e0fae781c0f76f070aebc9c23b2f270727083c70d4df1706c4003c8cd40e8e14889c1e360f0828703e5897350b0e0e1ac00058f737282876382269a3c1c122481c4c3f1c721d39f87e3cf68dfaff96adf5777f5ffbbfada3bfe51bfe68dd8f9a97f7e1b874c45f070fc6fc874040fc75f48a6473c1c7f05e4209902c9b42753261e8eff8f4c49f0e8130fc7df47a6ae87e39f804c4bf070fc59999a0072fe3bd31464aa8287e38f804c831e8eff01325dc1e3fc0d9069140fc7df864c5bf070fc773275c1c3f1ef916901328df270fc79642a45a6327838feab4ca53cce9f0099d6e0e1f8d790e99487e34f43a6541e8eff0c99da0072fe03c8948a87e39f93290e1e8eff8e4cab3c1c7f01649a83c7f9e364aa8387e3af23d300646ac5c3f10740a65768d1c3f18f0e45fdcfef5fb3cef68ac7bd278fe08e7da1bec39ceeaa57f170fc73e80cf78d6cad789c3f0eb9eb4ef60e93edf0af32ddcedeb58c87533f06194960626ac42849c1c880b9002a1593c2c086a24165e0b4bd3ecc1684697b7d1a2721b069330d517f06d6c0d54cda92ece2bb019b691907eac7d80e60dbeb775925826eafaffa885b17f3c09edbab8f12f403536e4f6282403e9cfad4c7919045c4ff09813f9cfa11b7f25f8f336a38f2db36d977fd24f5332bde154fe747864fe25932682af5ebbdf11207c9f28de7815b37f40c5bbd5171903af111e7fc7164b86d7c77355f3fe7ce889da4e6b3b7563c8b05b45abcd5bfbf45721dfac43f5314502720e77f2353251ee76fabf0b72fed3d2503e63bdf329def99d4cbf807f332313ff3bbe66faef92974a37beae3ab9a1bbabb5fbfbbafbd7e9ef839043a7746ecec2f161dca374df7795cc857af3b37023e9c8a15c0a4476e21de18fbfe14aaf1bf7a0a39e7738a7331e48eaa60fbd7c89d0c4dee6068e40eb53bead39db62a777677d4a7c376675f9d49c6ee2cf5e952bba33e7405fbeecebea80bb6ff491ed128b6bf467d8af0d5d518804744409f9bba1fae522c3c6251c1003cda738a5e128473fe5388e2a1ed43819c73da643bd5b2755348f3394500464026358c804cb00c2aa105153ef701fedb703a70147cb8b7d94e7b0b32f633ea828baddacfa80b2cbbfb1cb9056d06edc1580388981f734f23cc79e7a532fb80875493668b6022cef963f2f5a895be1ed8d4f9c178a3d90d1888dd4c9447bcf1c739e766afc7ec31ffe168788896f65693aa5de3b5ef6ca884a931b63f955143dda37b4aaa1d35102e65175fee6bb32cd3b497da4b2d88a6bd7635184dd33a24c7659a868768c5d7b07c55fb4ef5362ecca9c14d7ffe8c963232c6c4a86de8fcc7180fd1b28ffda6d5ef6c74e4c8bb53f0ed20813bba9881c7f94d1463fbc3f7c5f6b7c0eb818147027794cc903942f545296ad7ae96df1c426239eee71c0e627f6a1a2fda9fb1999561add666ac4ebfc249e73369f2dd9396f5c8e075194ab857d6e03936b478481a6dfe75701cc5626aa85455d36ab5f415452e3008e17cfaf4319b19b6f9f2f51022a49b1bbe4f29e5cb996d767c8923f220077b9148ccc209768c5f63a49452ec0926501475010a2e40780212134b5aa862473969d2a51c12a5f4664c3051a2891a308184164869810fbea08521cd39872149dd016043ce0777265541459744a43d62038a592de6943a2a1d818d4db1a022842c36a5540a1ddc112ca2f8b1cee5703ae491ee060cb438e79cdc4c3202912866949c0e8877c79db2c8911b112d8c2a362713929438429655c1920748910f204b43511675811da1414623911d908aec4085d76cc2842c62e4d1c205573a105db88e441d9e986404a74e440bd7098331c60806a478f3624624ea64fa48133f588209b250820a2e5040c2174c88555a2d7cb62805a478f36a13095089e20ad70cae80420abc2085a2298f965257482a229ac14912d901a9c80e611092569060669452ca2900cd9973448e9528c410e59c7336a1896c8293263cd9549b48d4e199483e34a2e40152e40348185c78c2f03203110b4e710a4853d8128f9032a0295a00a9608c2f584102172fb0069b6f624828a594984bc99313813807a116b6a0c10c9e90042f8696ba904455f02be7a494d26a55d5db8cfd8cba88b2b1fd8cbad8418c4a74493aa28405cf0d9aa0a6dd2ea59454488a54508a54e812a9b014957ca212500e9d3973c8c7c78704515c5a7378e4d4c0a3c80947db1fdbb29faf30cb83a772ca29e9113928728215720a69180f90221f40ba787551d4c55117485d6ce922a90ba52eba48201a5d4ce9626816494c56e9e3e3e3032465952d6324911d908aec3086cb6f5e5085b209a986fec515e56c1fe3c86fde2e6a82062dc860c9670a345081185c088318638c805e3133798014f900f28a92b0e414c67d0b4988c2f62d6664d19296231e242cd4c803a4c8079057d405a1a44d331239df51d2911b78c134eb0ef5683fa31134d9fed83bda741e51338224be90133b82ca39e774f9cd0baa50fc468a2146e5a812541ca18fbc228c1ea162896c3e9b3d146fde9612cb487608d2e2a58b137ee04305cbbb2041832e76b03d22215284278b251c5cd9520b9f85160a60042a4350c11550b08417ac2c3cf1aaebd4c8022a4f3201c21ecc408b000a2daa28a51116453d50c14e6d7162b3ce395b00858c1d73ce2925168e307757c37e4c5e3e68820a0a9e70820628c0c29c73ce39a751d07624b8fb4d96229ce004252041d214aceeb73b141f1f1f26585c76c76dcff1953f0f1f44dcdda5bc32853d5fceaf3b9862cfd77c8563cfdf76a0e585f62cc27e463c00a3007b7e4e0d3caea062cfb78107893ddfc77c20be7a46455bf62c7a485264ab2b2f016c510021be2b643145280a4488d203a12bf4780167648516dbb7b8e2823de39f54412b269de8620443480802129660032bc85985146c80824b065513cf39e79c33d299d158851635e2111446bb7ab20a25b088305892c0092975f022c200a04891032d98600a1d78c1624b022868d8667635fc146b22af5edd6dd9d48109648802697b61b11d298aa40bbf0a57a862092f78c214545421562cd6fa82a26767b3e79c44629e1461cfae86f77e03bf561e73ce39e79c930b02d87c3567f8fc81972658d1831148a14404168fa8f3f106fe1076a752da5d0d7bc29c2b6c229d92ba45619bf3a12ee21c7cb90311aa8bec6eca18895e3d2a8d0358ba4fc7a6bb259bbe126dc76a5ff66866adeeaaabb61f2a98aed963fa65d97b26f52cc2e61fa53fa92d944c5c9a22b2084d13691e0043c07a2f9f094be6a87f3ca31c4c810ff7649a1fcf2807429b27c6dc3111021ae520ca36c299b84321c8a7617befd5a3bb76af51cbecb3caf59f58e0f693324af9f1e6fd5339f71cbeefa48c52460c42973427de3c191f7c3146ddc5f83531fe9bf1a0bbbb3f19a573d1dd9d4228dfebd3524a39253dd5ba6defa17e917a7ead8a37ef7d8c0d9af6fbf7dac381104238e3e15c8750c3086b8c5bbc795f1f8cd5e18b31d65021e3bbba6da88edb36d3507f378d1f8919a899c7afa2f1a6d3cc8ceab599bf19896cc6cc8c4945636606e3b755db76828989e1f6844961a8192afa3130a7146a464686fe8c0c19999919999f9179192d840c2d6589984702c6b4f98dbf96d2d8514a72f0cfc7269dcfb1d41329a59452fe7bac1a619393ce79edc4a00a52ea524a50d1f645680a2e4210570187a884a1c5cbd2121884f6330a03cb33dacf280c2fa84cd9cf88ca93dd411fec6734450b1195735ee405765a696db4d6ad85f6d96ab35c881d00e859d83f1a6b36e10ef9dd69c713c0c3f1a779c2bc03618f02f0d05911a0061a6618c0a473ee98908b37fedc8ed763be9c120a206e0ce7f5b0d8d5687c28658c98aea19a11623632ec789d0156db5f86d743000f47c70c991ea86ce0f62300c7c9b7715a8e6a276c8b3a37efc6ff710f870518d6f17ad0f7e7b8a8f358a91a6fb697c2302447bd8009b59ffdcc9d06e10d4c6ad63029f15fd4d5396f725f016f6b1088dee82c853be2c307ff419b0db7efbe9bd1a12c176f68aafe01b7f6f7511fb74fbd09eb1753fa4594ae3a52cfbae5aee6943b9589e3e6cba284db561af9dfe77109775e8dffa0eef286f24ddd416ecffa381615ffd72532d1661686f064d7ec67240431f6a56143f20f572da68c86c0c5c6d9cf6808538a8e20b47fbfa22324b1b40493ee16a8f9683fb78bf6b2ef977dbbbca218e0dfd6b8658067bda61ce0dfceb8758067117d41c0bffdb8858067bd9af0fcdb35b8c5f32c222ff6bf6d03b7f6b388b8b0feed18708bf5acd79304fcdb35702b01cf22e2c2c7bf4d835b3e9ef572d2fab769e056eb59af9f1ffff60cdcfaf12c222f3dffb60ab77a9e45e404c8bf3d835b409e457425c8bf2d835b419e45a405877f5b066ee1f02ca22937fcdb31b875c3b3ac7811f26fc3e096906759f972e4df4ee1d69167113589c0bf8dc2ad083c8b084b0efff6865b393c8be86702fff609b726f02ca2273afcdb26dcd2e15944518afcdb18b78a3c8b28cbd1d670cbf42ca22aafc7dbfbe216f61e83ad9b5eb6df207656445797c82edb93f69d5c38578b2a963a54abd42b75a85641fd388702da969cdbbe54a972e54ad1d111cb8a46f3ffb60d9aec655fb6675efcc63f55b46d5c2755a4f215cd7c243f9f66fe63fdf05d8d27536fa3fe90535a720a2968bb5f4d4ba4b4e4145294ededbbf1b75b2cd2cedc055080817fb175292424d48b65e5ca95a3a3a2a32bb01c6d4b4c6a56c5574c5856aacc2a555cc7a4d4a5ae6c8fba432dd5ba6d2ad53fc7e56c4ba7201b248394f88587221eb24bd00675d565a3d42ab54b477de2ebb565fb979396edeeae72081dfbe29cabaa4fd537ab1aab3f2dc05e2dc0ae602f6ca8cad66151b6ff9531b8e22b0c8a7355f08b89dd820539e7d81325da954fb6fa8f6ed94eb56ca7596ee0a7a3eda72cdb4f2f796fcc322ccef9e9e59c9faec04c774c5eadbab2ac24f98a6525c9b9b75d7b1b4afb193961cbaed9cfc809af6d97a20eaee2378eaf6cffec9d2054a445cb26c07e455aa29873fedbd9ac794e9db3bf8187c4af36dec6c7dc69da86f6b8957d54025f813b70e32ddbdf46eef0d0632edbdf06fe01371e7262fb1a5cf38adff863c97c6616201d996bcb606359494ae2c2b2c28565c58b2cb3ac70b1626359c934cb4a87aa75085c76cc6659498a01c7c0b2b2c5575b585694923cfb7a6fcdb2af590d9c428a3a78480b1734f00d6f578d7af9aaa33e577cd55d245491af58565058e0cee9ca69e9f578d5a702015117145f0d615f7c85e485e86b2862425928169713ec09e6055bda4e3fe3a19e9aa39db5ec1ef28ff6feb489af8cbc3f75f9ca46b5ed1b396efc9554b6fa459c9b4f818f8122cd3fea82d8f16b7e1bdfc92c7b1b997eb1656fe30220b391334db1f84a6a0eb76a06e2dcc4e22b0e485a14f547dca3f55cc2c6c3db18c9314138389d4373b8956551cf873371641e725703cdb2fd7be8a893a965c6adaa65d10b8aafa06bbf6d80cc7a4189a28af212f2157c62bf7d80cc7a09bda6ccbca6102df90a3ab1df464066112d117d9121faf202f2156c62bfcd93592fa0571319af2649be824df6db3bb38892bc88f18248c9579004fb6d5666112911718121e2f20af2156462bf9d80cc7a05bd9ea45e4f88b6f80a2eb1dff69159445b88b8401171e1f21504da6fb732ebe572b23979f9f80a2ab1dffe91592f9fd7cfe9f5d3c5573089fd764f661175f162f2e2f2154462bf0d24b3885c4eb09397afe0cf7e3b486611bdae6857907c058fd86fe3905944485aae1622215fc111ecb76fc82c2221a22919d1942ebe8222d86f0bc92c2b5dbc582f56967c057df6db4732cbca92952fd5ca17205fbd31f6db11c82c22a0265893225fbd30ecb773c82ca2222c148b8faf9e18fbed09641691cfcffc09f2d5fbb2dfd621b388829ec827507cf5c0b0df2e92594450a2c42847be7a61ecb74d99457494c5b33c9cb7594443be7a60ecb7b1cc221a72ee6d165195cd22aaf270ded69062d1162d695b244d491b823bb64aa751b1351c106dcd8aed36bed3aed8aef5e065b15d0bc28def3422ecfb9d5665fbfd4eb372916ef8875b373c8f2f31fd8d3cb378383d78f38a69c59b448ff39779e2e071738a598387336df070fce794ac44db42a6d0a481b5b2fd6f0ecce19e1e7ec880efe3f19b23076e09818031c61863e3f03f70fcd4d595f1f0b2ba6a09de2ae6c891e323b6b12dc51bdf96ec1623f6c9db90279785a2d1b7312bd55d7d1bdab664f3aa6db3d996b42971906ae32d1423168a916c836c907d62a118d1a188d56cd8a06102d9d71c22166d501f237f5d5b23a822e76aadb5fe0070ab62eeb31c81af99fbeb5a5621fed1c3672319c9db205f21797ffbc4dfdb6e703702b93a97730fcfe5b791a2d6378e7cc643750d017398800e457c77d46708595d38e414967b030381fbc6135b477da84fd49942422d23df4da19b3917b30c17612cb88a7331872f61ab7ffdb7e75c87fa0000b7aefb4b9dfdd43e5167a9235fdd18554be6be7fe4efcda9a354d1952d5594c2e2f4bdce342e724e95f10b6371cef11105639bd99ac6559cf39738c8ccc65730e8a68ec9ceb412b6fabe659bd9cfe84b0f7645b2e1a8865d4f453adea606d5b7c1ad7a0ac243f5a7b4f11d8ffd5a773c78e47812b6ee221d6df1553785b6165f3d23171737b4a219af3a87b64fa1fa3870ab4e256cd4c78b7c7544a7b0b80cdccf291eaffbacb2fd65c0b3ba5c2ea834a71811049576adff3a98b4352554d176f2e4bfbe10b8ec2d4730fb1909a1ca164294fd8c84e0a4c67e465996f6a949d5ae986f7602824fc756bb2baafe8b7f6a8243d427a0939397945a6c102f811bbf9c7329b624f197c05dbf4369b175a88caf64fc7a0e619c263ce49cff17fe9f38479f083981b29d36e999fb3997fd603bbd3e05398771c041eaffdfcfaf230a8b738e7a0951515e4cd9f26739e7bf3b342459fb877b7a189a43790a492cdbfd6e0add9f539c73a5dde1a11a6c1dea35877c75df8f701f967d1e0fda9d12a8b4af10704bb15ace603b01e92d5bcd539fd7c3f7f61f63ccb9d11dc6384df5b56c1cdfd9a5ab05094a0e0a7c770afa1bdfe121eaf3704430c61633d35f249f0349e321c8c5eef0d0104e81dcf9ceb9d3b6e96beeea36bd91dccd9f2e67f720e44d7f03a06919c93f2ce4791cf5f2d5159417be7a1bc9d10ff6070720459d256478241e20c3031eb025ea2c51814742860ac8f0830c6fdf97b0c02351010bd8afc00ff607fb3fbcfd0abcbfa5f9e33af6e537fe2eb91cc92f3674579b6cdd6df3f564fb0ff52b505f8607d47740adb5565963fd5875e0568daf03b762cd02805bd9ef2df2e5abeccaf6a2f8b3757ee49fc557f11d89fafc68d9f090af1eaee2abdac2433f4280deceb2afb5d65a6bad357e26e5b561e3ff73f6364cb908e79c081b5577febe74cf8b5724dfbd285821b8c37f70f80e2271c377300921df4125827c079b00f90e36d1f31d74a2f51d3c818fefa093047c0751b0bf832ce0f90e428180efe013037c075fb02b84c1ce77300605f80e0af1f80ece40e73b4803027c07a7a8e13b3844c377f00603f80e5ac9f90e12edf80ebe70be833bd0f11de44100be8357dc7c07b1f458c077d00700f80e1609e03ba88319be8354acbe8335e8f11d8c7280ef6014acef600a7e7c079f782b057c07816e7cf7bce861f33c7cf7b8e81e12b8da3d1f7406e80880e3c2b173e4bb1bbbda40db2970470e456aec9a4de0abee9410e19c2fb1d5efde12dc12757c8b91bf5a08eed82124dabfbaa20ecae57255978b02d54581e7a1baa6c01d5bc5067e1475220fdf430f6f4477dbb6437027bee00e7f237feddd6a320ed48b43bd36f714d09dceee5055e04eaa08eeb0f27aa4b07838a9f8823b3608eef0ef41c7231eb46fbb14752e927da5b0783d6c15469ed39d4e0edde53c0edd7159775fa33bd5eeec52b54b7669bb8d8f5517e19c7f96d5bfb5d6fab3abb6f1595662e3e1967e9b7f10b7ae3c924030a23b9d1c4e77393970e8eeb3ee543535fbaa1921a9f643b60f81acc3fb908bfc9d4096c04b20bc1e5b180fc775727238eedf54ebd52143e0efcc5ff3100f793a29ebc30ecef9cb5d244b0152b17ef86dc97f0bc35f872c99f038ff1cb20cc2934738f29504de5f66719d1fde5f6a711dfb7efa777a9be90f123892290ce00e281ee738be26e3f89c3b26ef39e0cba1bb9ad7fee21f35cfed9cbb10ec9abf9ec3b6f9874aac78a8344c582a831a32342323000000011315002028100c86c442a170384d1459f10114800f92ac546e529aa74910430821430c0102300000000200813430003bbdc2bf263da8a4f0944ef9db508f2d0a90b7a8edf14e0f181c42f4eeab7bb0d81bb00f311372a7cfcb71c1e63f3bec715be85995dbae38faba3a5a1f1010b0c429d8249db05d3dc1ba891390b5c2bf68057c9b201021e4f5b05d981314256a193e4821ab1470ceb4dc6be3b0ab56dafb7f40b9231db0ddac6e2837ab9baa9be54677633f37cab50c7dc28701f887801ee1cf6ef3cd25bd3a2600f706fb0317f04369d9a2a003f8155d692c5b876c848436700cea6e37295897554fbb730ab5015804725db67320b2a1ad73eed44ff9b23772e1564812ff648b1cb9107883a80a23604e240c3228af08aad907bd1a0defadd77828ac92223f76415b3f3bece3c11c1bb0657bda4ec86c76d84aefc608afff5bfa4864265acc3809c65e68a71742f4ec110e845ef5ef6c5e91aeed8c776a5edbec3f878c6b4def036b7f7cbad6f6ff677a10124a38773bd7235d3af7edb539e1f4b930e7e95ca3b2ab03dfb5fefb69061c9e7636b82dcddcc2e465772f0e8fde2b9103583505e073bdf3d46d3f53f73b484ce839f5c9e68a9e2906b55be32cc7b0bd873a54cdfd5791da4691cf61418c4377a6ffb137c13f3c3f5c6609f43fe461ff256f8083e3d7a524da26936a924c62434dd4cc6dd15ab3b6d31a0bde950af9c08654c2fd7cac444ec36110ff8368778369dfbd3df9334b2e97bb974744487a74a0f7028c0a65a51ff405e7d3ea6f29f3d33a5b8f0ff4639801e648dd22f75192e74b5440000642b134bfc9bb83878202170ac0f0ad11d9bc1a3616e0e8a785098e5e8666f61858d3c887da1a121af8be94efaf1278b880d7c4fceb8b70f2cf071ef3721ad44cd5346a2f44e3957361d03a006e3be836b291836a91bdbd69dfc649e8322355b5c057614a759278520c1f2039cf4e4f27187e75f1ee10e6f6e0ee1cb24b0344f473f0556449dbf96f23a9b3702301991256727e4f082e7358a7f2842ccecde1830c76169db4e50bbfce42739fde4858313bf031cba16a76758375aa36305df0e63c1f9b73d9b49b349c4148abef7eaf13747a7d8bc89bd3716dad7c9683032b90d2255426b63a4252aac31971d5003220bd64e7342149886776f3bdda45e9f4b1b5ba42224c182d8bd4af6c5e011eee6a2b2f31f166771a8f0bda80926cdd533a22fe869d95ab028ccc332cd3338a2cd7ef10a3aa53d1fadf172c76749f7d07fe623874cb7f2e28cea638f4b8f3a7637884f00fe52435d33689984b124ccc8396ec9a0c9cf6552fc627499e81c42627f672399e22c8b00ba1d72aada7a64e312cd900160359fa74d704130e0fe051fd6f1d1b3357e9b55202a9a7f73e0ddda122e0323f3ee401e128556f7a6cd89cd56fe3e6bdbe1a983b7b6cc6fcdb6f53e6f09e9b9b077c68d25ce26743e6239f1a37a7f9d5c0bce86333e6567f9b323ffbdcdc1cef4393e6893f1b32d7fcd4b8f9eaaf06e6bc1f9b316ffedb94b9f7e7e6e6ef1f9a3407004eaabd0ee692b908c825a3df98dfa7b67479a4dee1212749aa75680d0fe07988ebc848702833247543e52220cbd8bd173604253ae8f58dcecc389b061e32d72a4e595a4a8403fbeddbc3a1a569bcd55ef030392dafc3e05a942c3418ab35f1a144366514a7e6c07f67040b2eb5deecafd7f9e2f3a3ae13de28cce742f011c4a887a9b510239871510c37a761336762acd94da37c8d7aa4e6f9b7a0b77b758c62e00b4997f04de44639963d56cc133bea67d6056961c4494450b8de5ce5fa6f22c8f7fcc3b6a2dc2422d05d0231412b02d64f4761c2461586ae5fdb89da451e62d3a36274ad077f7891e5288816fdddf033937c6e67c7757830414c091bff0b8a72b9553a6cded2ea71fd63442bf226374f2a864badef11bd5eba19874749c78090dcbb355ffdf7850016b5b83a35755198c1d1d4849ca206c1cb3ab60f67766c1e87fec0b5f1a08d6cc2c5e48d7c94eb1c18aa0b165cd3a6d541756413d201ac569b9c5a6663fadf0357bbd8d4102159f62070abec258c97da55b01f77d31722b84fd8f4e2c2588b85c0f4567a352b72e1e02bf90530b02a972ffceab64fa8536e9aeea2a753c89ac1df576e3432062fc7437a6a34023712120868fe2090287a49f64d3f0708ca70f1c7289667a5876df86e5ae683e6131ab678653f2341db8a23cb26195391c69e86692af5cdba75463e6e12decf6faf8dbc08c76018db2cfc2c06be6e90a269a18d18fea0bec998a2514b3afaa4eefc5c86928c9a9c60e0c53b2e47f12f4a8f2c4ac8572c2e86b36885fa9825f79e8a315f9585587419fde7efad03f8fe23740c9db80c24deeac228d5469ac33456f1df0ad03265cc872455db094c31000cffd7ff76f3342a156cd1c1d132d54fb8165bb619d7c38a842629cbd275c1788824de3d5cb6f03aeb09907d6e4cffe197aa024997a487a3670172e16c8e871bac742025181ec49e15996b10a4b8580d7575242d3f884cbf61588df7f673c5680dd36ccfa1301ccddad086c02bcc1d16ac6c159ad4ef4212423950290e29c89fa3c27aaeff10adb88e33491357d6feafc4559ab0ea73afdcd377d06a020b72a3f6b2dfd567c239cb484f7aa41d8c5f5f09b4ab111a22bf6ce94c5277e136c302acba6e28a6805b53a362a53ed6710a3333a8ea44c75464e959c7299eaec05c248e1395591098861ec03525250c10ffe67ebc2954b3423786898df7b590b7009ce00682998a04cfa6adfcb5a418c77e16b5d6c0f6e902eb8b999e0a1a15759dd6d6edf8414a6fab32606e0b7f9797b27e4f93ef64794dffdaa00749cb5c5ba21d905457aafe9dcb7d65df21c05954add4276fd8861f0af7dc235345245bc0f3006370b89501b4ea31bd318d6aed1429985fdaaef02722aba434320d37bf6cd337f03b45310e6cd17718f259ce60afb0aff3f91541881eef8f4d3ab24cfddc86ca69243b77c6a566bee266e19d4e7e1423e09551ec7a115122ab587b02839a24c5f099e2bd93bbea309affd02c30747cc6953a89737f219d6eb4620b26c3c2ed6d83055ce13f0e7c2fd9b48922d250a65d7cbba9c0cd15a9c1aea1e931c17d9a6a0d198c20ec50b1401ed3a6d4f92b0b1c6e0db8399c59362ab07fcede430c5b5091c5bb0ce89a678ac1302c9b6cb44138cf7cd99903ca94b64d6afde7dd8492535523c9adab42bde0fa1e62a47064f80e2827a002323bcc470750330ce1a610c03b4690a51451fa26210519fd53c08021a0d906121ee3135c3dcd74e9d56ddc4fcb34bb4bcadff40f6f3d32a7476d860ff43c8fc46d32840ab89ef8fe9d35e8600541883f7a55a4e2aa06cbee145a3080f173e18a1150591490f9e216cbdcba22de66a96c0151eba83b197a22599b97582c1d49a925948058a3dd1e6933451ecef080fe5dad21d299b02909abfb4b09efbd3924e180ebf409408899384ccf3ce83d54c0976125fb64a966f5769366efefd399e571668f7a31f466fff988cdfa71c8cddaf2198ff99b04663bef9447790ff1b8f3a47854b1a0c7f9ffba66219fe1515e4dadd97b4fe091e7497bd4fcda1ef7cec9638e25f568c75f5e5e62aa10c03f8d8d0a74d493d22023f721a29e9bd84545059d681cf7c33f056db59d4e7399a3b3baaaa1c58870169945f5683e705ea0715d0a226debfe781d31991399a4e60a4252fefa8046ee5ac24a3352013b154d119b5be8ac6af60a4d26ea8a2813e62210d088f7947d59b687a49d263767fcb09db4810babf18f1bc9e9fbb5bcb482480b623610692c90e66f373820b3ab68a147deaf8ec6f9c169a07c7c60e0239777fcb0ea6a836372d1d01aa6d260d9a3ae347d3dd343335cbaa64bb3c389a2a8d2dcb1aec781aafc5d38e6380e8e0bb047bcd7520584ee6e7adaf245141b322963c7268cfa1092638cf341f1c18318eba1f57e3cffaa255d638570e5d618c6ea7fd0f53505105c1140f54b7365159a164cd9c970a53bb5bb6b3e17b142d79b54f2a8f9a5a828fae2ed59ab3a44895bc0c379094acf27fc85b1235d24c94d3454cf7b7c672c8486d6a5af18a0a5e984d6ef7da93ff49dfd349e2ffec34a642668cd3eb7deca62e318fb019ef54d5ef70112f0d574b831e0e86bbc0ec51e8ed24d21e261ce64af1171d2014df672a1943ec11bc25f318f8ac2328de8debc1ef243e24da23d6238fdda1c0b99bf6b2cdb4c79ccb03e8c636d291cd1eeb183035dc4f243340f69417013f8a779d741b2bc866e0960516938a24f6906722ad83b24050d52f5805045f29d993820168db13cd4ab14647bc36cba17b0926dc86c1136957f5641088699ba210aac1a532564bd48b2a85eb5097dc66a3bab0826cea0d81ad917a13b29612a63498cb911c467e2a1ca92787aa4f4f42714397e6d9c0588ce5170011c078bbbfb6ae689d495a26dc9321479543ebd25eb4f54d4482d2531ec0712a8abb76298d460c32b6164435820f8ada15f0010dabf6e204151dbca90735ea40e8bdc0917ce1cc2b787e107aa51d9aa39412693121b1a402b87fc929b4f5ea6c2d3ec448d49501689a97f484071c65a5e7dc01f0fd546b1c266b1f6d4d98ff1c241450255a8aada08548123afcf1cc00f2b55805b1f699d40a1bcb31b5168e5ae490b8c54cc302397ac5a09fc3e9dd401a694bea829407025c6d0daab8343bb7e8e91ccf64177edc1e2d0b937fb1e6a6fe5f41d95adadf6a16ff2a40e80ef2118abb89b56d087ea0a51e7e3c6695cba8b5f895058f32584a5f188d566b471197d30bc3c237efdc6917284bc8af970e8ea99481783e415162ef71f79c93fe2bb69d60f6f268fd841553bdc7b432aa9317141490cc12c4d3c03a9571ed50db528cee52e58be1812fb6ec0db835f78bf8edc5437f4dc07d893e4fab5ae3797a9c50795198e0b39c170b43ca685cb869c06f30c644e78151a8b99ae5516adf8a7d7e83d8880c7aa8a60fee4296ac279e2d70396afe25686f239298f1b391317f88f61db7bf5491f26f6b47bdb6ef2fd9aff89a8f377a8a599add1684e705432d3988c8ad9a432b0f380ae7480130ed3dbc5b96d5dbdbf61d1e121515cd3169d46c54d1fe7dd446905c2b7668b9a0b8faf592402f3318bb81bd1ed0d183c6e86db08c83c68536c4b9ad1d548253098520e1153a3ec20e4bc59709cc6854f908ecb338c5791b2e8206cdb6490e3c3b06449fa59fe7efa9b6c691a5135c0c37bdd86d2a0504d285eb9750853c3e9c4176b764076e4aaa063d803c355f8658398d4c5c2fb77415cf5ba1913e418cdc81ae41ed6906eec4ec1b070b2426981b11a174488efb637702a2c0d180a5d6bb81dfd631de90a4af364a837844d1c5706360a4c03b823db02c5df14f214216fd671ad15570741554cb62569925fc1f4c523a8a14f52bab558e10d4a8ac22d25de43ce1a1dec68490095dc3c2875f7e557bcfd783f8a8583370f76dc83bd795e7887605ce7685630c48f4f6e553ca2d0003a42e8c1809c178812ac04bf6d17701e301b6f79fc46ce69ccce5bf169d86a2d97b0cca02f259e52efb54eb0c9809c4f31e84e294e0e9a3026ea88839c96c6b1dd9db0111a6c1016a5b2a11dfd0ae118fda5a40c31ac86389a332d2e84565966ad5a03f01597ba0017cbf723e35d77eac690be25c052edd34d69a474e0cae1e46897b4a2ede78e3f62c31d10959050831ece719c82af5677a871198347bacc53343e0bc61d8f3bd8c87521a769ffd69eed79735f42923e705a62b0be9a27f14c1c449d4c0bc222bf8493b8b0862244b4963d9bef7b709b76c0cf5a2cf9f7ef9bef9fd929c426062f95d38e135e38a07684c1ca837faab76c56be0acf9c718de00c05781bc99212e2bbe2e1adb209c2fefd298866928f2d1067a6109bca07022ed1ca06bbb12464b9648fc6963e124278648e07dfd888a62747111c114159e8a25582c287da442c2fb46aec192ef7faa2049e77ad0e25dd5c36e6d03950f4408ef727b8a2d2ff81f049c362d18bb1c64c4957927b4ffbc605af8d05f6a87813baf35642155403311667a032ce149134f6d4f37efa149128bfaadbd4f7b395df56fb558f852be5fe1caf7d4c38cc2d1da8c60afaa439971d3bb5c207bd73b774def5d15b69d4ed5e5dd949fba750101dd41f1342ba21d6b845103fa7c1527609066c58e6d1816dded1797b53257a0f0b3ca1f3e08fa161cdd546fd437827a0c5ead3cea69ce7e5fc84a22332355cfe90ca17eb41f942d368a31c20bd45bfd0ac70764bfcea18ecf805f424332f22678b2eb9654d1f039075c67bf9f46afcc50d92c8ae97079aa291a413e820ed971516d6c4cedb80c34844ad2e66b53750a3691a18d3b48136cd971ac3b2e1fa469c9c26346d585bb3b59aada161ad8c6a6eaa476d98d1de8c868db5c4a45106efde45e2585733e2d62808479b1943f5bc4d0c3df3a74540b2ec119d33c099720e58bcb2dba109e80a46f721857699abfc137f832715f98722c8fb125244c1c965c99216ae6580366b13f919d0e0cecd3ac0e5ee15a8b51d4895e210e58319bc93c54c4926cfec773608c320a85504818b84be397dd64300b04a111fe0f1eaa8315bdd1c00abc9ca1038db07803299e6018410851ce4a1d846acbe8cd2551fe4eef018777b272d19cd4c381c5d230134517f0d3c2e700b7f2e2431fa1ce4e041361fa8a6fe645a910084b2706866a466a8e5e93b671f8781331a90310e840ee893ef3d829b603f9cae580ffa131ab5cde4749744485925645e89e69690f2e70bba28ab4f4876fd553125ecb272d3dc335882e6f1be8cf2d5770bc5053dcad832fb233d45a1e001ee960584850143e9318e8ef033689f44a8ace5328d35879b2acb67aeb708a920e99a7bf3d042a5b32304c665d1739a8eeba96f0c8a5250204f0efd07970f8e411c6bdf09d70495b5fcc5900a3babac455dcae3e224a94f64600992193b86f7b5bc4e19d1cd0b3f3c9334a27c98f086cd3f87a3217a576adef1f2ed27590e071d99c5c8034ffc9241b5091cdc77eabce8f99defe29cacb497b6e926687862c2c4607b622c34a6459b5f23eeb242f61a235b5e1352c42b4aca2bfbb67cd01f8e1d084338bab2563300fa3a9a02117b50be545d7e82da223705756f645f9a04205a87bcf26e4fae0cc0d75eda2e77766a682f5c491206f55b1d29d16051cab198ce1b46a42cd7a04a2f6c8e06b10913b326e1a2ce465e8c54ead43fd02bb25684463811b3d4fae051a809341081aa368f52b7f9582c7b2e2c7d25efe6e6d94adf7454c289f6db0ddcb4b7d3fc3bd1216a16f270a16401e6144feb287bb3c8349ea24196885c953138fa42eec9fdfccb15e63d90da5b034fb63a3208f042f2538e2fff03bbc93397662dedc228dbc6ceca466f5796a6221a0920d8d28b9750c7ad3e7911778041d4aa343b8293efeb8e964534b8bbb999e6c500344bc007f0d2e629e873a43e6546efe180dfed1f98afefe30c70b97450d4590adbd436be8fb097ddccbec0aaf76e6dbcae878b01f4c0e0efcb620b3cb0e361a73e7eb65d7789349fd7044513d6f7982b448f473c99a96c1928362219f0f31a9b2915f83b051d94aec7611273e7cfe53e41ea5f4edf415b17a4bedba9e22eb8d67500631f25e27ec5f21a3b73beea26478fe5cee2abe719f2942191ad433051b78e1065393c3d8b0854aea9deda8267c205fda7b71fdf9a1ff0c7d239196938405146973f367ddbfd89c237c6364eec0de1a70fdc24eed48a9f5ec904c1ae06bb57e76bb717bb813b047c114b9e02799cb749c003ea1288cdf4c486d6b7a039d0f0d7b3e46ad7f3ba3a2bac4156347ae0d3c88e89eae5ae4bf85a5668e94912a33e0908c9d9380a74fab95a9ddfe6d48b729d5fbd43add9a56671823deedc01b9f49bca0e7b006bff7e3ef39085ffc92d889f607234187fc7551301f225fdd8cd698c567c48dc7052a35298bd440b3c4d4a5b7dbe996525d7187a9e4742d348ea71f9c29aa532a0cecdd54786d6b61ad950d8d61b320b2234e961202cf3114bef60d5762ceca506938e1c484d283d2765d8c1c1f9af37bfc1a705d08295d238985ca80d3bccdf2617d1154cfd4271f00080057e6b153745fe5a7412881f2e31d35fb45efd39b99e4911174b87668f908bd544be5ce3c43c3c8491b67ace00384cf830e0b529741bf0a4027b0ab46f428e054c92e11e001b4ea804404f539831902405e201c0b8844710dc01438b004636611d4005514824808e14381118d209e52890100c424cdd82487d08f30178c19b024a071cb72a34fef62ce6be35c05601273d94bd69b961bd5b87e2ea3fe7eeafc43e390ec068d6c3a870e94c2c7ca68d277f758731e6b6cb57000c8cbd047c01f1be581c187b5b7a52af5635d5b97a1f442f5f391e87dcfa03cfe8902d9de50c5948fdf02a2164782494a608d37236225cb440344ee430e3e54d01426372d49b9bb65726247463e370ade1caa09090d42f946b8bb399e20a65048f84f96658df2ae8a0ea2086a65d05f2fcf9078d80ee9fba4798d6c6d7fcbeab915735520f13cf2d6dbd1c033e7a295328ac300190e9e84ea051600a98b4ce6a65be524732166ddb1314f208bd5f9f6a7d006c0472e1ccf679a4517e800991828fe5b35ed68473b2da4c96af2cab404264759930264b4007ab983ccae3acc2cd01cff02d21d448e79929675397ee37eb2f39b1db796f5cbe08d637dadf216fd96068f60e3de03c720e4e2f6062ddb594c9790009ffe0bc7ed45765c900c613b31f0576fbcc90b0ce83b6fb3d17269123d9d3ffb470cd53f91af8aaa10b2f23860c9245aa9fcffe659c0c3d8c0537fd055d1bad01e526a9abd3bf7b401c80b871456f9a74eec74d4d7065ec751a43fb6d24c36734be41e8d0f53167764cb1560302b0881fe80e01c70924e601060c606b914e9894532c4a3e18b757aedcb68249573dc629655d83f9163b961973b623a530e118360dafa18e04c26f3061058391e101f1655d2b725161a0ba1df17860bdaa2c1aa8eaeefcee6cfdb17e565081f7e372cd09d76220094a0144d7fcba5c7d50e335ebcda748e535afe4d4e5cd7acd4b4c299252cde94acab1a4efab6c3d2ff611eb3c276fd43085ee4f508458ee107287d44ac0b027c5220a8bbf7506f8e97eff22318df01ce8a20e4e41e40a35928e29ee6c4e987d4fbf0ca88ddf1c14397b22f35019f8d05b106ae2534b51c9e806d003d8a5b46dcd9ecdfd55511b4415c018cae9a72a9e7c262287453621c4b8d5a39db7bd187d4689a87d386782aff9c5c89434fe8c7650f0697c9fbde1ab4b415fc221cb8c6aba5122e87109a69c216bbe36473d75545121f30f1a87c35a606d32ab06c35ab1f8a95a1e12eb8f0060c8daba066438c23434b2736be98484e05d165833fd5332a061b919b2a463320cd090ae931a67045a266181543051a8c515f55ca1038ca2c9a5d4b9adb88177c82fd8a2b67dacadaf317dbaa9958320c97c54625f604d391bed1c5efd1959082c857cdd44845c24838ac934a8c55883cab6006855aa802a8eaf53d24bccd256823f79ea51b07e5a7febc20338d6f70c1242ef90ab820f00e788a73d2e2f2e208cce0e2f4cc2b981d756d7b973feffc9c4d83484d9e2ff7f9e41e280e8279980b17d545e9ed9632387da35dc09ed98bc84d432d06a525329e5c3259a503bff26663eedff7789b74c8bd499666e960cf8b9c3bef906f8be2d49dd67406e645cd090ce4f7df7270fb1d27451fa30934edc42a5982e92a977310d923b2a2275811b14f3b74088855ad34797b8e1ed60f4cf99fc58f08966431ec66a12444ab03cba6bbdf3a1616bae3f7975b0300c3afff6fcb8ea7a13ac457493cbfc25ada9e32e982e1a6cf76da627972722514f83e817900435dae3d72f0663346b8092411396cdb8ad5de5b35ea120dc26225a0f6380bde52a38f989a385fda7fc51e5ce41838418216ca61534c145efc09cc914db67080df951602d299b203f23edaa1393a6ff19b3f95a3d815c4cdb5e12314f0c7b09106c5628a65f358eee9d96c36367b087be8f5afca30cc6a22970ab120575ee0a7a4ae1f5ee4c6e8a7d248262d3c073d3033285c7c7222d28cb0cde4c95f5db05f47d78c7ca98184c57c4ee38dcccf2974f8c401930c595ed605fabe148ab80a091f705a59d008311a68817f7fcb459f95cab1addd7a66f6cb9208e8804a4d9f647aa425d8ae34d349d0d7163a50f6d0034a80bacb6e3e297d2c76b67014296f9b611af19d33a5f7ac8b9071e59f24a6380d231ee724addf42918256c435e158408f0ad16f65af1c18a23af345141dfbb101eb248ef7be32887def68029b296f922bd8f931572e3134023f61ed74d7e3f88e47a3c78fe32d7b558bdd4e572fc48744320b3167565e5a052149bfc88d45a2548b5b3330f4481cfdcf39b08057a3330927b9c31eadeae3afea070944d1a98216dafcb10cb62330d17b60f1e2efe4ecc34bf40f54302d4fa9d1835cfc9157ec4b8ccdfc3f5c53378d3a9f3121fa098d0964cf51099ff941633a722e2e85e7cc1b32e287ef56e61099faf9f76af095478c6cb2e79b30edd3f1c8cfd215a8c6a029929676539da26511254ac1437087ae780b8037daaf49a5d89efe1c27010e95e20b2b039713502646cb3b7af3470dc553cf16c2110ff22754f74cdf6ce0497ce69123b09e44de0ed16358cb7a21836171041c028f87be00cfdb4595dd519795d03c20da7cda46f52d7596d49b702be94c7a2f588ae2b64ffa83ea6ea5b20c7680a5a730ff9302f5ab7ff67f9320265b1545a079d87c2c55b6075df09b4d8057c5681c659cba1c2e095da5980dc9a796a3ad5e31aa20b6c03819b1580e42e64a7981916c06ab03564b00b2bb7a144f4fa48accddb2d5e7a243a14be950c8f501c9452523f7c0542ad5b745d7d5fe421095e970a3171858b95fe60f5f1455530f64f144206952b18306c9a684485fec05d654a7d00c812181757d3b9e149ede3e3bb882f94de0ebe65d687667443d16bd1597bc4b2b3c5d868115f44b96993c17732fc40ff3d153029454b282701d726ccdc1291fcdd8f780b67a32edee3fddd333b55cd12632696b16e544e5cdaa35f3f62a7a2424b01cec9ab44877472458568cc950cf6803d8c4ff9bfd1f91b56ef78258325e56f2b4feecfed251e6806e13ae297ec29d846980502f4d2fc1575af51d711cfe18fd9f0600a00ab20a566eaffe73e4a632c67295fadbecda3f88630b0f1bf05c8a4d4e432235f8088397b3c4a70c1e68b0053a6aac49d0ca0cca0667c806c1563d906ab359d1c73fb00e17cae3beb594490345015ed3a716690167de40b51b6d0bc5424f0f359054fb99c085200df09281a48e85436a1887d7cacd5cad86ca884c1dbde5d3b2dfc18b79409c47fa064d452430d98fd2b8a410b450afeecd2416440ae8b37ef7fe7c15115e15492dfc732c8c3b634a077e8964cf2a2e8fd041418f1e0f455d5d6dcbb4faf9c404fdce38fc9c04b005682c8718433db8ac3746078043e38a6cdc87c4d0eed863f3e83c4bd5c80f71f11ccb031760a1e51f1e26dd72117082532602015ee03906991902068f8e20ac22dffe2a4dc379f427b517ecf64b441c12f170b107d4e764e192b55c50751de7d08693ea8ff5a5ba5fde86a900073a364c59f6e1ad4601df3969148b778992a2eeba4e16fa534af4c1408ad2557a01cd361056f387fbc737e796e6395c55e4a32fe015394d2b1c8e37c62990a53180efd96ce5787e48107fcaa059d66bed6259f6adba40791b40f1b3f747fcd249f0e3f0a200db5a3d4c8c04ebba1bc104694d2b423210692d271625a956116df84ee3705105d25ebf579a1bf7e65371696e9fe48ef51940ab04260b98faec806eb5e13ca18b14a15cf428c42a509e13923b0f91af07a1f45f6440c99c1535c2bf2ef405f0b3610ad8008c55b3d626c115826e84529c88829a4e39c5f5a35426762ffc7e6aa96597c7f0ff9561507138fe5f9e09dc34594860021f0573228de54d93479887fab4a1878421bc3043aa2656257c602c3a2d76e668002c433bf47b3bfff559ee59618fce5994f9bff3dcb2298ab2254d6d6eec3090fe0be78b4d1ca3a89dd61c1bd577586de639da21e487bcf03ba7912ee3d9e840a2532d5f23132ecd9d551d7246edfc90889be915813cc841818f69638c14910d7896b945206808a481587dd08358b2113aed8967214f4794584fec412547aa5dcca04f4dea2073783a70f74e0eeb333324e1120c00a711005ea1621811fba200411c97d288bb18c75bf21c1a8dc93bd0979837845020c5903de7452a59f8179e66a46b3d42c70ed784d14c239ca8fd129ca6326d5a046b69d31cb056e846058870ff8954f7be279a85e0c6d5172f0ecc96737e32bfb83a2f79af3dcdb57bd988f2e32739ae2d04b210d530a24fd1701d03fd58962ddbec80e05dda843323f09b8995a07a49115e7ed34861df6e30fe0f6c1926797a38932a1d07f9ff2f83750594de88b5a0125ddfb38fbbd1b0146156b1f6b89efe2f53295888290456e06c42eb26b9a7560079128db79138d7cea7d12530dcd836da4a1e8e6ad416b26e8af1a45448798fd787fc24c5973428407de66a82745e049b40c391b0b67a853b0eb3b19359e1051e42aa8bf47d1889c2f983fcc2cc6a6b490fba577073517e3c2036c5ceb83c3ca0afd80b1492377973e256ce08d3dbb49373be673283a03af25518fdea09e9678c1d9a7a6bac0202de0213d7b30958f575341c2f66f133047eaa49a413f327dab6f176663bb9ce1205876ae541a429613d99894e08296f9bd3235f7387264c15f549e28cde630dac9649948d0d95280fb3a043f8d25a336fb351d6d492994d4b89ce46a8aedad30a437e16608d92c5c176eb4de9095fcbf4b29e053c2853c575f73463983eaac3870509b23164fb3ddc871195e157eeaffd8381036a8726a9b1b208368bfccf0dc809a28039cb2fd73dd9d4bd30b7aae769c3e4d9356892d9ac6944224f82802ccc4bb851fd6000df4f52d286fdc1affabf6e26d1bae36b67cd7689fe1d14c51dee26e6682a89a40d5516cc0c0c68b171881bfcf656bda325839a2589465d4d39b866dc4926f341f69e2aa494af9f0597b09bbf92491f92302161d1a54c9428ba0c2ad3e1982d06b87acd00e7afbd45c4407f14aeecb8ba9df46be7fea5b36ee42345c9e8c140ee5c7e77d1114721cc08cdd49a2d64a129dd816270bbe3ed244716433f4b1b89b41e2a37be71ceb57346f3bc79e97539fc8484066f4ad1a6d45951d4583ec0437a44a8dd8b3f4ce7ed4cbe2ef150d68a712c8f6c391669de52856e4c34e5a6b780f855de8778296ecc7ec524874343de43b1d1ffa6369eb56456c45ceab701b2246a8b13353651f3d17dd09f0976763a412512c38e5ad86683f05d2a913be0b2d53aa3b18d09c8161ec2ac9a8b41157a4fb8b905ae1410c61749140a9610c2523cdead96810c6f20ac2f43494536feff6fd5e0fd95cd488aa9acf95677fc7d6a64fa8a5f43e7ed891f157ee0ad7ec9031e6fe58b215f8741f92636242e717c6d44eda8b19b27b420f4c659892c222bdd1ea44a197813f57117396e37196f4d1fcf3d892f7630f2cabfea6c807a8caf0c47a888e71c9677e81c239aea5224fa147f350a56b83a5fc2fb51e10e5c3c1944f8ebf5669c448391130de8c27260315b57cf0a9ad393c58e2b48fc79a98213d0d63eaa7e960b9f76c2e7831debbe525872ffbeb6a86ed33889c568d2480da30768c3fabf89f3a8e92eff43f0a1ee6c6ffec023108e2a0c87e5a08f2bf19ba9071158c5507865ea3d5235f61bb0d0d6a3a154c7e7f2934d19ca1a8ce5226bd10bc683b49f4f3ba259064056317abc448153fb688728fd49c1dc15050a668f2f069e0c302508f59a32a8bf80e4aa2fa1cc9435243c789cc67f48f7b748da8e4ffcaf6a9a44a62e28f2a0baa0d90614f3a281c123d8252dfbcb49140f18b0505d9fc0dab6be41eda901f29ed4d686724333b18639bf0d4e53228ab4cdf7c8bc87d739d2ad3f4622a223c76d6d15b336c9d2b081b3c4f4e6e9112d90e31fc88a25ae27e3888d143287f6d4c133b240635cf890f8f9532864d89ed28942c14115a8129f284146f75f1e8b8be2b3fc93345f40b1d1988dd1b8141e7048d7e18a179cd492a4514f632c831b71b5cdc5d2c296c08e3edc4bd84bec9c46ca631c77735045e9affb18f6573cfc25aaf1d50822dffff4155adb9908befee8466e7ce31f3bad059eef0b93c72b9e158f06a87b77e168b2a67ef910fb84e5b6b17c7eeb690b873af404114a2a2be9f46e8f8e1048425c746f78fefc731c1ab83cd6d7a91fb08cebb99c9a56ae65739f31f057be9f46cce33d465c8d65f478b99604e176b6aa2a09b1537fc73a924ee431cf7d8392dc43ed6ce01a50ba7ff4fe62b53be902e03ce94f0b0e20988cb3c19d00af70bfa220601dc23d3bd6faec1de96ab650f948850246f3a8a3f75810dd8fca430b89150fa43e40aa42a8b21aac22258be0bbdc95e8f78b686e2f22f1d551e667cf2addfa3be4984208f5eceb8ffaf52c5297deae7f811f4c22b197693421a51e0e2e8356c9ad14f2b93d7b019cdc97a4d8725c600914448eac91911eb45c9eb44c352ae10d1e09bec9a80f476476dbb45110411f44644636f154f6eec5eb1629ab7d38e0b0faafe90ccf1583fb74c1172eb03d76546d99d7f9b4dfd1b81ba571050775d56ebb638056d9e2d0c5716c127e0ddef8018d47378e3c7762863ef7f87f030558f375657dea42808145802ee36c5bc16d1d97e32c4d4da1c970e9c2bbb66df29f5dea224b17b98ca9eb4703b51db5a0a8859985edde10d9ff757272825be42cc0a8109ae62c2c801a004c7db08bbad20b84195068d6c4191ba846dc1b9a7603e0c2c4b37c49d0fd1ffbde116c8a191bfb42595fc776946c8434bd0623078c6fc214f99ef9ea6ed35a5cb2e693f074136431dd27a8478c86eb3e50088e0abb26896ca5907f4b1430f8c02126ab4ab5a9b8104608f4e051702c98a216f14a207ba881de28c8540b88fac4d55fe1093dbb2a82c135f117f6875601b16692cd7d14382f926b8c8d1be349f8742e7462c78470cd062897ae19ab3ff359f9166305c51f99eeafec212adc67aec7837c15f4bea665f8f0afd5088b8bd3424b2b611da2bd47badf46609df07818b4d0934e8e951790715d7a601fa4ae010800dbbc1d00388f0861732e8c682349b0c0f49e65b407e21b0a08e9c980b70d32b47ffa798461895bc25f500ac3e4d5fb51d741e58b1f9a167bd5b9945a3ddc09dc42c67353fcb54d1319db3aa3df8c33d6eff9746dd3eb57c776d973ee434d337b9686121737d61ae79f760fe395e8e0c1f4b2bffc23fe3f628aeaba0f0abf686c981aab278e60c908d99a56376604defae8499dcb9743ec28bdacf0067557d11081b838261ef177d3cce8f8952f5aceb93ee5dec05a9b72b0570740849f14206f0f6592decae6798ef6ec2981794ead1a47d797951e8bba032fbe5e5fbc0c48a78764417561a9d163abf62d10749a9f8c0e5fe97974c43a3e22fd9b183d069094736543af9707fde8c3f3c7b70e27886391ce6a50402115338b572028b6780dafbd73c757da226163d25e60fcf33dbed867e44c71837bdcd48fa8a048a412f542044b1a57cae3f193ca693860d8c5a99ba178a2f9701aa638706080c7cdda6d30034eec565b8c9cc0a2956a98c6cde78d379d830ec1de07b796e29bd05fb3ef194f30fcf95f4a014b4648ac43335a442574d14d28bcd5cfade8b47f3a04528e3df87fc40eed310c05c72ba1084fa11f8fb00d328b4a598d2e1f308cc157ce34e372dd4a4ab4daa51fbd758a694c0b456faa04b2dfa8fb62e21578df94b8f5d7c17ccf69a7b1e78d5b855aa31ced252b4d8499dc987cb814677c6a2e1d5b0144835d35d0342391b2c9fc5675e0539b3624e8b70f0985155a0e926c89262b2a0dd9de344bbb12de55f4c2d831f8ca57ba3d7aafb0de8760ad4c2fa1dd68920c5cbb781de8f50588987d9ed8b9a5a31effdb2c7c48a326a925fca91fe34e81306ca7840bd5ed530b2d3da00b7fa47842a40cca56172c4b75e9b846e3e68b28ff9bc93b5823ed0878aedb3b3da7ace3011100281f4833bacd9e843274d8c61106431cd91683e8fbd096b0c8dee8ae9d41530e0c3af3ea547e57ce92dfc137d802ea04c069f1c7f71befb8d50398628df008960b26a1a648986d613f5575b3956c5976a3356b09810a4c6e98a1cd103d5f73bf68d0db59cf20ce28757b325444c563096dfe339d3c639d752b700bc14db2309410451bc6bb7cb7e49a24295c90a4f4942d1361d8495008e1e6c6a82cbd267a1b17a6d8884f0b3caf3d567dedff845404e6341615c74c9fd3b5d3e00f4f755dd580a402494d6241a86fd491401b118df523afcc24b24cadfc49654c1eb0cd113b1730869b3c3d72755c9449ffaaed01bcb8c0255944f86113b9ac94dc00141556b265b584924ce444eaec4baa44c4f52adff27555cda04a242ea2c3bdcf1dff2d3c2b05d4a9595ba1b2834a1315e7c06ef39013708bf2c36ef52dd5d3ac2c4ee9defa4336dfc5f1d637d300009a91ce147c742580f634c27fd45af1dcc4bec61aac8736ad200ddb153d459eb993179a5762271424842978cc607b322c03894225825b5c87dbb4de99be1c20b1f5bb5174483e64180c1c8e100a345857e695434ed0697c127aed7632bf64781a4f42efdbfef0fee4dc4bb6aea5329818334c38edfb5cdd376dedc4b68787d39b085c29912b50d5442552a62cb92041d97c954670b009fc6506502ae4988727d4fda70302c2f52611dc5874e682b6a216f0b2428a871057df407640aa4cb329d0ac730880b9908b7b4aceaa5f92edc6d362b0d4aa72f7ca1731c177aa3080f93881a6835cb1a1be1ba0c9750e2bfc10c57d91e46406eca76b4de8b2e06e076058f06c02f30c46b0af71d01560bb03729fef4c465afe1677f13172efc1b04eff5272a84e2b08d92e1a9f88179191b2842fea8b0d5b776481aa73cb297fbdc4664afede8631dababb6329cec279fcb415a1cea4b146159c24a3bfd1448a4dd15e7dd9df0614cccb864cfa7c68e9a1333e2467f9e4446b03500edbb57404a0eecd08f1247a8452c697775eb99af91c4c799861c490c2401671896b96230ec50762a6ed9d311146d9f6b5678b7ef88fb463d1299e8ad3e5c814405600265d4d10c244c1716db39659eb5f105f1a6376d9eafdb48a02d3f3c4d6e45bb207ae473c55d77313548aa64eb97a49bb58159f5fe1686bc7ad8007c93a5035804f2b05d7ea447865845c7c73f6996ee168f7958ef301bf4867a275c95d8d4c37e7f69986ce677822eb54894886f12ff924968ce47cbbcf7b25228c6ac7a30c68eaf14c9f7faf4458d6544c7564ae0909c13e0382ea518979c67dc96a73a64ef07aa354f7cb53aa5583732c54389cd7ee0dcd894b565ca96db018139a9ceb312b0d7e66775de1f9003c38b4c68210f6c3483849466794713b3c3c46be9001ddbec50c74541c8839029b8aae8912564278165c0fe124b87577cce30079f8e3f1f73d2c22b72fa98cb0baf48e27a1ea176ed20599414c4d12a3dc3e1c21d584527a4d2477c5e0b21a94d102877761815a21f829aa012f652454e14278246f284bf5ee96ccc780a986dbbd1bd150f9c564503ce8aef91b05e60e16d926fd59e0589ff5bfd2d42f9dfeaaefbd2c75f109271d1068f71c57c08f675d4d56837530ceb57f5e87294f951c481424359620220e867f95f407b2f63d28dc14dae1a5d4cc34d0f113da080207531035290ec9ef040bba745d148ba49962f854749a55e45c9788aab6fead516736ad8025a5fc63e92e4d8c69edda99e098a9785bd0ec8d7f21c024e29063abec8c5345c73a127f8cee6c020f4e5e48a2a1a0c6fd2d8b7ebfbc17e5d2e3846213f862d357b5d063cd20ac64f11fa119e2be4ecdbbb573ef290cae652d6538430ace4be6c901973c571c75d3fba6e0f7599e04da256b534dd821b4a0ac31ecf1b2177888bce6f74c796911eb660313b1bc6c741008acd0c088535da93bdc8c51a0a390a26c9c65ac70e5a7afc89b388fec086a419e9c08bbd1a5ef627f87af9ac6587a1988653fd6a2e4df44ad7fe1c44bdda46300ad05f29145283270a4504b87083d15ff538a804bc0fb3b18fc46b5d55c8c4d965a8ed80ea75980865979ce4c69e0020e78050c149ab007140e5c186525a30231dab690c92ff9c5af2141cf5811f87fd81addcc09fa12852fd50f3686df41d1c161394b00b87586a67e220c2b0b3ee2dc82f31e0ec0b0d1b31a7e6523edf4a69eeb54ca07c6d91f1ad384d18b0803169a1212643237a0a01db65fc0cea36a2b4f56192db3d1b557c56e85cc3f8fd63010ea5979358093a0842388f2f80b9da40b8f70f473ee20f44a899e13925f358303e603e6978b8073c6a5263ff649a8ec2c06fde3f988e5982168bdf7178be974766b516d312c4cbc5c3fa153c9d1b941d2c10be3fed8130ec820e785323d05e31cc68a0a5ddbe30a85a830e831e1f26a0133d6483300ffacdcbb79ab2849de617fd0f9c6029fcafa12e57bfe420010560ef8861906530e589548c69127b7322fce3bcdd899da7d1e47b7d3d0cf093db181c9c1096760976ccface6dbd19e57780666a5bd7f51364b731c5b08cc0c20c2f85f44b2118ff6dc6bb498b35b2a24c354df54bb2228430b2f43dd09675311392ab753368ef1c5932296cb7c4f7b45a5b2776012b9b52fa943b0ea6d7abe09f478cf3207b567c106a988e357705229a7ee1714373ca9f1666dca0e8bf86d70a5bc68168418d7aa561738750ca38357ed2e9c7c5b14badea0cef976453fbda87c491958a7bf9b332bfeec1bf65da27124079daadceba65fafda170a0ab00f12ca8af929c2cd95ce502a32ecfb7c0a6b7bed49c7fd0ab1b8cac78861694fd513354b6592defbcddadbbfd8cdee8e4184fd73cf9ef19f230c6e48899b91688f731dccc91ce5905971ba0779107da53723a7130fa3c984623ea27b3e7164f4409918dd16a3ec41bfb32211967b01b800634280d01aa92ed734d8f277c2031532567bf623642866da29cfafd38015e2bfb7fc6507fc6ba80182fa7af191b7ce470dc392a50219cb91ada4828180ad2d34741f83a8d1687d3b522fd53edfc122f035c689058691b8b68c28e3e2feb960cc90b5fdad2cf0063454c98056fb15f762d40ea5dd922b14222d574d4e0b87ec437e01010b8921a26bc3483357c81239004ad834996ab6e6072de1172d13b3a314f8483f731105c623213a5c561b2e2036cbedf334ed7b22f221d702a727b40c27b04162623be80905e7cc9a4d651bcc7947ad49b11b2b945ebc3c366ec451411304206741d38732f108a3d4f6c90369bc1b60122b88f935b1d74e5c7a6c501f7dc74e4030bee3e1d05af36e55dc58920df7354a602fab0d5c382c221070dfdae50918a263294749bcdd39b831e9d814e143667cdcade992ff6e6f8b5561c27448881b27fa8ffc333e1c5e62762fe753ce73f26a2d793a11c905a0a86741a82238117d246ae47f8f42bf65f3111c40222a8b89962af4259d12b6a90656c9cd752a1047a95f7614428fe5ffac7f61f567570c5462279f2180895c0c1608962c328c871488d8fcafccb37038aed4500a23396fe6cef0d42c25acfd2f2831820bb2f28367c86da23f582fdf99e80bdf05443e3cd08c54eaca4285aad8e413c69d8216bc4866384ddc42a6ab965885a083d88d21283cde1294dcae04f45487e52b9c1aea9d7cf24e8b0c5a108f09050e6e59d1410d6e7b4168ea0f7a2853c891a5c7d431b50193ad1b08ba2f69d9ac9fa57b7ddf5641de14606824d2d133d47a641d2ffda4fbb265139e28733899a45631446c5050eee5b2e8a7df90628f4334652126d874b6ed888e926d6174718dacca2a47bd9a766efc17302746d16914733c7b529a06609a1e2511a1c73cd79ca22bd62f8651d5a3f66a506accde7e704b8bf771c41e1eb04c061f364189c4b873f4e6a186c660b858102566c91e83f409ddaac4637e5e314499d009188b7b41be1394e00c7f7b3b18157e321e6525dc3d08adf567363a932577de6aad58ac56361e4c412e5214abedb7132ca68c86a5fb59e942a24b413305f8c0f6c4a0f52eb387d8fb1796a1da74240aecfba0322c880cdf72f1c39cd22c770e4cbc0b363c3c1157263f027249c500f1eb004d598da55862781a4f8122d70ddc53df28ac61da37b17d52f2b274b86d4f2ab15de37b32db7a2d97ba0d21968d9c7c0efa2315f79ce7d14a6b904446bb3ca92c827cbc4218e9099fec778799c147f6ae37083f74c5a7ce9a87885be5acf5077e3f093e6f8c24c0fe087e34af71572dad2ab3085465397cb6f0291a61f97ec23c9703b2c22d27d4b99d6c9e68f0f8740cf3a0abb434875d1111d5c7fbf86911789f053d49f3bca44652964800507de02473ce59b716257a8c059f3da51415652668b879f482db041c737ee41aaef84c3118faebcd7f2af42adb9947d13a9f568255bba055c9b7920fc636508ce79a9b98de6a05de4a6a506fb3d8b620d1002b981df8b07aafa22a099a2f84211a159374a871ca9728348ded20f3b30a49f5583d4208c42c1a9a0ccc3584b001602e2e3c47308dc1a882112e3b2cf6ae3843bcc06d6c9ab2417067ba1baafb4bb3fa623b3281504526718c7861813e1ead418eed80839790296900505adba06043d95efab3ca0a04a7e37eb7fc99c45070505a8017dac560730443f961a8d18f1de924ddab207da8a05328f7c38a45e3969e44db0c4314e0b9ce3ee404ac54b44ee800473f4b82b065f68966f982088bc9cabb83ca7803aba08e38682b20bf497c81224024f812719be280061cd0b4d15cc1c74da8362959fc19e7d01fe12fb02196c6d072622ba9d838f115397e0163f7ff490723bdd74591f12513a39e9995217def063d14a2b8ac78955c0e27c078a4a4e09414d39ed9096751032ea0341139d1302e1c0f1c55c3cea4bc1e2d0711d2c8a218305be3239d29472981f05faeafad98459f55cf4f2ac700db31cc5dd6cd6a0dd3b2650bea8222fad010786e4a8e2bfec7c595bbedfbed7561598294e2dfe452adfa908884c8b13cb5ebfdae5df7c0faab7ad5bacec19b54e84967f2a09d490cb4892aeedbac1603d9beb3b30bbe46c5728e26968fbd36be8531a91164c8edf66d29a97c267f1c241eafd97f1fb5105ad589e4fed285963dc09a45857ed6beb27392b040d1680ca457e0790fa0e0891481df039a147c10946272d7e48ffe9cef35530a2ccc969b5106d90d9d6abb0690b129cb756f8b91b3fdbaf9c2da6add27c41b20668813cd288da71e1b9335e638ac34ce12cba045d40280e6872df8a8f7046ad982c7d54375f3184ce14ddeb07029144e719917ce1266838053c6ee66a1814de84310b64ead62a96be17a0e7a10af48b2133012d920f36d652eb2faae56e715f1f0ab5c3b2d482371fbea69c19b9993444754d2594ca29934670408c81bb27115f478e81f8724eb999067937b5915555485650ed5ed7ab2576e97e138ee7f164411cfb50fbc2fd9480edbc6fc1175a2c5b7eb78a6d98a768332ab1da19e0041170097b2d9082195386c13893cdc9149614e29d3b7d9cd11c583d1669b0460d594fd919bd425616b11d283671b195ec69a603401df79db7732c04eb8599fed9dc5b62ec2d5c006b1f1f2d83dadfe4a7323b40093b9246717752cb7e65e90f62243747fe2987e856b7daeab152fc039fb6f26564833e6abd83a76a60ebeccf0d0bd6261e5a20139efd21ffdab2408860de85f0916287044d259a53f5e18efd01a2fb25d5078a44c0245000204e33e195703b8701e7bcfee2071e242ffc5fef1b18a64b6846569ac04a4347ec80fc18b374b720e3efd919dd78c2e8fcaf7e3182b4d8a7ab3167fdfa38af4d8b65bad2f82a8a60f8e9da2553f9e67e23572bcc447c072998d2118fa4787f5d65376b777dd27f698787bd990926284850ff3c5dd7657d186d4e4d3b2e6691da462a84a6a2c696c07d7e98f95629937534d08a6cf0652ad72ba6b64158e18549e183c274916d3c9dbb488b8ef5dc44b982ee0457b640f598c4065cef04ecd8cff4f9c048155231c75625a605029e381fd70b720abaf3748428bddec56d56d69e6d8906d32f95473717b571d0efbbbc53a63cffcc2e787f52d5b7b9d9b72ccc46766ca8dfa0cba132132def3b12c7132809df225714d2b5397be90865b2187157faf65fc07d9e7e7b402de36b258c51aa819da95529186bcacbc452977d3739e71f8370e65732031ee68ef6d6e6d74616df4e36a08a23ff30ac03536c2c60124438bf34a2826387723ae592f7f568f24777967fb9af202da9925a89fb58006e3db283dbe8fe38c201b9b421da8b3c9e9055c98bb2b4e9df5aaff21b4632e1dfb036558fb4a2b4669a3bf56cf60bb5c77b3aa117a4a15a518d13239e2206d09a4b53840dffe00091520df8d7e75dd39295d95882b21c454b4e209bd42b162830e8bf7f96548321abd195d698f371f1ff3ede47c121524228726f1c78a2a6e6b62316f5f400d6ca85433d2264441645cba220c317a7e9f6bdad69e29860da2d9ce801582d9371538fb0754989501bd20bd5510cdce6261cd1bf951f03fffaa11a541825e982f3d419bfc6affec3d64c9622bd93dab1e80028f7d21903a0ba14deca99079fcfee047be1b454fac45ab618ed190391cd69ba0f06d16964281e838874d30d674a05028418f28f6d4358e0ad2b3e187691c83ff119bcad2168130ac179160ffb4fafdd42a85ece6e0ed3836f8284c9fcc7badf5cfa08b6f8dd8c7de427e01671dcb9dfdfeb88efc756fad02b753f485d27fd0f10750384742e490811a0df075d8ddfb9f89b91db84317fa6a9f2f3311b441bb90d033862b2b0c4c2d4225b3a8e506f02d9a6809916169f40bdb1a2d3797a945a2e3035531ebaf0a4e5a22ff80ccda01c75bc80cb51a443ccd8118cba8f907997d1b6838c4381102cb7e118d85eb88742ba2d4a0f95500c28f3126532fe8d83e774f707a7ba15c4d572421e531e1ba9a48e30aa58f2df2481f4681d4e7a91a8060259610d72f3f492b93d817aa1931d83c134cdbc2b7cc210b0d8c59b39a1189c11abbf2fca25d2e4c9d7d5dc0c592970536423940d38adcedefe4dfb82eac5dfddbfccb4b87a9324ac25c5e1ee053662b7b8ff77860767065449710091edc4d29ddab46b14d2a6adc2941b3cfbf00da915e2fd80302fbe13439097f9c1df4fee84031a859817fb5bed1c0c059784b4dbbad6a6999763b10d92c422db5985b15f7371fc32185a012f832608ba05c8148322a76ecb4a58b305f8ecd5d1b754ae39015f3113d2217d24655704c9ab250d70d087e61283ac9556943440e8b9124e5528f6b1a95f9c8561254ad127131b99de066efef0d6e158e2b43eb83ed34efd80a36722e349d7851c3b4770ca23e7ecbd013ac9009c7846824896987a21aad29f745b60b826f636d467fd9e0775f4eaa3958b8ec6cdb55f3d1578625982c49e87ae04ac8a0de6bab2d6675200e91757c2bb587819e55c2d33ccfd11ea3e2f1ca580c0fe9156e49430c32c981fc9701e50e40f282de6968b85a51e2ad833d8a66dd101c8cc7d4f161d4e9b811329462e4c6ae3a2557d76fb94aaf870ff7dc450c59341956d140c72e1f0f39fac1a0561c5a5f5edfbe8dea88096c18f88fa291ab005ef4b2216aa29458a68f41baade82ee2b2a82dfef8b9e49f62a5649a069f27db03f547b51df5624b2d3bf161ac536a895251b01c5bc9c8ad48eaf58060d61158f9ace219d50c2ca508a2d24355a03a956a402a454aa0e531e032aab8939b72b92547547e51f27b532f8ea8a0520746e50db58a8112f8e2ac5e1459eed3ae78a16e36e939db1bc772f1b8ffe340df329d2dc963979daa1385b7f8eaf73eb4272ccda1860647b6a72e954feef0220216ed1ccce8e9bc69457002a777af9f22ad7bda26631d7eea35819c02ed20440d77cf196e08df6a3f2300efe61f79ed558f064d3f0b9321395a27e82b79f57306f9eb39d0af7c76e422602881804397bf92d2d44a7bda1f4ba809271d44bb70ae909de3994fdbe8b545444d99c14749e5487d8b050bb470e6075685859a74b31649786abc1ed2b6d4a391a6530c41bd959f0b28b98c60d631296d7e781992ea2e1dec56dd8effa14451979a531086b8a4c5fee084ab3d362512bf73c006bf11999c04a32c8f4eeca2ec226beaa6a6836a4d1df9d08f6bb0e674b95da4a1440f3d638a3455745610df21123aec08109e92333edf3a5ffdbd8d4b8ba265880616b8cdfb851dad088077c993e09c6a95cb2ac3ae40b705b141d088436924f5573beb879f80216e1141f172d22d4c1e4ea62a490644357b1d64773ec29d9ef171c8cbb37b23f265057b15220aa045525ebb81e9f744b2cb244bea8edfbf125fdc825232d730473366749b317f2387dc0274f1db006575d6b99f6c8aacb091e4fc25e3b773f9440751a52909a9949ae55ba77b6d4d42c9d0981bc22ed5e9b2ffa44230025ae146493177d735f1d22b9957ee5bb1f403b06e97018836f367281df1b48f3035563564c26d9b557e0547e2095fa815435778fd84c691f8f11d20b0e5081b8fd003146b9c759ce04f997e6b777e3d398be554d060d6aa4b37131692530c0de72f01002fc0154eb6c36e215a2b9ba11d78a68df73fcde1f7da2bb35c9318e8888abf3679812dfada81adf2a1b0acf9b7d6e473ade6bf448e515c282d6422ca9c4110020fce98f03311db4bd3e4148377e279ab0726ec545387e8ce818f0851ee904b87919cce6ae9b8772003e695dffeb83346f239bf4032c4a0ca4b165972ccee7073375c269b1f3892ba1a688052879b2972feb050b168257f6d7312f7430bf385664a798c70028f8e8d94ed3f18f30a09b56d608a366511e23401846c0aff18fe00902be637f440d0f704f10bc1f0c65df0ec7b99d0d12bef4a071e141b4f108f2f4d4b6206d81ae9a765b4c82f284132dc5abcf73ea1952007996a811fb5b4b6457023abde199091af2064132e320431942fd952c3056498d64fa05f51f22bf7316e9386603e4ca7219c2994f7b9845f076c62216b2c50604c9eb1e17c87927dc2893cd0b949164b79d9d61fd0be48d688196eee8b37907d00266c83d60b634e77b260b5fbefc8a0cb8442b1e118049424cb87af41f1c72045d7d07a20520ec027b11866771f705a6730e1e43751b74a880cf76bd4296339d0100ab47eea986981ff52e25b2eaa703764946fbd503f59667f74caefe5df9b697c35ee53c31a5007f6ff02b369c32f376cd79eb8638643e1505d9f516f0f08a10dc31d2bf0406f717a40658f9d85f8a74821239cc5c94368380cd22104fe216c1986c8cfc41e16692d7d038fb162a10492272c2a6d49dc8c2b8f0b2502dde31f63f0d5fb6166c86b0fc4b2208aad8fedd0e63532cdd22c64062926946040ba2d08a7492471c78a65a31fc0f14133c208f19214117ddcbcf057058942fbcbc4161e03e7322d4dfccb0aaf22b5978e5d272c24fa137a7254519f0edb52c4dd60e87b3a46608262dd904a925f1756d375addd971cb4f2a1c6a4a7c12daa2e88ee9fa0143f685d8ef0250dd1d607e87875c94360078bd7101c3b4a12c9278927a74848eda0ab9372818e7204fc10fdb7cc71c325af0cc343ecd59f3bd1a13152fb7277fbba9fe19c2f73f1b933e903102905149a4bc2a788436c2b70507fde4d05d1bc7fe754b9d37cd5d281030333aa96fb66d55aecb0cbdd3f7da1b21a365b6fb9d3bd64176dd31f02a3a4a206cf4007fa9c9679ba1a8568fb555e0a33e34b50f79b10d7be6cc7410c664199cd95bae9a45e56578c940c1b1dbd09f80caf048ffb6356e715935457c5ae1aeafcc326566f9de4cc490fe7de954d81056481e01d599199c571d3150e9c8126713dae268046af531b95619d89193a60b2d8ab5805b2aec567736760ddca112cbaa16e2d4759e4cffa8f665bf477d53d1964f6fcce8530092a21b9d772f013dc3aa6a0206f39f7baaaa9cd2b65956de2521a6a8be45c43d0db52ecddb49922b660b5604a1cd354cd91b836d4d65f58089b317bb44d02a9f14205b08b76188f868136d1b23a506fe4f2056489b403560d70052ada25d43b9563294bcc4abeb1a60c9abc093ade24904871266a0ec06e49b03c1319d7fb77a0815e7dfcd18a41f980105dd181baacd302bfdda9c7f0300084e40a23b1da504a871aeb02f73cd97ac9c7d95204101360c8ff53e2a8764c9cadbd090ad1adeb896f3ef8fc7129e5a2317068deb551efa5d2bf42217b55880c45009260c53f781af5b7f8e64a60eecf0db05c6892c8170089045591fc704d5f16f04dbfe4cd96a197352be4330b0be58e54652717f30a42eb86d5bac1742816a00473cce686b577db435f7585cfb5d466ba0477a75193da25fb4f50339ffeeebbdb9689192bdcaa6ddfcc73edaf9775ae78bb5ee6312d8bf95f90ecebfd5e69ee8ac9cb46e0eae81e67f958d9a1081136dca708f48c3cd561a0c5ccb01841747c4bcc58bad051054c5b03ffb01493d39ce3b6e2238ff16b5998ac60d1e461f2c37d41562b52b55fe72feed90c5519f284f863096b14ed4fdabc4275db3d389d30607a634a8cc1944cdd6e1fa911ea51f9ee97b54f3c7bab1dad92986554e8ab00c5b715c14253ca2b1af9ec9f910befe8163d522ef354f7dfb3bdf13025f5be44cd3f2e2843c53ad033c809f382f06dcc04c1e3395008e3d2593b9f16f7febb43bb7be8ee6cc7db664a6d6c288dbdfb79513b868d7866dfcb85aaf81d4d376065360a214f6cada5885b0f90e547f36350c20d8970680177930925522389cad097cd5c3b3eea7c73310712723889a4330fb7c17c069f39e600e0a26a0bb9a314821660aac6318bf907d6a3462e03bfad9540431962327bc34e4e7e36b017cdcf07f1663a89cb790a4fac4249f7ee4b8ce74b428c75dce83b65d8b3aecf96b50942ac0ca0c10fb1f83a8d25647a44b3e87870c9e97a16fbb4a4b7f55cd8cd493d952328be1f32f03ea1952b6a3270cd9ab0262904f02850e1c48daa14856e448205107e4f0b4036d3ec66ddce3cfa6d3cf339f980322210a43db702445fb8a4ac600b815f80fc11d951c7f50c218afce8573221f27062d3bc48cd9df9a76cdc2afaea9e7869831841bc3cc6fbdd93299e98a6729151657b83e6f62875e571d98a4b3df092d01dc80b64777cd0c8e119bc30487617fbbbc519250aae3e74ecd785a87605ff383ee08314218cd8e8d3c8619d6d0b25b08b0f7f78ac4108f59d452879e949a8290794dbe5390d15a8204d0f58bd450f30348b0914b78c71a4fd2ffa14c489a2f84bd92882625b66fa822540ea2c46964c857bca71101327f6b287461e0e523ab4e3298c52ab66a655f3283b16d5d613edc00adac5c64c54939f1304cda1257093071d2c378cedf910dd05260d4788cb129442005c27dad3526a7e3b39a7d2c6c78fd6eefe48d61d7232d0faef1ce7faf1d81b46c6b9ab63a7f5cb4c798e772c49dd557a1d224485286fdf2839b5eb50fbb5f8b60d090917e63b4d451924606885fba6c736bf2ca686fa384c427a0013286beb4c47c21ca6c525d1ae9107b7599cbd77b8ac45daf7ebc8ff9d7512cdec520cc3e38072d253ca9496dd868c95c1de7bcb2fef3bd765c239b84b42ef207bc75b62cd67781b7406e107bc6a6ac3650db599ea0838a17458810cd818b5a64137fa27afc622f06b188492cb114ab58c42c76318b5d6cc4109b18c428a662885d8cc514733188254662f9a0faa036bc2e2ec1cef97f3134cb59c6fe9dc2ff40e18a8246adb35705cfe3edf903856743f7c4439de7b9f9c449c56bb079eaace771dc727253f038da9c3f56793ee84e3a54799c9a4f9fd5bc0c5aa78e5a1ec6eda777051e63cdd94385e7e3fee4838a5730ebd47503df390ecb382ec1f962b2aeca84bde8f8aa162e8e5a87cd1eea8d975f3a26db7e6d87041da72815c40b3e1a71f838b31e40e51f49555ac5a164e4a22d9cf621658987b8f4650154c30ba4333cdd4ab8986c6c3d9bd20e9399329afeded505233b75e80f480ddd09ed2d8f9bc172fef3a7f894eb6a7f5fdfc96b1e5175fa3be79f7a85ab6a6ef9eecb275dd5ab68bbfe8bf1566fe9c89f015fdd25d1b040758a5a70e9c7deaedb79c0d002d2734360ce82418f1e7e457f6d7ae12502bbe999d564a520863d10c33a1d80194902e2629c72533f21c4c74818744a1190f9bdecda54bb784950b7d1c2a9a9045672314686e6143ac8850df6d895117ab103e5d57411292316b153d4b2fc9ec906870b2e4089ab15c1dc839562ca68de93ca1a5728985497d21586cb19edf551d354ec1293db0e5a32e73173801c9c28988209caa57f27576eb651c4c2f4e010282096fe0a63708f69a3f4a7b692a9fc3aa204faa4e051ad85dafbc563bfde7a11a4e7070756c638773a9ba8ae13036b61f6002f6c551e28b67703dc033867b0804f22d60be3af4c42635009c2f59d48de9b6e68692b0b838c2ce52c70d7cf4f16c695b3f576732876d8ab0c90dfed4f09dd79b3d28b5208d67c614cf83d46fb6ebb84a599c8431b181e06ad6ceb7f8e0ffec11f463e48276a618854f5342a8eee21e42ea568630a56e384003dcd006ab40f957585528357f434526e81355c1f78f8d3613f0d2c452121ae9ebed33d41f35cab4081531eb337faad9e569ed7ab6cca69c6e6690a28437fed56e51ae2f5e1a3fff5b49e119cab8197d476906169743df3d2c85037105316e55220531474bfb0d27a9a7e74535895f30a3605708e12a45a5c608a99441e198193c3d448a3d713e972d1aa98375167abe99b2c33a65c65f16fb62a188b9f6e79a80ee63197f93593b22eee50b3e97c092de008e2960ac6da655558a3194982bf53920174ca53615a3cdcad745310854554c4d9b729f6a5b78f4c79ff2c435211a0310f16c06b2af31d1f2ed06f5d76b1a54f215cdb4dc7c91d2873d690ca928dd865627c307cc4384d50d41c0a8e16421644f13dc163dc23f8843718361a6d411be31d8f781428c637dcd9a99865634f07be508abdd2f51ed85d69a688be5d85167f01c7a6a76b431e611ea646261e5cd05eb6730a85f09ebae16c7e03b6727850265e150a6abfc092a1023301c3c03d2caccfbfd58e6f3c7c00459af06ec694a2cdc843b597293b5de503fa919671dc3af8cb944daffaf94cd6b896840fb92dec6a598c375a7596b4bbea60ed9f79b5d8f2e76d775c31ad22dbef348572da344f76e969c85757b71dfaceb615e56ddb122f1cd9f82a6c54a278c00799b91e83e10425d5f19e7b1e159732d72ae72917e66d8a0ee66c0ada6b6b7e922375ed4d5fb66e82ba1501816ad6ad96cd723d708800c19600861f4d1ebd7e35ac8e6a89b992b4081eec28c668723e148c387735caad5bb76b8251c7d5e2b568ab44871f61f0359094efefe76e1ee50d17978ac0687aca595c73926dc39ce7abeb8341d974d45b6e21ff4478b6c2afa2c2f046e363138627a889314dcc8bd3c0634bbec5c15a1c6b83fa62d559c06f3113700b7253d96429bcf3f1584aab5be9cf596603636a91fbc1caec3bcffbf636aa8b4efacd7a68629e33de05bcbc484b660a31de6ba371423fec87eb16091c9a817103e71fce01707274bc1c5d83ecca0f66ee1ebc4aa35addee3895c058464194845d1dafcb0c459e41c327331471cb2c2c701c6d08d8cef4dc8912d717bc29b5a93d22009a203520fc2cd30c781fa7cc55ca2a30b39e7694e681f32c603ed50e932ad5b66920a485ac438cc369e6558ff914efcdc090fd8594214b9f3e652fd15254421c558efeb45568059fad1a16a815642a04a3e02aabf601c2d552d4091db70fa17d5c2b34a09e653bb759eb4ef19ab34cc34f446fa1bcbb5e5b0af34526d03525308493f37343f74a41b32ccdf406758e8290620f949966fb7f3aa909d4166858f50355b0dc034f20f74ae00b2772d7a4ecbaf294ca3a843ef432a899c17d766e379d1f8826684bfb993782166505e5fb2178a5888b0806974aefa33fb816cdfee28e99502a095fc6203b905bea6e79dd83d27197236d2842282f458d5a99763ba828a9d1a18bbc9a3c6c1fc29e93866e16015c2f66af753f47e9f8282b7405d177708b9b5f35cdb38783239668b79916104545e7e94a0516b12320451022ec1a6d734bfa0d2e295734d5693429d8ae316ac7f487e65b4096feee83a6a385f35dfb44cab51bae73d82db26a01d4bebe5ae283f5ba9898e52b83944c8eccccf1cd80c57471e690642d59a32780d5386ecb222697d6ac932786472acdb66aca4534386ab997080e23898a3c27e5dc566da879a0a9360d1b3c9fb8759ee801c5e4f15c5b50dc628cb791187e6288894b987ef2f101f54a9c915576209afca34f10ea5ae183525dc0146b193e031e4490acff408fcc3651e7117c3eef2ff3aac8f2dc8c7be5c02bc68f2634f143603893d9d5ddc869e89b76add8067f9762fb7fd8553bb56433851755cc75788acda8dda23ba7e6f1c69a60b6edb932cff09cdf411f45bd718c3ad034ec7a7dd0ba0c09e7d1b15a65e5e0e12af7bca2f0533cb10c00663bd80887eed81f7a0d1ffb6f8da899d65d62779932fa20535a20a688d20071b2a241f642a0709a77a875d6f0cd161bc583413de01325bca7ae0407f4006f5b57ae037fcc6ad0ceee78041b3ead096342074f0c36292cf74875dd0487164e4aef8e7a30e4fb1b311b8ab5433e05c7814f59f7140286fc13b63b2e6e5c71720b3c278d67fa0d59d2667b7ae2678bb70d651b7b0e79b5ae61b3703c27341885c7ca07e0a726f66c6263e4fd504a1b1b04bba7b8a9765fd4cb1b9edac491fe75d3cb13f287273c612d62037ece9f92a94d6efb73d6a74d8b878c36192342b40220ee292dd88d3da049616162429a45871a66e22186f2547f54d7374c3a39829889c0e84911a6e634787e94f92d7086319197fe8d021cbd8dc54829be543948cbe2b8aacec34202c541e2854a4b6a30074bde60b1313e65362908f729efba3dc3d92c714ccfb2a2131f1a9039a6d93946a4b04c24e7a8c5dade7cac4503c965d02476816d559c31373000786ad66e8695f53fb483607b91caaf4b169c6df68d6d8ba3b039132b5188479ef17c16d0deea0acb4a7f961546d801fe6295e069a96e34b9bebe6fa91266ada1fa212ead33b1001f5013249bb947a429f68e1213c3e1c85029207415b75db32bb1842fdd768eac568322a691edc48c728763b9fc7a17c5f71f48d9c669c648284e6df56aa47bd84b712297e0342ebdd31d07484b5e716474a71a064fb838bd8dd5391d30e1a537c1445246f74d4b92eedc2990b8c40ea10a857777a08469b9c8fd8b256a4d6da9827d13a11bbcd49565658e08558db8811474c0c086bd1e8a25c100f51c1ce9dc0898285ca6478d56e734a8023063239f4a30645ae0aba737fa5186d9cdb2580cc54bc20058d276dd099e59c904ffceb2dd13f8c7682b62f9e4aeacb1d434b22762707683a50d46715b63035284f337c0bcdca6b13cd4b943d24cfaab738de42092c089519287fea91fb40d8223b7116a4a0a2080bdd7dbb2f4eedede146444ac266ea2df7aadd47c7fa3ad9b9832fdc966710417ad6028bb24ceca791ff22ef5e71c57f98a2aa8a20a6b8ae88881f85f489eafabefa7c901f5910e958711cef1c78742a288e9814b8e87f0a46e45ed94616c4a0e14b5f1857818b959ba3aa0ad396a551c9e86bd385413de9a165989cde21bc5263cffa6f2db55f99d18ab9f78ad23455727ea3ffe282753397ffbcb681e103d1cdcfd5b0a6e9cbaa1bb35a14084869ab427dde546b793552df0d217a45c6e885b9cd344b972910370c6130b7bdbfab1ee51af35e68f54207c27dbe106f527d660db7abdcf05c653dcf5754f0ceda6c2f6a530684de50fb8fdb27058db71b3495b7fa061a22da7c68d02c004ce048e861253a0537156899625d67685cee75f2856bba9f8540c62e01c3ec1f5f927527d639330bd65485abb652a14aed4c6d019e4497172183ce7196056f4f98f7ea348cca50766cef58da64849e0cc30249c5192a8ce86df5641e5b1bbfb5ae7d60f5962c1f5d08a38f9e519895633c129fdb9313a46c01b7b018a2eb0461d5d0ea377cc922d56ca7698016c92f2ab43a74cc6cf49cb8efa4ef94746ca4cf543f49ed0522498177aac8e9a43e330b2fd2a4ba5fc201f576e6388e055f20c722a7659dcc29f5052795aee158b618ddf62ab74d0c5cd1320013f4d4ee186ef20852a0d6056111cff4c4eb9a8238ca97092cd2e65fb265335d1001e91ec5b456665a93fa852205be7a472f0932e199794bf2d0249a6ea49a14aac1df147ff14713d02373231c21bb9d2bf3a48a9d3942c81884bccfe2057a338c869b917adf4d4af062665a182966453cac1a89daba2e2eec89771621df36daa7e2167eca18fbda0d98cbb51e6bf0721e353fe3dfd194cd2c538c1ae3d39f22c089e96423cc5ce829a7c63d3ba68371cec95333f25d44d0dbc0e9cd950c7a140090c04599c2faf5e139df5f699a035b86e2f19840fbfc8dc6e139fa072574bca5c8291615e1b515670880bbf72a8e1a36a97772a6a090a8608bc520df35f6c7f76cc8576de4d452d8b528be9589a82769df80584939883c4dced76abf6f8e932f0c0448b65eb0207d1603b09e36129f2e10be0e41466b75a4b8328a6eea632670f7a35aedf3e300957e8da8b410c98fde0d1dc43e3aa08269b33320e694b4f25eca1b8024d6278fc6cf4aca0b53d07302c3f87d0a0a1d268585b41fcf2dfe033d19c3fd25ab9170a784739de3eff5bcbd4818bce63116a98e636f5bc6e2aae8bedcde5bce35c0fcbfd535591f0102964b91d79206005587ea61ca1d5cff8f534946364d7735171aa0bd9fcc951457791bdee3c80eb4a30bd1ebdbc12ee32c0dc2a37d23ecc21f94276e219b22a634d571eab448a59078dfa105e0ad08aad2aff3bd94fba4cbf8cd4ed81057ef11609048312e090fd9402579ef57494cc29091d810129afa4a302cc855325ce83f940dac4ae195d849d00df031cd4459885e3ce48b8250e18c441b3366639939f6901358c4ce274165e9403512220be273320ad468c087c91b406114219f00a1d1d5257e4444304aa0b8fb2708c43c0133134880d9604a017e83b4400ced98bb4449fb5ad1b80fa8138814cde6c07e67c834e1f228c4e846b514c59990b59edb4cc6a48c1e7aeeff1a5cb1962fc47f8d23e58b4222dd74b1bb346a5c5e7fac800b9ae5172cb754e331e704b7ea4d134a8be13c5332742ce79f47f21e15583e70cdb268cc0bf29b4a3220acf9ccf6d3712430a34b54f1968d5bd386e5a75964b9945cd3cfb89ef4b3e743e669bc07a61965eb3f8261043877dcde449358519d90e0733bccc8cde9098ebe03e6e0b1a41bb028047b3e9868e4c0b448b2db4763be205d89f95e2bf9650c5fc17c825ac12c319b537e83fa3805363d9b47c619518ca75d91256cdc4afd8cb316da1208b35b61dbf6f89eac053a833bee37bde98f6533eadcbc8351d893e11c85e32781ee26ed5fe6d823fb87a21274bf199f1520208209374185402d5182b2a81a9a271588e2eb8173e2ef03f96db2610503511db9757f6c07a5b0ca3862ff5202f6484eef6f4c716a85e839b02b11775964babf2fe5e8bae8cb38c91bfb487f8ca6bf34d63b1090f02cd98cdee190fe884df4fde817ad8c68cb069ed3c708e09b337b884b4c2c9157e24be6c3e502e7172c7a4ba8fb302f36bd11d8244650e1bef94d7be548dbd59ccc55eb2f6d40b2ea3390b9d33d8d9184dba8e9b0a5050555964e88d304010c6a35d2aa2e6f01a94558c75592b907fc6f82c9c04a1aa71d2bb6e4c8066db22ace1df8d14304a3509809a5efd5466fc0664106f1e911d6204eecfe18a44b7f6a1fc0014b6af3f01ed6e36e8dfde4a52189262090828c08e2c953994d9bacd5d8c134f944903af592fb94cd6540b0e3dc040c6e675125156c3215f6074dd730eab6b1ea9e238b328a2f28d336675f95f8bc0586f8315b32bcd8806ac66af669dec8c8727577a93af0c4f482055913c7f2bf4ac85763482a11dd59255a67fe0b8fcb9a0e451922396c21305adae3a8ca08d9de20e75b5563ed1146e78ccc7f902c541ceb382d045faf7c301da460333b8ccd077976cf59aed8ae75f8d8b8c0230ae6233708f6d03a0d2e95ca77d8614018f9eefa54f39bab0c73b979a08579ca1a04921b61e566773b43f70894ef91cdd27000f7ca1078ac3490222ca4f265ebaafed9d05c1d8bd406cc37159477c4ccec90e76cc6891c9e004e3af655f4a2a2328528098549e043d168c46b743ece8e94ec7797cd32bb61855a9f9261240dbafde331117037c88e4923b4f101c4393b11c5183e75ae23c9985cab1c320023c14e837e6ec5ba907931f91c528a1ceaf1f15e0450a9f1887f3e6f05161d97306e1e3fd77a012ecd025c77c23f9918563f1765007b3deb4a3808462b458406f1b182a2f168b30a8e4d0985219a927c97ae89fe29dc9f9f145b6c51539c815c98d557cde01d5b1a55fd41664fa9ef4f347d570343053045842c028bb3c2257bd0d888fc099109d3f8173c0dbf0c2a63f54a3a08861960603d22db0e9d547f9656b66ad7002cc40ebbeb4a897aabc17711074ac74b6c2a39cc19cfabe474785d0db0befd8b6301ddbb8b225e10b851cb170558361223f95cb1a06a001e0710e734b2d33618ad3fb50a00e323859f3b3f3d39acb2fa69e9fa20498601c41d3eccd79a9184285144321db3c472655c1b16430ac7f3f477348310d40592707ab5362ca77bfc8e8e0240938261dea5b51e1936be86d15ec546b6741bb02a7f2c02f84e42211f99a11b2fb24e6afa348e00bbba1a5c67644442b83437a08dc0cea55732e86409e9d0a00ecb8e63d5a6b41ae8b4c4a387cc069eb459065fe3a0ddc27ce177f30c35c58893855037ac46e81e69f64d65036aea0589f112d5df7309a736091e436c8c436ab7d78ccea01147bef111e8f711d5108776c32cff83587533b619c3ea38bcc5be221950b5e364c970fbfaa1aa774d6b710b8567de204d00b6db2c250ba75cdc61efa2c9851ffd3a83f2c4e99b751b9f5582b2f18ca3f4ccacdf93cf8bafc03f47365becaa738534c1fd2409ba614250c600f67e9c5fcde4a71f5198e273641945d96afdf4efc8cbcaefe62c8237931987bde611ac01c8cdb89c603cbff4728e37c528984041095560aceb56883624745101e72840ddb48c2b500800878a89a7007ea2b0c4056c8d8a16d41d4383b7200d78abfb175b1ce373163dc2fd49b86c7a38f9725000122b3534b6177d489e4476c3594abf088d5ebe3f1820cb135e60dc106dc381270bf225e6d26271a324cfc02a6b9a385c719cd0751f4b68a70a37ff43d06f2d53d0f8204676f433073e5351102c1ae50a5064f9d747d0b2414a0abca0eaeaa96e475925a0b22bc3e12036032166af1591d510be94456b311640681c48e2bb4aa16584da403397f658ba6a188e5bde202a3040c78123b96b433d88622d5dbb4050d8a7afd91b71a1ecca1c5b507006c4c585116c5f0f915ee5ac0b33da68140657e665a15b13d80358b5a323d09abd59546502421456b8672739de6554fda8d9ba3c8bbfd2992ffb5080ed37d74f82ca8a83479ac913f03656736567d73648a62a09dd8fb20c97549567ec652c4d978419246431e4c862c56f89374b47092584b4933b9e3a37653df8356118214e45d727911859a1666709a0a866768a919c6b7f9d14be9f1fcd4c165521956dd5c7aee11115d513bbb2d68425aceb6594a777fafe735b5706232990087a2f13ac3bccab356c287908da53faab829b2f5e48cc0cab976191012bc55cced1efe80d6ff07f7b56df8510237193c6c17c4a4fcad4f2b624332f8a2dde1e45ffed36d9a3243c9c2ca38828763d9c41e9b3763cbe522722bcda31dc4ca155f37875e13a1cb33aeb442521640f9cee068cf57ac507710664cebf527c3e5a36ebb929cbeecfd1c5871a78e56f2bad4869a4968da52a25c5819b4f46da82124888b7c03b6f8e40f5c696ca8b9c4bee281f43e2e789927277198d4239f5457316a0c69afeaa165aaff562622ec08de2a3d7362aadffafbef4b5056defba6b9ec64dc45b9859c150e5a20eefcd5765a235a15fdb25b1a25610c9e0a819857cb43a8de43fae5a0cb4dbea90d153f6817e336ae04b8980cc3a49fb30794a9261ea0018bc5996e0ed0c2d076ed98522898729c89e6c7e6bb63671ad847ff5e67206908093cd1f6d81a944563609aa2a5d7ddb55652ec2024d58e6a18ed0cc847cb950b434cdd1f552c03b26a106a061c358e241606c9111a2fb54f605a3899b05ebcc01970a8b659ce10e19edaca1d904122570ceddcdfd80ce805751e9c40e314106dae6dcfc99ba2e509998d5a3b3474c4d95a049db4f2003decc7d80469e2625e7a59e7c1211f4a1b9c7facb2655825ec6a5ce29e25d84dc3fd8560efc93e256e0118352eea96ddc250112353794ff03d72eb79f96753f073ad48e4291e620389cdd3a1b00d116027b8142218d2184e3703346e641e3afa9e52034d035467e5cd36bdd511bc663fba7422ad34a922622db258e1b0ff213bbebbdea14252699a3e1bd4d9f0cd080860c75f430fb523e83cb0913f9408868a4292878fb39a25ff75b2a1e1220d422abd9a64c3732fe8af434a560b295c6d35212641c66b93b7abdecde36fe941dd080287300522ba6f1b75be209bf5c9ee5153370ad5206b9bdc73324b2f05dfda2c3d1cd3e15099ba148184c4a69c1aeb0f3e2bb9e4d17daebccf99e6300a0197a6acbd561df94dad042ec887f9811623bf70b00fd4436794989e1e79a4d413f215fe447090efc2851fc48fd06f279a8701ed45c3b57e5fcce66fd6d95479503ab60d5bb67f28db5037abc3d0973f9345425d11ccaf14e2c27d42265f8c40b57f9ca88d4f4f500071117daa5d92172e6df97cb52b68a58e0af66689a4298baa1eebe79b714809edc426bf93cfad18deaf5c79dd20c3d2506bb01952cc9f6e85cb93c1233e0db9069872b8adb609f4c8b0d211416436677a789b9c58650cabe94075d8f77cd998964fb15cd002fd8cfd9d25a003951aae13f41cb69d533cb0a048ed8b9bacaaa2ff941af186a61d741b356bc738898229ac56c6543c4640cdb4c069ae31a2457c750e8f9a6f280fd0a1f176b20f2e8e3f399648c66f1457b7f7f634b2b26dfc931ed80f53061d9a50dfb3f2f4aa2a5e720d1786b7805ef5b467f5c16ecab2aad4bd77c82e25d7586999633dddefd1cffd01736e88473c5b6f1099574538d5a7738d6b1cc304539dc008a6ad76e246a951bd86a88d0d32bd6c1c5310a29212f7e11ca1391ab441ae5469bbac713a5065862067fe697b15764acfdd19fb4389548aefc457f27032c6984834cfa2e9d706f6258d474d725a20f22a864f49cdbad164112d7dd79af522e6197a8d1a9e0a4816ecedf400405a656a8d6fc6932ec1f88329d51afa66ac24aef5cf9e61340f4706d8204b2846db1b73d1ea0e03b7e4a5ae3479a0aa03d7d0adc3cb2bfe78148c2f324e5310fc32167f10512a4e3e6f4ed4432362957838a9f14d74cc98b4a1681f98c276618492c310f2291e2b458f159fab3f3833ae869fbe91b39985403f86ab1d9220ab6b85caa6f2650f49c6735f560bab920dcfd2646d83475eb3e077221100c84d6790786f6a21228ed03f12cd977cfc3f9248b53a155a3886954bb0aa03d34d92e26bd235194bba6280e3a7f3484cc2841e27a2542942021569acd84d7a5d418fae89fe68f88256014c5e049e4cc4770e56105887dc1c7a86ca1fe539f5efb0121e47b58385cb3f3cbfbc4abbdbc4e91c0820ff610f2902ff188dac89eaea2d7a31c3ce29b909c204735c736872cedd879806a2bf27e9a3a41ae7caed4f90f5f6b6d1922d6592295d0ef00d000fa74725f1bebbfe8f85512cd15bb74d3a78e4e72d2751b76f37297233781ce4657d6f59de775e182eb13fc37f477c2dab93e0b7a305bdefee5f7192d377acd3cfec376b8602c324dedff1bff7581e26b16f2249fba8f0075ffbf7efa902f72f0ff6f9cfebdcba141454f183145d9288420bc25808ac2e98ebc4ca5bf8bb071e45145c740085194cb1842dec037cc7b29af45107f6ebf66f78a4490be67625f0b77fad13201450d084329d17d24dac8bc394873faa0755a83f791e089e7e428cc14fd18c28eca14014ca031fe58148502fc4432d2e1ec41310fd6bbcc036a66cb9de87ed191849de9ae2ad2ab766694bd2161a5c7ce78452ba1e06c118321e63ce0be9364e178b0d168ac4a00782f75eae0be74413de72bc459ccdee3de2fa8e0dfc4d7aa382b8963c538335c3648defa371e369dcb0a81bacef67cc907283068dafc19aa1aa90345ec85dd1e8505cf8e385a71a0f3e88ef57c3060fe47d0cde8bb778d8c3d7f3700dbcc5310d63d01b3906884a388f8b0f97b0f098fee0ff014110630ff585d7fb401b2fc43d1b2c0f75f21e3cb16622794118e4fd7e066b8622ef8c19bf5aad1ea4c13ad5b0610cb8061e3f95d7248d197f5fc85db17a868c8b0545c2f0241734637b4b54a5a490e065994e56098376c9036d10a00983d756b138a607b1c92a79cbf4971504fec1f731c617775e7ab052448e860dc0279640865cdd08435a2ac87e62094e20417b599e145f22ddc33ff74b3d07a35ba562d1a0dec50a3481a7cbea93c913bde518e3d388576f31b8fa136b85df84c9ac6ed0a8f23e342c1632f8c6f79fc0bfe1cf0dd722c58c6f61b9122c6fc2bbb83be1355010c1774ebf833d5cf256d8cd7025ec346e8d1b061c67b8c6384391e06a9cc120c171460bad9c58a1920963102f19bb292326d32d21ce5ccb9de28af11d1b6098301472675248d6c9b5f4abfd8ab7b0f811ed4b7d040e7fc07055e36bfc0c04b90888a6074de3cd491be3eaa4962aef43e39f80f8f14ae271869fb622418c4156dbc0644ea71bfe981e34996c8c356ad4a8710abb26444ce6fb6eb8c449d383a7f4e7f4a2e9c45a79ab3b9417b6b7ac89c6af4edfe1cf8a06ebb4024d2cf0572cd6699c391612371eb328e32d7fce03a23f6ef7fff0512ede65b0660ee6c54a64cd60a852a811f458337caf8731e856c0932b9d4c7e59bee4562c0e9e4921bdcfebde831126e9765490b734e3da6041a342d66c45862afb1d4723a2ac901b4a114de112eeada36c87342fc4395688e242311cbdd329fc3089ea4f8fa3882bf1455fe29c1b43d538b3e17fa1111c7e7fbb92155f12c39f9063b55da17e75798eac64fc8b079f267c57b23f54a15f21bf0f5f883fa7f299f558335f3a9d1efbe9345a7b3a7d873158558ee7348f7af18f5c9ea15be3bf1a658ce1db7186247cfbe15bee5d4994f128f11b9fd83f85e16a062afc1926c54e8629cad72b6fe44c2817ee45a552a9543427d4683fe7f2ac628c2f4657121fb3faf2b8f89f7d472e8f8b71e88eae0a9f267c8b8a8146f5a85035da40fdf7e20f5d1ed1fe67432334279a8fa27a57b9ca94a21967aa14d145944aa552b9950f253acf7dab7af0ed9852fde933dd500018654319a490347f7af13d8ce1f422929338b6b7c47116fef7586419119f7b4ea4119f7bee454f153eea55a990b622c1d1ad589cfba05bb13fac2f911fe6f065d9548c2e7f3aa1def4f83fdfa1a16cf79c29346113eaf1698667a8204e412805a1aa8242a1be300c8308c3d106113ef760db204294b7eeaafb8088faf041cf5a93352da19482a812849213246895ba0f8856e97613f8ecc7323da89aa1c8f0f4f7041a39d95314f29e4623a7ef1e87484edf8d34f2bee7423cb4627156de8792f259079345ecc40f95c4f08a182eb9d28baef42b95a74295a85279ca5722195a09ad8456422b5916c025540a9742a5f04ab8e44aab952bb9d20a7f28d261ac3e55eafbbed4f721abb10883fc5cfc54295f8556422bd7b30097702905830b969488c595520ffeccb960b93ca17fc8c2aa7126c220ddeb54aa7186223d0586ac952bb9d26ae54aab556825b4125ae14236005c8a2c2f5e844ae1951763b8145a79f1a1d28bf0ca8b70c995fe852bfdca85f833172f6e7c2e30cd2ab4125a09ad64112d07c5182c478c5c3ed4873a5914136044181d105797c78201b302b35a8181419829c45c316629540aad34c1a50b9726ba7069e570e0c6e3b069e58456c22b1d13e2eac6e3b069e57040fcdeae6e3c0e9b564e0b0c1818ac6ed8845642a515182ce4ccc1d40003c60a0a5402b37acbe534ae422b63504a60f71ca7e25e65ef673a810fa2dea4528dd704be09a50adf640a416f168657157e2a35863615a6b00af5a9ef94831fb250415c4f59fc032d61b478d182454b155207d95f3030c50cdbb7a3f78ebaa329895f93c9e4f5e9823f0b8d3c71d3173efebeef1b69524fc36aaf498d334ca65cf5a22a157ee0f899c69994bedcdf15378ab88a3263c816c8fe72812ee4970b6c217790fd25892cb290dd7b4dde9e617275f2897d6e6c6fecf0c7fb0e61d4387dbb674abadd77e098a57bf7995dba3ce2771fbe88c9f03b150b4586e3ec43b1da6bc0ef587e1aef9b460f8dcca43cb9e3ccae9ead72c719f6c627f6bbe7ba118f33fbf76639e18eca170c4041e620fb0b06b07cd99205698720fb8b963124a8fa6c38e859eb7d0af5296b7a7b72c1bd1127c317e252c84fbdea51ac192651a97761df45ea512e1e358ae30ca544a63a511c5dfc283e0d0deac5ee2dcd37ce30e9b950e1efd48be3ec234fefbd2a35626f7d286fd9d338ebde64c3ff1b47acb2dc04ac921dfb65936ca031ea6451e3d580f8c4f4dc8752429c89e2fd8fb4e2898b6dc27671eb84170b85ef74e34c8ad799469bc55b374b12bec36df19d6f039cbdcdd9b1fb2dde6940b4e44ccc80f8bd1561ac6ee0f8560c2039b3e4ecf40503559e8beff4170c24d91055c5e2701c9e39179be5d6dc4705617fd87126c59ec67166ab7069824853a598de66f9ba9305b1384211a92f471071042d261509f00ec031dd320215607704223aae6b1de4708404a0301450b7c3410e474001e500840fc886304140617490c3182f0c50d089ec278ec0c584839b83c4beff6a25055b5b44989c1c2e0ee26c725adee2c2e82007ccd958cbc37738dcba453220da6fd900057155f41067dfd730b0ddc79e01dc217ede7220c4bf2ccb91357a88b316992ce40c07e92b8bc3ff06aa4c0b6263900571e56ec700882e04c42136eeba0e002277646415b1e48b34e00a62631552b861439cb24453799fcf7bb80ec7715cc8755444fc2871ca1298fb11c45aee83a23d027e8700f1247a3f6509ee1fc7c7c4d44d11bf14c02296f46c702cdb4830892f899f6beba316fbd8da2040cf365680d8897d6b6c14b1a41d3fef3b2a62f7383c0ee3cf77ec8ebfe5461b782c6239b202d8768b7b2bc57770d3143b2ed17d0dfc6077c31f8c2dee7620efe3b01bbd91e0182e471a2009f7de38dc73b83b4037a6ddaf1fd9b8ebbc6eb47dc3f570b5d817e23709f08adc73dc95622d0da08b5396f01568bf71b0a0b83efe0d83b42b6fd9f71dd40c063913510ee821ce5aee45024d90fe36cee33eceb224c7ba7185ec2d9ca0010b18e1074838210b9f46d1a0bc855b025a2c793f00e215685045ca152666300311dc200b1848800807885fc0bc6004099618c10d764046050d1022d5046922fb091e60e165b62a028b2b6e2d16537600851d28c13ac10b05aa9c21bd64e981972c4ff092250b2f4c2c91ed858924d205d95e38300412544d5962092f36916cd68ac953f358416c7a7ba3bb65e8233dda6041fb20d864e31dcbbd65d9707ace5b4650e40c0441d0f41ecbf4f5e4bc0d0e6f591bbcd516c738ebb7c15bfee0cfec0379cb06dbffc48e3604cd7a7c2b44ffcf07d22d105140fdacb5403da19540fad3ec0f1816c777bc6f2390ce83dfdf89cc774cef7d04b2fbcffeb0572c8ec502c3feb047581cbbf4a1505848d702de407510c8212cd5d02d7f2f07a4fb80f4b77d65d6589058ec8f1b90fe4fde22d12f1aec113d701cb02c07e43b76d6ffbd0d8bf47bdfe4294c77eb1b57dee281ec1e6f04d500d928c4d9eab3f460a538a7fa8057c57f4e0e8f1e20fda2099c80a0213441053f88514111587cf0103ffc60058a05281fa4c8117b7278e49868b200020158088c5bf821073f0081470f902f409c804c91fd05082ea00ac06e8a58437c61c9c6477cc1b264bbb1247d818019945b0b013210c0e28a2f4528f1050b1457cc172c4d903314f9054b07c822b28034563364747ff78b41cedcb13b86f1c2058da84a8528f064fabc8ec3d76d7bbb778d1a29f1ca294ce75d8771bbf707c6abd25e157cddb69fc2d8ae518ebd65410fd5a1b5a82efcf94edf07c35bee71237e93f7733ab9f5c66e0e25aebafbbb176b0182aaeeeb6fadb5d671163c60212d0f59c83b629f0282e0ec9a21bbad7bf7cd45d93362f7597fda35c39272ad7f60a971dfdc8fc2a514fb893059c8991786a379614818e04fecc7b41579efca052f5e5861c60a32e267bb8d08f3a99ef8128132d8881260115c7cef12e6d2dee26e6085186bc6082d6eed1723923e23b22461bb3fa5cf0a1558b7d6afbb1528c020b06203e48ac3d88a2c5cc771aa212ccb8a29e4eaf3bc2ab2a882cc521562aa60c167fa3e0fcca7f4a14a603a994c556ce0049e4e3809339d14e20cb29fa842081005824f5441a58a1f4817a958022a44a170d28a0a2cc2541896a1420ca602cc47050952aa548a0a2da41df35171844a54a9a850faa6d8429c4209342e68684c51a03e21b810e3e2850b174f4c11c5135338f102c68b17282ea829b27c532c4da18027a4280303150346a8833d63b92bc5186c032c0514e4bd2c299a90620bf984145962c88811438aa54bf613522861b29f90a2cac3c842c60c19327a00d6a09302d360053350ab1933ae9c9cc8512289276870e5091a287d67c0158dd54a461068d4a04123c614356cd4a8f1c40ca0c04fcc0004e4ca5b369e988105da468d1ba9ee9b1e6f4a90039b455b6bad756bad75d40dfbdccb2ccddd31c61c57a5ebaa785e95cffbbecf851420b6edde472cc900118bedd72df60e984c9fc9143e74f4e8e0d1b1a3a3870e1e3a76e8d0a14347478e0e1f3a3d3a3c3a3b3a3d7478e8ecd0d1a1a3a393a3e323a72787276727a7470e8f9c1d393a72747272725e35d71bd1bf6a66d9fa2f3576de50a22f427fbda1dd2d01b775f7b7e840bd9a349bfa436b39e4e5cc68d14aa0bb63b4e8abee2839769a4f2ba1a0b59c7677d8cdc5609f882a23ba5b468b3601ddeda2457bc50baf9aad3f33ed817c26fa18ed86cad4af8165286aea8aa1359cb5569453148643c4a3a8ec2182e9e8e84104db9179720f0f5151118f1e3c3d652682e5e410f9c821e22182ede881035bd55b8aa3a6aeb556d4fed3ddab16edd7dd4774b7edee4ed134cd7f961fcb677a5b73fa276aaee57ae65aa6d96ae6af6699ae6f749aeb5ad652b4e8cbee36a2bb23d0dd45e0eec6d2dd2fbaad93ee8ea145fb6aea8265284247658a034b551c184aa395384869991299b4eee6ba7decee1b2db60d862954800004464c33bc2851238216f000848313041545c1230828da4f8bf5839e302f8e3ed713b01041094d0149987ac69a09e88479412308d7b5a94470c030ab82000b7c527808b880ed252989eceb5dc480e07df08edda4946b0500f6dbccbced370aba65b5146b77c0eff29c9e872955de87e381871efceff7601f7f0fa6ffbe471ea8bc0f37f2d08369acf23eb687eebfd11b7be8ec732310aafe1e3a7b5a1615fcf69d35a50a154bc57e97c7abe02955f0b8840e305059420718c8275596d00106f2be0eed85bc36303105132b20574800bbbbfbbb876a87683187fba639af4b8e24dbf5e5c1d884393cf36ab6e06ab4e830c6230c480f437a60bc2eb7c6df1bd30d419c7962bc2becfb7b633c31165bdc1f0ae442360038cc753981dfb0ab31137e8d16618e29a12a47922af5dfe5a1f9145e57ac7a1a560aecb8bf1e82355bd46c51b3c5f594e3284e35666ab4c89154a3458e29399230f6662d30aaeec3a713f8370cd99d5e9c9d5423ed8a49a5c213eaf4e0699c996ab6a8d9c23361ee0858146bccd46891638a58b3458d19f16bb410734c115de448c264f72e6868c6998bd98a69dec5c360f5a54989355bd46c51b3c51da372ae0ad838665e994b845bc68ea622e014caa6503e109f98bc3046106797cce5f1c278612e192f8ce83b5e9830be13fe27c67752578cf17ae09161a1fefbaf468bd064aa3153b3c5e841e12d2fdeba9e170f0a0f4c118870cd24d56cf179607c202611e192f1728950e666e13b384cca91f47d5e182f8c37c525738970b5a8d1a2c60c1994e9c4b2a66fb4974ccd165e991e5c8a774131297bbd334d71378520f737e5e3cc7e8a158e36546ca8d850b9770c78bbeefb3ecfce56530762d0f47d3654441c021a94cd140fdb50b1a13205db28619b2a268fe63dbc9a52e2987afc36546ca8d850c14945808d63f679c16fa78ca7206094c9a23ed30fc427a72f8b10c4a50f2f99be6f8befa094e0f235e13b5d58534e6f9374e2c6efcab7f461f996be2b1f16b00b02676f735b6ca8d8289d3e2cdd0f3a32a2f7f65b32fd4ef7a74f02bc5b6ca8581c703c9db27c59be249678ac4f1236542c4e6733c526092fd95099a952ac1d6da87c427c4964876d926ca6609b2a1f4e6ac26a3a9dbed56432996ca8d850b1a1b2721f7cde37abc64c8d169e97238974af7377d1431f67a7377dc784fb9e377e5eeac12b86fc5e35a35d31e0a3c071b662f29bad98bc3f6b81b962c8ef671fc62d30f78a21ed7f63cd16355bd46c51b385c72161003344c055c81683935ed0e83ae0ad2630011733a894f1945ec8c066f01783e338cee380e0beeb1101460e522f5a10c3450952344830811291b8c04985c411a61412546484488881d2810b308cea24810cc43049e088f09380102acf24841a35a0f14263cbca8810c8863041406174f085010a6a01a00320bee2b31f4d5bd18832e490b8ba41d32f4b7ee21723c6bc3bc0d660051552b861a3068dd50c193160bc704123aa52210a3c99bc0ef8132f5cd03801bc17f78d2a15a2c093e9f33a0e5fb7a5d85fae5cc1c2438421dd41f7eefb1bcdba2498624d692f3313093e262fd9e037cb933a04711452a9d403a1e2418a4fea410e95624d113b6c8eeb514beac30e0adf4b205329d40bf1be82c42fe44e600c697a1bfe74d3a0bc65329964908202b2db66c6fd7dfb9c89d55780dfac4ba6c2243288e0830f5e54c81adb0824c8a14256278104af37c6b6ab919a1d91601624f8b14ee09836ead7ee34cefa4395c13ac0407edd87e9da91a8c763c81e755881d40106d276a7b1dbe6bbd79bf9df915c6116cabf23f4370eb00572d623cd5b345bbfd21c524bf7521a09418fffc3df8ddc3885ca8d0ff7555ef8e0a7f90e0c72462369def2cf21673080a67044e2ac89c043f7ab5b23c5a7977084082620022e6392927c38d6142aeee37d95203ef8395695203e3c2c11849f071bc4f0b04490f7dcc8c3111e9608e21e8f3cb46e8d83692624a0055a8a60030dec8006dd12c4f691e20018980009249e10e30125f8b44ff73e9895843760c5023f54c0043e53a8dcf8e0aff2828615ce9cf1c12d90fe1dc66315ec839f1ba75001e283bf0af6e1b8e7cab87b46b4bd230c6fd95fad6e8d090b1089aadb5afca63782df649fc3d74d636804bf8f7e71636febdf6fc4bf2daa7bee83bae7bc6e1cdbdabee99742ded008c630380ec481e3c68d3b8e3682bae79e490776ddc9ddc81dbbf0c7472fbca311246ecf9026772377ecf7372285f442f74015eaa24ea6cf3b8b4d29bff79404aab807551cd775defb2df39121e3cf5df1864630fe3c6b2dd979dc7b5cf78d69f4c6ee1bfffb419f8f08f8a49078768d74a3167f6ed482c59c61390eb9116585b5d65afbf9181fe3eededd72b9cb5dee729dcf2d53067398c31ce773bf4b170e77491d777d2e37bacf7df04371acce0ad3832a1b48bae7de92524810e4462d77fc8c78df3dc7a47dce9003b03e78bc62ae985be323c55e1c4eb16d9d68c2baf524bc23fcfa8d805204a614914567040ee322c690abceebba22ae3839c176c3e0fb41114edc1be0c009452c7d4554c16206633142378d63f9016969fc06244b8ab53f58c4b8b558c06051a202b88a7577777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777f75ba5cab52f5ce1b37befbdf7de7befbdf7de7befbd66fc0a50065605579cc0f75e514410388caf74e1d2711ea7006bbbbf5c99805b7b05027edd1b70650157a870185ff181882b88d8e20b1144f0ae7c5340c19de0968008100c810825dc5a228ec0442c5d772292b2b0d676eb60cfe00e1bd0832938cc35008a06785935a009aee3b80624b16ac095ceebbc064cb1597c196208a4e3f03ecf1b220c14a6ef1ba2892f437021610c9165882ba924c0ae055f8698b21485cf2900368e998d3224eafb41140b9c71af9924f28efd2896fbcc307379ee278aa771c637ce9eac9105bc662e8f67e6d698fefb44d1cc35b90fd8be8322bf779fee970d995c1ff03d33974755e6d698deb4f2cc90261338ce00736b4c2fc45565aea97b6beaba6bfa1765b090a6bf5088b3196648d3cf5e94214da01633cccc402dba088a6c6f99c68e09e2375a33b31961487f0e8fa8cf4699ee09707697987c3e7d6bc4781632968b99f6a29a7d23cb5333a400b07e859b9aedc0c20a2aa47003a7660668e1868d1a34565c6ac643ab4bcd0e50e381393e1772a4ec4bb1160701368ebbd38373e342cd8e8fdcf1fac89748be828cdf9727008fe3fbf2b4f02cb49ef63208c0cb91314fbec9bb5d21051bef961cf2f3d6cbe5a9f1762503c6db3197c7c8db8f54f2ad442467f8c819be67887d373c01be2f0ff7f6c55b9af0fff45d1e1cdee6f04e0a507ea2b85afdb76cef18a05b9ebfa188abf56d52cb26e590616ca3fd3ed1d6bcd0c68fbc49fe3e3a175176938c8adc27b3b4c0f0b2cbe3638e28a25bad160200809fad324b8b901fba3c37e96eb93549748b02dd5aba5db86496162437cc6dc18501eb7db652814990911aff347e26cef8d9ea22e166d1adccd272c3cb2ecf1d43467c178f8fc04574ebc5a7ac4f6669c1e1878ee06e75abb512c5ccd242f4472e8f5d528055b2e1b64c6083c92c2d3c7fe4f2f8787f9b3f90af727cf78e67d12d7724742bcce5f131d68c9309b32456b935378c3573c558337ec770b9fed7ccd225936549e4b2e4586e0dce2c13e9632e0f8f1eefef652e0f8f8f1f552e0f90f7bf4b97e785cb038390f7bf5b2e97cb43e4f21479ffebe5f2184112c39277a0f7bf652e0f93f7bf662e4fd03b0def8fabe0a5cb63c3e5b9e1fdfb5b5581c56044acefd6b4981c461c0830da50c30c6393118895448618048084d5b7e6c86884c5650b0cacbe3541c6998b01c27a8d33f7d233ce2e969db107eb265d2f37cbdd72b9dc2e2c8c01180130b2585ec5971c0b8dd50c19b706c638b362fad6d08c335b4535ceec12cef2a287ebd77d006395b62c7042db6e3130b06ead0bfcba930173f1bd2bc01cc666a0e03a8efbe2755d7b692b4e18019a42fb85f65b077be68e77498c672163a6bd5851e2b3b2a5bf140c25b07b62c56dc799896c7b258823c856ca42690af20a91841264ffcc92412891de0b3185b48f7b40deb74a5090de5b283a78a4fd3b33fdf4ed15fcefbb4fc2b1302a4ce2a1442801281a94694425cd2ca1902baa84768b6519acc98a2a218d4e53b6a2a0bb4fd0dd9f09badb89ee2e0109ba7b04dddda5bb45d0dd21e86e1074771337c89a8dd272fd175ef8f406f33335ad27a8906aba6ad4e7a146e86dd4160b3cbc4d9a6367565b48f989101291900c96965915424acbd3247299babd0737d46c0d45289adafeff4ff361bfaaa8cc36e90fe4180a7bd91de59fe667d8893e12ed443f96d73266bb7d26fa55bdd5be5c4b2234ac598d9d5915426337359677305354b6de4aa115569eb9bb05d0edaf8e651dfe47b99631743d67a7f92a5279442b6b655adace5bd1f3ecd8a163aa486a27a0bb73b4582bd3ac474a3394fca7f9eb99d59cda3e133dbabe1acbf9fc2fd73226b466554d5d679ad7b5b4e1a4abdadd2c4e08c7a36f0cba7b498b36568db4428b35bab4dac4e83455d54cd2dd1e40b296361ca435c795e3d2b9418b4ef326d34a988ab343470f2cfb208af9f0a1630706cb3a373514e622bac956d308295d55d328a7dd46dd1dd4620d547777a0bb311635cd6691b99e3966feaade98bcba9eb96694d597ada5ed6934f2f5dae9a1e34877dfebaf661bd22a44e386283937158d52d6846cb55466a4d6f25ac6ccd8590ad56e6794b2b69e371486043bf34d879c16c5d2f2c876138a52d682c8d4f2489a896a37254f6028cd07a9a4e598d02d86c25e2f357599b652a8a69647383d884a221f3b3a383d59479979e41e311e1d453d3b7278dcac68ac7c42436d3cba6768714696eee652ae65ecd79bed346139860e39451d86c0447773a0bb973873e6cc99ee6eb4e8449263656a649b61554b19ccd53c91ec60d2cad32cf28344b3d9c828652d96a1a459cd6910a134dba2943598a99e66ba46c94f60288d4828ab25d10a2b6d3574c8eb05cb31141644c78e1b90340345296b41809ca8b9da96d0ca155d85a29435a3d34cd726af97904d4d33906a3520c6b0e91596d31485fd8a9e9fd3f2d5a34fbfc8fc585e61d91625bf6afe6759d0ff18124a51a1a197ade6d1c7b24c354fb3e87dd4d4a5ae66cda475b71276951999b9062222b966aee78d09528aae65ec2c93d0ca34db96d0ca5a8a2a51d5923644452a8150256a2680db5e5354a63e8aae32b594fd2bab698692d33f91a8a94ba696321c9c9da21d1e3b787ae0e8e828737876c078c476ece0d19393796ef25aa6ab4a745371d4f5863e8165190c67358d70720d95dda0466909c391a9a5ecf55a53d466bba942b956a645228c0e7477accc69ce45422a526953d59206cbab991e050962aba54780d8ca2439a7413eb4723dd11dcc1495c54c5a0925c88924d784624d5eaf6ca6ab904dcd6bb9c6d2f2e83c428918a9b5b5145acfacae674eb3ec34d50cc55ccd9a99c6ca2c1382e95013d2a116abd1b1e3f6363a76dc5a79075315cab77cd341289683d4c4b251cc4c89c8b7892129d312a805049669506142e66996b221b9bb37d0dd1ac83114968914a1680ab49e5925227f6cb5f448941c3b91a86a4e8d4e24b65a7a24ef6036c9b77c1b800cb69636d5248254d2329358998fc07448a2438d88915974a2435694d6443593ac28caa4bbb7c840cbe759ca86d86ae9115528afe5aa0a2195b49cbe5eb5bcaa48aad069963258999636a135d368a54d48bdd16eb51b6c4565aaad3c9be4b55c9b189da651a9aa39090278d025002a04c850046b29d4e1000934d1c44d12560f58c9808a0c10e981064141bc9083aebbb560e04cb72b96d7d5acd550d8139ec04aa28f651a0d4d3f13fd59c27e2d652bac54cf325dffcc36f45125dd7dd34da445b48a6bcdaab99ab4f24cf31a253fed445313caf9d3dd17103a8d4c5a29947730d5f586229532747712a7b9aa39f632d85ad6321afb9a56ceb0f486aa2eb428fa1071b40add9f8b8f86aa4c77e3607fe043e7837c9ade60397238d5d89c5aad03fc01badb02dd5d81eea6c0c9a6bbb374f704ba1b89eec6dd584e06f8b77903bc9aba5219ac966b387d438f9abad434c768e68d0867ad1575ef6075b70ed5d7ddffff6fd3420baf2b4421c6e4208583feb5fc587e0233cfd2b6de92a4689afe1ca1698afafc6b68e85f279ba1a17fad4599f67f1aaa59654666fabf16655a6be85fb14c33cba234ab6ad9ddb7c5d402502da6a4a46add4da3c55450f70b2fa8e60fbdd1699e5976f435ff02906fbd00e44fd32813a9a9ab5ccb9850770ddd31747797491a6aafe6356eaf391aebb5ca72326cca7e3a304fdf2b5805cfbf8fc3d7daf6b603c030d8f67e70ed6dadbd7ec45acfbab5b7dbc1ad5bcf6530b1b6b35ebb0d5dd7bbae757a8bddf192c5d6fac92ec1ada9398bfd666b676ead7f33b80db7de61b743706b3bb703b033acbdfea580bb5ecacab036c6776d16dbb9b57e85ace3938987a36c90cf7238ee8f3bd6b93daa1dcb052cc21bb75fc0e2dbc29cdbdb9c032c16b6bd45516cdb165bb746b09fe7b0d639f76bed0e8b633f8b7277ef86d816dcb1db9ceb02697c2cd8d0450d8780e59c86c5f6da7620ff93c5ed66d73f6485b8cd4a816dcebe56e0d776167b36b4178bfbf7bab6e75a77fc8e32b52c67ad978361a0c9b17eea3eb7d65acfda9775bfc1edacc9f18562473fba37d604bafd3ceb425c054fe1a670fbb655d9cedd8658bcd869b8bbed2c2eb2261c16e4dc7236c563adb5d6860ba0034d6e893e94e77600f6b3f6bac5f6884dc17df80d07dd73ce5eebd641f7b0bddcf561af6d599345596b7f5815fc743dfb61ebd6beaced64380c8bdd72d8fa70d05e57c1deeb8e1d5b1feeb6869facc97ed8adb52f9b829fbc6bf187adb53c6f5547d705ce5e6bb278654da61788830d152c00b075efdccb568c91031eb1d6ad5b6b6fda6b5b777790ee66d2ddad16432e33a0c09699c1d51fb403326182288005119a943bc819c00ec63410cb06bd640733c84b584e48400905d0254aca5919015040052514296fe4f860ad707a7690da004f19e2d8a1c0c9ee94360a3dc69c41a247962dfa660c1454b019b3c56d863080ba902527870b6f730415c63c4a852c335c74961739f41037443f06a2fb76c7cebc96af8a279f934daba92bd388caf534531b4e8ea130f3bc75534e017841528d175e93e7d13079dcbd3150413e2819bf5677fa7a300a06ca23006abde10d3f31404420a94bb967c236610a3d1c483ad1808a619b940fcec3063703b61d902f492fc840840608c7272c438d15498d00cbbd6c3e38ae716fa8487201080131543e08b8467c4840120d530d281811482a32e3fcd41313928688103dd30db92758014a3e7c98e6eaace043132a60ca51c594cf0c04520e7242683c1f38868c03971f5e70ad5391130b438e73783e7e1801058cfc300306e04c7122082892c05b911140843a5456044934767c38793ebad397ba3c488cdc144e453c1f7ca4d0e43b62e3a7c8c97404d52406c44b92886d764e9e8f176177027fc6026020e9f41d5181680149dc1539db0da03b713fba1c240ce0cdd4c277a43b79345243542a6025a619306ed090e36b2961c2820fa7cfe5ed0877f02001f250d8abc1bdee8e09855b78219ec93b611c2e06dc23c472362e8b939163fa60d70796a4ef24c3c4e3c3529205797c2d8c03e3e8349094238589702e702a9870e01b2a9d9bc20f492e1c7b8bee0dbe9c01c60210819e304062820361bc70614203490421e50051d42745193654002737d840c30c4d806410c09157cf8e160b0100c0e315a800862001186bd0f0b2c400065a3030801f185e29c440811d144e9060892d5a92b00005b2486086560b464480e886231841086d0003cca2203f4ede962196784c351093812d2f1f3b336080a7cfbb614304217092634b94080049911f24ac2820012ec6f43001131cd080941a68e0d959c1c03308a3041a82bcb063f2ba0920810403962809f2a3059485823fc19de0667099378293f11d7063ac0ddc0aafc244056a0a9f813be14feecce2f852e06e84347c86c7b030685e702e9cc68a38e5207772d3f739b6d7daaebb6b84aa6be39e20090b918465e872b4a288351f682e90c471288f051bf88029a08367a092d84017441296c124e374630544928900f886ab02b876a8afc8d7d39d6e4f8e17247d5d926038f1c1c4738598724323a07490c49d49e25035aa4842ad371f4832ad52eefde070b82b5562803420f003564a52b1c4bf373438df8ae6cc0f9c0b5f8f07e4aa2e906fbd3758061858dda95322e9825d0d6c03ea5c8b51d8e6548473c1234057a33b7533140c140cd30c308647801b851fba1a97cb0f1c98a400cc70ed850290027ea1541d0db6f7e60ec140610ccfbd1a3c91eb3c9afbe262a46c075e0ea3eebd7ebdfb5dd3e906bede0c9e0b15d089c6eb3c7b3dcc03e360fbd1b86392c0568f0d24a594f081a6c3363e223f601dfc300a7d9fd9020835103383302d0073336a72668b21967e7c607881073d8f60049c1644d5e7e170c312256764f084971518908305024222a0032ba260010a44c084161c170b42380fb8ad0c50d32520a1cc15544ca1021048e9a1004e6eb06189122345565061868c273851022638d0c3018452b8819e2ba8523ac8810eb530e9cc164d6082131c66c8a0b182052b7802052208410718604501420520400d4b94208942062b888109b01cb9810da290010b54808213602089052000082494b1820a149cc0090f4c49ca31243f3461191ff440072d50410948d04406861022c76a40f25343134621831648c08821a6e458010480a4c8cf8f572ac4ed831e44210317aca0042468a2031968809529390800c90b3f5e3c3c746a5cd0e03ee3831ee4c0052d58810a4a40820e64a001435811620a162010cac1090c382e2da020430caa140e52b8b1a4b4001b6ac8a980100b408ab2449fba3b002d9a98d0a633770b1c050e0afe846f0358039e01d76275b8cecdb12ef0c66dae0bf765ac6053f01b5e8d4bc357dc0c2bc3f4c2858b2b625598124318281ae08d138de9fb3aef761dc75ddb6aeaaaddceb45c553cd4dd30ba3b77b78d1671daed7275b7b78881f4d017a15ff36a5e91885aff3966aaa7f9aaf9abf93f90d2322d65b02f7f45cf57cd5f9188ba16f14d777f2de20074370b2dde33a110c4f007dd9d6a3114734310e113ba1b6cf166d139b85c2e1c5eb61a9d99f645e8679849533f458b6267fe6c749669778f2d5e5a778b2d5e27dd3e7cf4f8e0f1b1e3a3870f1e3e76f8d0e143c7478e0f1f3d3d3d3c3d3b3d3d7a78f4ece8d1d1a3d393d3e383a78787876787a7070f0f9e1d3c3a78747872787cecf4ecf0ecececf4d8e1b1b36347c78ece4ece8e8f1e3d3d787aecf4e8d183478f1d3d74f4d0e991d3c3078f1e1e3c3c7678f4e0c183c70e1e3a78e8f0c8e1e16347cf0e9e1d3b3b7aece0b163c70e1d3b7476e4ec085084d6ac0e7dec348b8674929cde686659d443ba817cfb682ee55ac616d0dd515adc01869abad628b99da39965d14f774fd147b0a0133a0b3575c5cefc0456129da60c56d66e622d66ecca4c328d66c6709495e4183aa449ac05311b1a89884654ae6769a62594cf273a24d6403d3d9a349141759d66293bd72765edd1f5d31c4bd13fcd4789ca3f42cf27259377c1b24dfd18d29a661b9ada7eece9a1aaa36af6e869d2e46379cdb1168cbceb2c612a5ab3aaa9794b6dbfaa3946435b6fcbf949ed93bcab76ab9951cadae77fa92cfcbb64b2a19ccad6574fb35c6b5a9f555a59e45d433a76dc86866e695011fa43b9569eb06c5387deb5aa28ed33d17f363232d3f57c743d51d8d0907a9be5a32f323fabb71296897ea8c6e6f50a6a0d7db621a939cd6bce456a09fbd3aca9e659ca60b9062bd33543c9b49b556664d25e4569307355cdd3aca579cd311566da4820195d6119ca9735396dfd9749deb5aa26ed878204011952cb9c7e263ad30c259faf9ab17c7b24da6ac24c5a2daf66cc95e658996536aefcaea1a14722fa35377957cd6a3eba7e14b505d556d65aff2e35cbd4525626b1596f365a59f3a7a9d2ca150d7a17112445a789a6f9861355926ba51258b6353991a0416a2c6725b06c6b02438386103951259c111c110408d3dd5c64a5aade5025ffaa69957f03126eb0a2bba5e897adccb2d7d010941f1a5af2ae9a1cabccc8ace568bd4a2bcf57735ad26039f6cf802e47c812012cd4d4457403ed06176ec0d1dd4666edd1f311700416ea0d01a6348d661609d9545568bda15915a2d1cc2742b2d53c12d2e1d2d171e5dc9c47684a24534b19cc869e239456946628b0d256c2fe656a79c46de03ef015d0c095559406fb286bbead67999646ff3614adc17472a9b17c669306fb1355cb9ca66811d3c9a5aa2551f9242d6b32d899893e458bcea2a795262cdb4c2a66908f90d09cd6100601b51edd82b628eb24a7dbfed8155ee86e2674f3402cfaba3b8c151d86030d02a220fa891428badb033278208abcd6a8eb99a1e0f059166432994e331499b51874d42693e9e432994ca71a9cac9644d928966947e8f904e7cc382a0c8de140c93117ce4bb5adabac95838ac3d7d055bd0d31994ca7ee465b6c12a665d0dd316815e7475b1b17daa21a0aaa90d169a69d9582bba67566da0b403e66d652452d32e9d2dd308012cb3afccd4933cb1f27fa44b55b0afbf2d52c536fb6cf31f41442d7d3148a9d19a6e658bee9906342b00c05473d51f3bbe9ee02b43880245ac7e7785b09138ad1d07fe185af69d94a98ab8896d51d1d57cecd8aa2a90d0b3d65002b3575e98082034bd51a2c8a90cdbf6cfe2594665aec2c65423638ff8ae5d8792bca298e7aa3d9d2dbbf5633965398d0690ad56051804220224043806a808e808c809c00b174ec98010d8a759b20cba0d268a43a24cd4064b99644981c6525ab5acab0aaa50c6a0db65a7a44a6964732cd66a6416850cc564b8fa8ab5aca00b321c7d0214d62269113fd5962ab1161a2a62eb32d0e0cf0412da27fc432d123d188caf556ab99aba99a45660cadfda09566ba7e917943cd22f46de8ba9a51728c76438f64e0a2e6954664aa2a6acbb2352d3f434189cacfb215553f96735afe8a24c750253e44b29906a5b0d37cbd886ab77408adbce99067a095b9494e8156146592cd34a86d0e6ba3ad79a2e66ac64e149653dbc750d99a6ba979d321a756a6a62e5836775c3a74b8726e886237d94cd71208c0a6bb5fd0dd2ee8ee160489659bcd4c57f55553cb23200e4377a72df2c0a57b6845d1342d6d7fa4be8484942c41d284c90f0d423f4648264b829a0809bd7e62290acb4ccab5248216c9d4f2080d317035cdc5d0c10d1a7f20964ddb0d0643bf482896d55be325d6331f7daea14ddea512ddd447cf97c1ce32f5e99b54d3425a6db52ca49a2ea4fc4488658dec4a658aee06d3dd2c1852724407caeb55a2a69aba884c1a14d309661ad4841545d347822b462408a2112090d20ce5737a5bcbd3c86cd25601302320b03818b1e9760195e91ae5e7f67ac1cc583e6790a9e591273094e653ae2591a05f4bdb2d4dd121ff62b28409939f7ca3a131f495d792662b6b3838b6b2f67afdd0f809f26376ff2b769a4fdee65f2f989aba70d4d52cd39b1a2c0a4e0d16e5444d557dbd5254a8068b22b4a619ca89c288c0881ce9ced1a163c751c92486244a8e9da5b9d26eb59cc662a4b9d26e497270f236eb0d7d9a1687bcd0dd3a2d0e4175b7cb767b1acd2c7a1acd245bf4e9d2a8b996a9aada441f1bbabbc98f1112480888fc39f2e8fa3a3d7278b8746c6596bdda427e241a512dab2a49946b20a5209d2093201b4006b59a8d4a9b102f48e8fa423620c4869d1ead578790202942d7dc44888d90140c4280214c0771c5b2517ab3bdd16966a2d49661674e73514e1f4df30e65921661c8e96ea3d3cc34537664abe57525bbfba7c52063da15a44bafe8191464a8cf5b1a3363485a0cf2f5dba4689a661c9cbceb85ac963985e5d8114aa3bdc0c3bb86209de51b9d261afbf586be70c30b1c005a0482459116811c612b737a9222909e3e6fe9a3ab79a49aff67699496b42742579aa996b21b10d68f30dd2e9a6aa24a7e54e96e178d762b7ad5cce95ac3bb90d20ce555138946b379d57c576c488662aa67b645c9ffaa0945e8f582a966edf5424d219b3af42a8d56c25a3f54d3a4b564e85f436b5685ce1286b49a51326da8c9d090a126efc20f89a130578b3f96747793165f5abc88f07ac1eb04af9af862bdbeee1c9cfc4abba9397d9b58566f42383871a9b78fe57cf4aa8f29a20f1960514e14a6965f0ea0c59e2edd64cf5017a1b17cde705aec69b52bd75058cd34531b9ada6ca5cd0acb4545f973cc54bf7c951c1afa2c0b72ade859b422f96c5b616594fcb1fc0485729a2e918709dd7da4451e0ef0ecc063437703b5c8f391218204c87a66283f3938f998f9383871251962c3efc4768874770e3d2ea3134d8f3e9f68ec8bccf48622fd13989922a9657ea434d3f02e9586c2d0154d6d4f2b6ba9aa66552d6d484436eab98379a2a90c76223579976aae678e9dd99663a759caf28a2a71a525ed0633d7b4b47df927fa6a8a16d9b901a687da63051e60bacd2722fad5ace58f9d657e5226e151c4c3070fb13ba7b01c9ce0e084147778819945262cdbd45ab923891d43edcaaba9a22bed5616e5d8cd899e652ce79809336f4dba1b86167788dd9d8313b4e8347170f22e241aed56a4aa1906bba91fcb6ba6d96a28ecff24451d62babbd1a213490c493e4da3f347bda967564f7408508e8effd173c3c1fc1cdfc3b3c38564e86f90f293cf271ac3d0908a949f0c7d113a842406243a3a9d21e84821ea3820851675a20441ca3120e527aea1219d95cebb00f4399ec78e1e578e4b3d51f3666808293ff92274086868a806c845bbc16825ec9168792dd31b0c563e9a8f3e8a59245bd31c3b6f7f7b74fd5f5194c9e747aae173e478fd042d51922387ebcf0cc53442ca4fbe546b793569b4f2693433869c21e444912382eede6931c7871c1dddada4459c1ee064e96e15293f21baa94839861c436132b594b9866818426143345a09337f5c47f98495e91a253fba7e91eef669d1a5831f2dba92e8206f5383d93004488a16a1d1ca21b472c8eb757abd56d82d5dd5138509a93976a3c14e53e8ccb1982913326da5904d7dbd8a6859cdc1894c2d654236a76964a6280e4ede15cb39fd9b13fd73897843a6656a79e4f50a8a218191af57500e4efe7170e24a730d75a2bb85b478c383ebcc9908478b3747ba71803c00bec9db64330d1af22f35af665ac3dffcbf0f91bf5141f0e57b7474b878fef5592d7dfea646a7b5e451f56d9000913f141b7a35ab33fc6bc808a509ada68ada726ce8d1f569b412f63a7fe3aa697d5e332dc73e453faf667af4d9a8d4f99ba11a9dd692a1bf0902e401e0ca66babe9a8d8ef2d9e4c6d5dd352dde70364ee8ee558b363be8ee1e2dda2861e3830d926883b6d0a2cd916e21351b9da50c16240866dad0554dd534d76eea89c282d4f28a6455b39149c408a5ad678692a1a036a12865ed2cd79248accc4756144d856a592d6540592d65b6203d3720b04cc3eb05cb505eafd8aa0ad9d4722d63eb794387146526ea7a6613661615dd5434c34ef45c4177aba0bba1e8ee1474f797ee7ea2bbbde41485adb7253ab06267f91956d26c397d744d83d4d4956165519ad7d3bcc1517bba9b01624d171b9ac6fe44ffcc4fca9506b4e8348568341426b4c2328d86c26a6cdd693eb3510d500d8f4ef393143daab92194c59a6e55884cab620b06ddad26babb552db68e28d73256aeb9d6526aaddd2d27dd276aaa69861233a1a0a9ed4b9fb639b22851c2b472badb6bb1154337101433c3321335964d9acd46a6e8daa4882b9f686abecdaf513291d35ccf1b5a2bd51c33d334434187d87096fff9682817c5ce52b69e37f43ccb4fd1a2b5546fafde644767567fcd48bfa2a7ed693473553fa7e5cf507e4ef393b4aca5792d8f1e28099323efca37a45899535af9e8a3e71799bf6655c9513e8bd0b4cc34d867a2582e42ff5d46a7594361a7f92ad1c7b299a625ec5533cd6b51f9b10ca5543f7696b11c2b511a119a8f3e5666d9bf2b964fb35c89d057cb23d554f3998d6a685af4e5a32aad7cd5a4113d5259534dd9fa43273af4a7f92e23577e9b22f4330c4d6d4ff43639458b722a839539d75e353f46cb66d1aba62ce85d69b6bd5a7e7a3b4b289996d3b5a4e534d750f58bd08fa1b0cfb2b53c8d4cf5f3d1cb60a6ece8d7723d735ada56b5a4c1805464aee8f9e8fa6b566b78976aae59fd1c86cab52412cbab6aaa69061ac24135bf7ca313a5bd5afeaa9a5132f9ae1c86d42c839539cdb2155d8f5c67b6d94a59961dfdf96b799ab11445614fa399479f66d99a8dd0f56367492b73fae56728a52c87a11c4387189d66eccc404338fc9965ebdbe41bad84f2e5aba5ec57f38b90bc0b8966b3951f3bcb5fcdacfe99f3d16a5353b4e88ff2995374551f5d3fd7f0b17306d5e8346db6929666284b80907c627935894c199a0219993e01a23ea8e40b57763d4b32a5d0cc0888000000003315002028140e0a458301711cc489e4f5011480097c9a4c6e42174cc482284671100621641421800042000c0001a931880023ebc8a4f917736295e480c3834f10d4848a3dbe44f02cce07f03d76708415f8b2620584da6780e7a4f0e72a51f9d7b84188c268546307c5d0f082027da03e65853aa8329da77b1497e9f22b58f1b2845c5d9eb4d91d0c22a5575109c9c686815d226e668247fe8671c97b579a917bda400fdb4d1b4c3bce7b79835ef30a34e953bb2d832e43281028a78b9f2fdb7332ed61a0cd3c949ff53069c35c685e9d45cce5eaa6dcaadab3202c7ac5f1b436dc114fda13352b78410281b1c7ceba56e2e07e39847fa2b19d8f62f106e4d1c97e74a3414e8c5792adcbc9770e22d4bff0543aa0b8c2b30ba1e0786d5f56da03f1c609187a15cec8632f5ec8cb5ddb66fac8764c31500f8adaddc8b0ac2963c0979539a7d2a631c8cf00b5f25990282debd94f19292f94a92b253353c164eef03288dfb5e475e6ef973440579a5b43b6a0c989e8faa7cc4ff012a2d5b4672c8697042be5a81b5f1f583088eb9db70088a4e1793fc4cf8b0a940eaac2deada6b0b63eed129bb1bd81f7aaacd32f4e2672bf853e9910a1f5e4e44863a5c2f37fa908a302a5ecb2dae420b7acc59e9f30a2dac25a7e9b396e0298168747718ff7f03848adb164f2bbeb01610951a1647df4a5290a1c722c6b90532a25fd8e8868460c0228c9e21440af44d1d350586e9dd1799e2bc69e3de2837c5f8ab19e50ff08727db319132038f9fbd512c93db2a099538d8301efb0044244732259fbe53d656803b09cca243ef7fe54f81d7198d849b839e20af443ca940bde86103c6ab9e989db1349b418d0304579a1f27df42ec78fa90b45a3f02fc4203fe669bd5044a90c3218f8c0849fe8f1b02dbf97cd60b4e183915ff51ffe5dcc2e0d5d653beda23b1ceba39d9ece60903c1386e367133c6fc1804e9335058914964b041564eed8118f65b96e94f98f6d7d05c15bd9c596498426a87111998a5ad2cf73d236f3f7b1503d0001ce611e9a2d0ef44ae010913037235d455d1e3a3dad8e15e557bd2edc79ba4ca4ba050c8f79b3b4b1ab85c360f9b5c1a04cd428b5eccd10ee79675977ea61194d52b299cda55c5eb80c49734255a3cd2c894803f81b62ec7702e57d54046091feae5af06a6fcd9b36c1b1d1ee08ac126b2fcc66ea40a0e3814c7d473023be4ff195174679b5a42282320cf95915a8b2a6ede9641a55ef4ad45cbd91d43ae0d924ecd54c310aed3586b17f76765db96c9d8452e992472848539fbbb2f7b91ea3cd7c0ec82fdf1cb0a56bc2c07a3c25f83ce01562598cf8d43871b92aec3fc17f6f7677193b264744e1695e7c8030e6b7cf78b71dd3132a98c1cb71064326dafb6492b35ee5f288503020e2f9f57e2599bf0e7df3d6dff59c55e488e6cdd350e749669c9d7bf98eb0da20707bd766a3fe1017f0fb30a4be46eb693280a1243d98dbd18d7c5341632fe7ade366f40067f75ffb9c96eddf3dc5268a9160675c787b92c6e462de98ab993cc5462364cd6d2b3a23ac3741a711914151520348db9e6a880ef719f2a11928e4b6d3744327bcc1522034a56a80d6d3c5efc018c1cc2cc2c72e1d67f62dde78fbeb5f3b75660136899a96d16c47ad1f60ec994576cfe486ae81c32be3b408b1a0b437c64077c29d7fcaa4f0594c2a6fdbaab23d760216fbc73d4b03a70dc05cdf79f0545890c74678e7c36e8e989c97259212affc9d1d4efb264628c3d6c8e4f7c9350270614028c7fa2d6d9289e758848109305b76c6a9967ac55b003d4f922aede5e008ce296bf2c6f52b482e28d9c78fcd2087aac33ff8e5f3cb65db9268fbf2c472f3ad74f725eb6008e9eece1dc048ac1162ff228cc05ddaa4ddbab7f9da1430c3dc10f98f4f668185ade23b507093b0505c767188b8192de3713ff3c1f91413b2d6273d7b0f72574b7d36b744c6785f229964d857ea391f509821c55af026f496faf583d73c447a6c9030e9a3c55c64a683d387971ee6e1d6105aa36f770f27fee7d97fd34fb972aeb096f1c7a6ff35b3e189d34a506e8f47a17dd1ad66138fe3bb1bb8c7e40982b0068d727c06ac112d2cd6a338e19343f525c573b07b7c6d7b32814e8102ff82d751f08b02a52bd2b17ae7bde45e1cd77ea4a5754a5b442ddc7bcc2bd1cfb193f0fbed41aed1c9790af5288b53aef36a17b837d646883cbf110e43c7b39b3963bea3a5d12fdcdcef14a8cd39cb0c8555917e30702093fd19f864abadeba080703c0aab008d36b1a823868b891463a3f25a52e31687e1bd5a5a1487918518b743095ff6ad8a545008370c972e4064278a0fb083c32b2bd8a42a7e7119fa534058a3c8fa7ba999eb50a893a490f5d4b10ea4e94c027ed466e34e66d28ece57d1a2035739d206e1aaa1b37f3cb3e84119cb039588a635d795240164e9fddae3318d78b27f90b5a9149034edcc8480785fc34a0e3b8d09456961eb20c23414a25234dee133c4990ec78ada8779bf7d21fde590b48af7fddfbc31abb94679029a87bd4967f0315098fdf60167930fd6b5b5710a4806684879d70894ad8192debfd0269be3c606c6f97f689e359f10fddad1b391437e1d9cb681fa52e8a0acfd63411142b3364067b63b786eaacd10e9cca6d5f37e229ae79e2e8af5e83a76f4d670f253c9b88f52d2b390c8dca3f759f0adbfeab33db221a4b4d11945a8732e78f70860d22848d7afb92a42d233159ac9f3283c2e0ee78d7f779361c2d6d40063f171dab81bd6b94fff2182ae21b1a38d787a960de8fee426f38b3475d5faee528d873eef395d8419c41ce2ac138bf960a682c2bcfed19ccda40256c6992cc677192cdb76df223607773398fbcdc3ab1ac289ba0e7494c165a13739992ccea553f7d58fe8be44533d17eec37dd96d9f4cf490486567c29b03fd1c290cde96bfe45f0291b07cb2755d9336c39946aff671a67694f50921761c151a8166721bdf8fc34c850183a24e00af223e0acc9434a505b115211d0cb3df2d489ef3f147f016a7bfcb19c6d973b6c3cd335f931c9499e70ad2d380aa2578786ae98e719446cc5162729d8bc6a9b4132407f926868fb395557ca773537306aa2a86c48480768639c2d086d5622b2e5f26464d1992e6a6e5a4aca12e012a5ba889d53e461d0e5f86fe8693e3d2ab629100398c503c6efccf98bf7d8ff2c335748643e8db39aeab9eb4838e3675e55224b00ede60b9f63d08264fe38bd5357f6a8d727f60d8f046bbb24075ea302c9b6a345262dc7bef92ff89cfcef0d9d1afe5fb81bcc920fef34a7e9f29e15a16ffbee175b5bb823ca346d953e76667a276b021336c635631a3b31814f0b69c66919907f6d13e383a911a451ff33b2ad662175ec0037780961bd69609cfc0aaf627a8ed5343037006a6a2cad7b6595fcc6b9d0fbbde03fe5d50b548865b8144d4b7465981fe973ad2d661b9e87b9891bf45256af1ac5f6060ae4afa4d01db75d27a0c1fad49bc270a03a0f52d79c84332502ac150840b5259d06b7b43694e4972b16743ea3a3a551eabbe64155f75e324240cec434e0928cf7c03cf9f07acba32330949cdff426c7d681abfd6191b300de39578ee7ec880660223d5624523a7c0618b942d40d89d5227efd251f771e23619f79c74be5fe9d1a7dbbb8f1de2c8bac5e181099f533ae753e8fa69e703d26ede1ab3d88b8153ef204959826a2c65746156b74f96de4a229f9ad0571dfd05b2929fc327f02d8f90cd4a1b1322a95abc923d621c63580ae964d6c57a03044dc1c7829cc041fbb9b5d221379b4c693e3841118f184b253e770e9314f6de2288fb9bf188dbfe0a76141bd99f3f4b286d1e98c47b16ff117f5db3a8a9c3edb61ecc7701b67d1afd05233640ff7d64e7e0c171d93184215c41232bb10617991b4d085c7df7f2b81e5f78d747db783cb96c3cf7c56000601b41c3d9dc6b01e05307d8e5f468168d5896061d17d3636df256003fc5666b11dddf62d3c520b78cb9db0725312c373f084082f34b7325369f3ed026f3c088d18db3f13d6dab320435c88b032736a22ebfa4da3fe240ae2470c883ddfc09053305d0c3e074de804c67f09a0ce53599e0685851276ae3e22aad0a80c037d9e2d4e651c91e330cc2c9761db6de9cc4983cbde0f71bcbc89b312dc7091833f05361dd03345d85d623a1405eb796b9bc68f87a3af23a0e6c6e52c05b4950231102c7b43b9f393f8e2332142a051e8132684b80c52f81e2fec9c8f2d39bdc70f858ad175033709d736115c50a33d6440becb009574da32cb3038afeabb088dfa4e93cc661839554169288ca930bdaf6e43dd29f7ad5cebb8cbcf52e83439cf60a222284ad977adf9dd21c3539492a600e929b2a792a213d629ac9d8dae626badfa5d18a6e1c04a4ad7ef27c2d9119a5c803ccf40838dca47456843943ec8e655014eb8406bcdc4f79f4d722a1682fd98905808d16c01cbcf376c68725357ed1be9c1ae630146ff10f625aa976ecb5d67b9b3ab25a4332c1a4ad57c247ed5be980840d8e2878a809b10fe294f3e60bc459ce9a8471e405a453acb7e7a10ed69fa7db5ade8ce2a712c0db87fc9919b75dd5afe8d6aaac9314e3635f2efaf606e83af39feecfefd42ccf0bae9f028a7f5586613bbc9095c52ca8a9a16f43728ee4d402c005d260295ebf0ed6c89ee350b03ebed63537e2abf27753e9a97ae125d9ac2aad801ef4e17f8de3c3ede27baf2b2e7d8ba86439a5ade1cb69877f278bba35d33c31f1dbe679b7caaffd7739d37b94786a66dba3b3b16a71745ef54bdd93f517484876238a1c62a16cf317dcf14653f1812800c86f2cf2ebedd1d6f3fd7cf4787840719340fbc925ae8a78ef2b38f710f1074fb6da85295e5b168c2bdf253df8737458ad14b4e092752ba2642bfc479f0841264a5b60b1de6737037ffd5f14d3b87cd0dd58131c8ed6978bf08d806b47570ec8311cbbb39f2a77af8e70b01b99f79f8e7dfd074e5264a8ab42d6f8ef75b01aac17fced3db902a9a9dfee3b0411ebc189dd5f5e8f935c58aeef4f87fbdb43c6c3a70c44db4b0ec5b046ff68e0fdb12c66c5bd64ad629516c45647a38c7d4c048ebcc20bd4b0f664cbb886c5defb56e93b6ffbc22dd26fbc91bb711dd34289ea9f663e5e4db65736fe52151b0faea2587a5944c517d966fac48912dc48c1aa21edf5f33a2be21ca375b0a70de06362e210a97b7af071b436daaff2b4fac97b2385b5d123addaf81c707470800338c8e2b91c9f25955ebc80344c7fc069b1d6099c1914f6e0dacb3d0c14cc5e88e11553585d25340a84f50317384b08679e6ba3c199712d74ab63470cbfd179406afd3a929b4110b6eb64775f4d43a44d25b2d706ec5698bfadcc946b77d8f4cce05657cab944af0b0091a1a1e43df054403d67d907a35e10ef2466539aee5f1978dbceaad8ee871b3659b862cb4d4b749a3b7058b1f105429ab4873d2d08cc3fdf95568017d8b3d6207b6e10bbe841df9f04deb20cc67ca0549dd5a293628335dbdbb01a935eda5fba219c2ff9db5e910152079642e4ec320bdf06816a413990aeb538a647c5e7962ab4a9d2247435c9e8cdaabd56ab8dcb100b84075ec8dfafa943fc89bb85eef1cc1ae33bc1759c4676bb9dfd3f65dd267ee52419725c5256186d8f31cafb261d8f530c15fef6f5f8279fd312faef504f89df7e8c3e3ffe5e46a7836845a675b9c66787fb788a2118203a06e4158307f281d39df1f09f06a1040fd837c704026810f0fc4ea6f935ba2d3a3dd058c64b019a055f32ad40b1c741906280d2086f319722e9287aa9c2a1af9ec9005811b52f323e1b7c7329601945f5ddb7d16d79264ddcf3e042ba5fb3d4e0f78d9dd6fb98cce4490f2c225aa51055f05a20567500fa86020c6a9a1b2054cbbe1fe595dacbd73998592780405cb6194f9139d1e373877ae4a6f85aafb87dfef427a9ccfa14bd4774b2e1ef4d0ba685df58e5d7059a9083f7b3b2d61ff1cad13fb432191b280aaf813a94f593f03a5616b9b67eb552d14de366589506cdae7895f081614dabb5649d7703563a24911da4ff3be88ef39497c3840311332ed596134acac5c6eecc271bce8fb81ec31cc8fd4decdbe93e9b6e8367e1cb4ea7b137f4ad1730f37ccb17d34c6ba58a4bfda5b410dcba06659941e2da6b9a0b58fd1b3154f8ed5c50e6544e11a4cfcd3f52ff3c34448c2f5fc12dde6bae13a6aff49ddf4a5b02ef3d80a4914da1ebdecf05eced1a36d801923f5fc03ad2b7dfe8798dbbe1892c2f6b2ce6ac12565800f23166266a34b28dcb2445f33e423b4e5dc72d33decb451b7aa77a238c9953f665b955952699edf8f742754ce5372f905929cd0d3e667eb0045ec63c18cc97ce953b2bc53c10da228676deed336d37131ab160da800637b29c45499bdd02620a7c149e17e65b2c5e150ac0a0d31fc2f296801c4281b79268505cc980038e32a36e798439d1a16463fde11d55f86a58555a22eda54487c67e0b529e00d6aee013e709987c574dfc84fee2dd5c8d7c016ff079d08ceeb34c7c162f72f4cf810f576aff437bebb5cdf7612f6b0eafc862864a537cdd85e195607e91ee084f9243b0396f24d21e2bfa5bf55fd32c804af4d2ac9c8efecefd14d48c37d436d09937fe44f7ff8b71f8cf44fe2610639a09360634f5f7194cef7edfe43e63d9417b5e8e340332ac8606e0f13d37b642a768bbb3216c5e1022c20e4342e84114357af70d09a637a0d6707e247ce891e0b29bea2152942ed6a5c1bbd9ef031842e34f597666062039f082fff5f2ef876abbf8c210e12c68dc31ff9442b1c31e760f1ee58ea336f4070519f297fd17627adf6953e97dd35b7fb252df1fa86b53ed7cccc16b5620ca6c2514ef61193ffab06fd41659590fe9e43423994e6e9e17fcb6bfcdf1074dd1f6376e7eaf3faa20c14f16f4b86c4fb418eed02d2d9f4ae63def65bb2cc1d9da7e86c8f2cc7488791dfdc86ebcb6a797f8e82fb68c6ea0f92191af3f87099e49ab8b1828051fd8c92d4550f6ea4c7b9ff6a0dcf2beef4cae9ee5513e76a9372ac0ee17aadc0f687b5d40ef0f8b6ad867282ab0ef04eb05b9a82a08c6eaa5aaab9e5cb2589978012ade419fbabf88c316e9d393ee3b9dfd1189c09a45fa4e40dc1fd787bf6459853989c9551edc9dfd064efcb4f637780ed0cb11b7c3c309679dd6c56aca2cc7dbb9c2342c6036c34dc89c57c3f101f8d85f9d6d59cf0f66a30be53af0d677fd4f69f89b2dd2ad299527ca25bffe9cf43f8e77de17bf93b1459bd9305c1d55f7ff0704f087ff32eed358856119e8719f760036f7fc4851233b6b3d8dc1359dd5d1184ecade4a92904978a3a43523f27515a5ce66bea03c7cdf1b600f41916ff8ba4ce63d6879165367b415cf5c097e38664c0be38f46933a063daff05b0db8cfc265bad8742612f50ed608fe52249ccb8fc4f6a2f0b17ece81e8758cba9398eda40c0ca35a1a179ff32cfd7a10b8389a7c2b5166239cceb93e82070ccf97d596d7579a20a62d4253e8c16e32499f8a38f8633583f690c64b81016a9781f6fc80a07844ed897fe72449cce45ec09b7c5c8eba63d66a2045e24ee662980463751a31dc745c954545342ea31ba987451f70709300b79063d4d3e46835e77298be462a1081cf5468cd040204e3df669038cae8fc6741d994e34b85a7bfcd11280dfa46085c74eb2d624e32cd09fcbcc0d0e9fbd45e964af7c6a02b56c671293c48a5a82ceea17e14600d6ae368823941ed9fe0285fe88d808eafd9d1bab0fef601aa76774043dde19a70ad96f52585db3617bc9e4e1bbc00b1a0cdb8f2ba780ae73b80615d3f46c90131ddd3a306dfadc334e56386ffe8ada1b7cb41af9502578bcc2812bc9188c77635755d840f354cb965345fc3a28b6fc0b9ae048d405171ef363dd0c1fc39b02ac129360d1653b80a685d80a8d2320cbc6513481ee499d40af7042c497753a25b900932b194b1d685f6982a4c2796fce0849a6f2fda0e92689db0c68de313e725409732391a4a0046a8249cb42068f410f4b96b9c261972474549d415e8dd39837e9e82bfd9dad509bfaa4ffc079207abefa4fbd44d2e26447ec261e34504a8b55c4ea7a467680a4788fb70544b3a3ae8e1eaba04e54d34676476065548d7dad0fd190e1ad42e32d6c94135d3bf98f8f816f536a30316042cbfdc76aa8108190369a52b8ccce111771670869d057d8976d1699c3fe0342a38d7418063ec59d9099f6e4521832cda1b94c7c086da557c22804bf07bb301719dc571811c99970abc56bdbea7a34cd8b18326e03d00974fc48813eb40e203bbbe72dd05f4ac27a9538e608d326adca51ad3cd1aec1d997a74b3b7e9f73c0a067deea051d9316684e3e191cf8c2ac8cb4f596e832ba6831715ab40b09d5511304078918db121e1330805b3d95190a3e66bbcb4a34db6244868b5bf3c308538ad2b300796132f9b64fcfb3f4c00ff13f8f4caf8e8c8207a6c6e1536ae879695f17c061eb6bbba0be947fe075e4d67763213640174ce645b72a434f4f3e0c6c1ed3d937b3592beefe7695db9115fe928372fc8be17a587433db8b5de59d6ea7fd80a24e04f48b0e780ae629bbc4a3ec0f0b79bfc8f3639cb7e3bebfdc31348870fee7f0c0ef573b680e80b647c6dddd165850b7b34e07ff6a4e00b36c338e7c3492f4784041937d983ff97cbc3a08bf760c00d7ae37f41d9c8d6bef0161acf7e2a8af1b2afe7ca92b140a5ae5a57d531fa656feef083190788ff5975780d2312503e11ba792d921b0accaceab9b7e7537d522ba063f18e7ab543174b755c2d6fb70a40de667ba6118918221758f0e06dbb9ecff078edf2a5d7005449d877fb553e087c22e1504f3896bcdb22ba8630889c3892c0346c536ad226b3e60217341107b7ceedfae870ff8500b047b60ed60f868b6d27227b83b68a3d7055d4507e79e4b1997becba2e6c17e50dd697b99dcab7c42d9c5f4c39646e714081b77b26a43f3502da129b6b0f59bb3a0b6508e0f9d50ce9010268e2b24201e4521cd71c152b4322b9300c3c05518bbe4d7c93534c385294011c00ae7f8cc68432d510f0edf4aa121f24a3cc5091af801280c1c536e928ef3e3e4c4fb22b3be74996cfac519e7debb9fa2b54453cf86a62d85bb6fb541134ca8161b5e0129135033a0dd9c5288fd50f5d331b0c20dc093f73c29335bc7043a45cafd136e5b4cccedfe6c8355963e36bb8a6333ce035a1ce2fd57a05e5a332c3e9a36f8c3f5c019fd84226147b864b3556ccdfc4bd520fa483bd57c395e8f91e8ca56d1fc45c4217da54ec92539956446894ba89677bb0445e9c9861268a71da8bb09eebb0fcecbeb2b909461010e71f9a48874b28268c7a1c7343c38579621bebdd160eef3f135ed82ab42183f9318f319fde41e09b0eb1604e5bcae2b0ff6996415098271db9db4f58e15890e15e0bc16e08d9575cb046d2f5b545740cfc543a60380630f842f3ded9ba57184a54afce5f9bcfdb3081a7ceac8e8853d31a886148816ed2d3503b5aa12d11520d17e62594c19bb3805c7ed7b0e488639441466e61885202d987cb9dca55529d738f77289f093fdab7bc958e7c9da35a38ad40157f77c11b999b27e75331e86d21eaaa165355f4642335b2b921f98e7a94d5a4cd3fad544a522840ddaa6b8487a4d6f051a7d833fd1a02c1dc5d33e5b100d3395a0ee6ccb49af4d16ee6f17e8e704bb17d828b3d812caaf5d0e61a5f105fab1a9d22e5b0d1f77a21b1f15427ef6513d118adc17f92bf215cc05a5fee8f4fca48a6152c40c948e59462bebc010503dff0294c37407be705ea82f75174ca2ef23139c78c2a5b9fe5e4025143f3b1bc6784672628733f229b2900586045d055681be74ad1364688621c912a25d3468c42ae54ebd32ea8f57f10a1d92384790e5461386e24cf4bbdbc854936291c78a94918a2a98d4e7495d359ce3997f9bc4dedc51b0cf471680c8c06227b2a69ef6e3075e7d1eadac7e7c1228cd73f82d75c78783ced002948a6d321774badede47b7715e9f5d7c574e4f00eaaec934b54447cf60b0d5937281200c10f93e0ba34c62d89c99fac6a2f72d50f5ac1c4735f1f7ae3dcdc0b9ccaac4c0d6d559b145043da2dbb7601f9a7c81c919a14a0c92ee35cf0eacd07df77e37cfd1a78c312f88ef9f95bc10f2681d688fe7f57e0c126505ab2dfaf830e4681ed997fbf0756d805b646f9f88dc00f8340ea18bffd3dfd895faaf8fe0b5030919e4d5b1438494fcf260a46d27b6a090ae9582c6578b5580e0581ded572325f77b10657f3b8a27ca74b9d360b5a5b93c77ff34da8f240af061f462c35b0c32f135316b33f172cec77846dd10f1343b407c6b96ce49bf871eaca36fe6fab71c2c217d21cdd0634d5ef4bf1d4cbe89a9f33f457d1b647289eaf65af0f57522fdbf6aac23b6fbfe5edbb6fbff5e6776f7bf3def7df7aefbd37dffce66deff2bbfef3dad1cf128198febc61a33feb5feff6d3a3e2b7f8bdfe79999166517888ec6c8163e1f39d38783b1451e29ad229d75567018bacb1cc0ad6ac65c92ad6596289d82d9d5c1cacd138c062803a091fd1b508846b8b0ef6107e832045fa54d64be2347509838660e5de50adcf4262c744ebfd4987c5d7dab1e690291facfcc5e72d09b129584e89bf807aabd84270c0ab7a188ca64b3f211bc27a20880ca0e61ed380ab1c8020c977cbf1b33e4a744a33e73c890cd689dee9f5e9a34462005144c96a574c3ca9c6ad67591e257aab62e8a591a7dc1cf1b17589fb5b30230945a24986d5d8e1c3a186ed600867e68567a9352478635f959cd8ca2683ab4d9d7994cf4c0ede6f9c511c75e6b18912dd5abe9bc28a5bf8254a34896dcf19c26e4c51a21fd5f45e29621c66aae0b985e80a18faa844db4fb73ad4933ce2c4e88e53cace2ace9e94660666941d4ae4e0651a11930f1e1e84245c60019b3b710f10a8b6dd7fdfb1fc62b993d7a5e2170658c0400e447009e8086e6ab8f35c1b184b4b51327bd345ad2be9c47c5ceab2044a33ec9d75239fbe084e7969b85377124d104ec810947831710e7cd073f695d284c7263abcd0f2111d3919cf1922925ab6045dd1416e3c9ae84ba671e27a81e9dc39523c3528dec0ba5ab84c3a0a204570bb3cdcb87fc373ca835a288556c80ca7cb0becfbc5e00b505f9d4f04c32be0e614d863a5354a2f4de17f5f6275c373477792d6b58b19483fa1b1a80efa7534c722eb08cb696769bdf28e46f183799fbcd3a4e2dacc4c0431a3ad1f6218dc2c92e40f6c6170adeb0b6461a0089a17d66e5426be8092c2e0c896601a3825f04cfe80c16d0d40ef7eb484010d95360de4f69c8504c683c3819ca75bb2667815e0f2491883893a0210ffaca92287a98c1654df6f3317694f98777d65178e330c08c46393d02018b9e156dbae3ce3535f0931a6358cac87d839ce79c8535b6f01e706675944450299ee94343bc17880d05087fd3a5c649be4be8f5a777f1d500cc6ea45287f67d530be24996e3e54b45d4ff0a6b260474a483d7dacb2cbf3749530a024b45d0aac7660ecd7644d17001c123e675481a98588a691b3f2386834280f59da60c233071e9194adda5d23efd03c718536c42162a1cfa160382b505f14427a383ee66b4368edd27a62d5c915f981fd4fceccaba288b5c86fbcea310bfe7025b69f610b2453824d9ef1c28d1d030356eeb3ded8bbde5f405fe3898f5ee42c9b2c1488279b3d4f53a97c2e765aa5393bd305c4f7745aca31a26a4a36d64255f06e64c72e05cb9f3ea287e7c8d445a3634562871f46e7724392689f81e0dc9a8400a9bb125123ca20072b36b558444e5eb2219e9f433cf43d6caf40f19ba36cfbf86fd370299aa5ddaaa91332f029f2ed44467fbdec42ba4172d1e9a2388b99dd1b6738e02f950bede874d173b6b1305b58757a8664cffc953ce8f10175b12ff8dd329b22d4452f2b072bdc23128df3a0fff247106fa362804875c030000317eb706347c08ee6fc1e84d1c085ddf60d1f734a71aec5e62e2704315d87f8570b9a2fb0ab29bf820571524bef4e368fb9795d1dfdcdc53b955cf9c592731c02d9512ced812504e11b0dbc2385afa1d075d45e7b61b06adaa47b227e0ff3e309080799040d1aa7783d0a038509b1b0dee0d54d03d9fe259909f4b34b4884a18d5a933d056a55e04fe3529c317883412527b7ed5ced15d0d611aa8556c1805c8ede821d3cae944ada5fecc000428db07f176038d6f63a993704864d9c7e30e1ea112af8ed4b59f90360be3f255c9ff000ae9d98fe866321b087a72fa2b159b5eb3ff2070aec6a4012add92f8cd00119ffb2646d235ca16606ddfc181ca012fe609c51620b2c55c40ebebd44744168a4db2fc88c0fdedbce47f73dcc031ca8211fe3ab0e0525a81309fb6ff522364ade80ce35da76e79e2a9af00557154742a004c3e354e1718b7d231ae11a354723e560a9d69b81717d0875a99d9213f119e7cb23810af267d18fc5c57f8b6bbb8068015f605ddcd7328fe4754c404d5a8ba6ebbe9235954f6922b202e3e4e06c210445cb14280cbd9f66ed3a1bb72a9cd0a1745d45f34d704addf6ccdbb56783c3b9c75e0c8a7b227fb767187a3e000b3628e01d8fdb3f892eb5ea12434a9eac4eb05f5d86298197f1bb7e49106ea9b890f66b7ffe5d30dc7101c7a82b684bb4b40801381d533e305bb8771d8760f4bb0012cab534e114f50b5e6d3788f6c70e27f92ee353a4d8f50db0c557bd23d47a65c693bdc4ef78e191c0554f0962dc25dfe45ee073b0f44ce2aba78931dd926f72aff13b5c7a2571d5d3c5586ec96fa957f81d2f3d9378eaa9626cb7d4b7b4577c8e96de495cf53431a65bfa5bd62bbeea635429a6c34e576242c20e673721732c893344b2279602a6c6b57e103505ee7a1b4ee6fd7055d1dda0359fb6bfeff4895adf2c77b4dbf497144fa1c58fc95ea4dc35aa08eb7dea5af64afe87646f52de1ab588f136752f7b29ff23b227297f8d56c4f49eb895bd967f2d3c156df31f389566fbd7194095b9cbc58ca2acb365a159734d1f885a017bde66a7e93eac5ae83ed8ce47ebaf775d92d67fd635ea66fe49c6a768b147622f216f8d5ec4f49e72977a95ff23a99728bf422b627b4fbacbbcca771d3e9566fb179842b3fdef0c48d55ccbc24cb1ec99b5a0a9b1a60f46ad807bafa6a3793f5c4d743b68e5a3f5f7bb6e52cb3fd31ded36fb25c653b0d823b99728bf862ec47a9fba95bd947f0d2e956efb17988ab6f9ef1c4895b94bc50c91ecb9b5a0a969ad1b445d817bde46a7691f5c5d743b68cba7e9ef7b7d92d677d615e536ff25c65364f15a786adad63f602a65ebdf39904a7397891945d967ab42a3e64a2f88ba0277bd4d4ed37d78b5c87da09d47cbdfd3e488484d43c96673ed51d08216cd59266616659ead0a8d9aebfa20d40a9c9b2a293d9f4d2b953ccf8aedb575b6ad66bf43d05abbdf5170155dd3bfc1edd0d8cb70c7edf4398e0c65de942c31f40c3610d3f8d7bf4706213ee3016322c1c9c3cbfa975dcf37f8cb90381177b897125f93573825a6da231640720901e9434536df2492cb35e66fd51771b5ca510bd471720489b9854011eecea57dbea99758d0cbe7018a0128507ab4fb0a627d1f83f806aa3cb4487257a1b858f21d66289fd203c2fa57994c67b4d86782ca77d2cd2ba426c5dc50b70df9a82ecbd6c00d89399d2cb1a261367e2808b25755ab11a7d5154be0e944d4acda4757c1d7531aa94e2823a06c09f61908a0349f5268df9cfbf3b70b97afa0bf35768eaa4e4938734cbe2dd68070747b2b28b02c3d11d68442f705f1478a8402e1bb5c166cf8857add440afab5caa1325aa3554a8534ec4f8276ca2b21081e03c413546a707b5a60cba4b3adad8db1c2632b630a31c58880d1f86191b354b1b5b0c44a0111901140f0dc602eb90932cdd63921863490334bb882534dc68c356bd79d2232091538016b6b6a0d5a7ff4d309ac0f69eea74c0b4e720033056a6c734299a4d7ea7900269861fa0e53ce775dcea9a94bc343d39adbdd17347f6fa603181b28e08c47014fb183f827cdb1444cc9c0b54a6a5934b981fe1797612d2b1ead2b7c9801c6953a3e2b97172c71afe52df5b1ea0f12d6cc798db5d6d691c8d70c6e10fa4c04450108991e5fb13158ae054070cdc486d47c4a50200c9b4c011032cc7d45c6bbf7f0675724653dd82e07ef0a419aaafac59571896eaf248e80db7df95b4843e81256c00b6c5a73af8742ebcb672d1b7da70e5d41a358b588ec7d341c2a73c0643c0bd307fbbf779aef5ab610b20e5c0177a9007d3b044db7cef35f4ffc4fa01998462e244d47e2145be31bbab98b8357fea9c0999c1c84e5a7ea57701fdd122bc21865a0940f989c938d8ddcf22bff032b41397340bdba126334fa1861d2d821b6aa1504cb0b4e2ab82878bb9cd151b0756d03cfa40bce9d06ab4ce016ac687381be8740e57e3162d9179cd07446dd38b90bb85711685cb585cc723f88188589a5b3ea976cba3e81d5fe7084aed9288e4013c90e01e0975699f0411c5e13c4cf62bff317d35916da2f191cd78daef965e8140cc39afb13cef616d1e9a196c7548e400b576392a57342d22c1292e642489ab442d27685a43f9a4c0730a898ad425604db8ff9bf73b249c6cd181a732407f7abdd7647fc7db8ab24f989dd66e84a391e66f4f2ac9a5d08ecabee96d5bcfb14245f3457a82468a1b1b2aae47858737e0df6e379aadd0559383193a80c589064f754b11464693624e98d97cf261bd9cde4a367818981470a0cfda9ffe96fb2547f98177c99ac1db79620d93f6091ecd212d34352fa3fd594207f9081e33d58faf843a301d2b7a662a5604180c8507d88144e90b1c77b0813f16e52381642e4413a9b95211acc8f362112897744cc67259bb97b84105ac930788f7819ad641ecc171a845c9a05f536600599898e025b0d8d66a5f9ce68625d079a951728ed40e5c5da8a062e15149ecaf3583311581d7769e11232676a6957785d32a5592900568408f7f84b83a2219cb30b6ba15e5256528279207cc41b4c8e95a2f70e314f3e1da0e29fd8397e74708f6acfce068a92db8e98fad0ca7f5575dd1c71112ffa7a0a6e6574ec7b83969772ca8413b1436cd75dae1373720ebdfa8beec3de931a21d971cf8f58233d695f0e59519914db3025e67c2f598aef8506f813389c79b8a6a88d4d338e4ae45cbd792e4661f5c127381e1795d0c5c3e7629e74dd608691d8fb5401261674cc38c8f3325ce8c33f6eb38f931a5c725d68a03eb9bea0c5855e13b09ba6caabaac83135c13819d156038850d78753b501a19bb3ed4df02041bde9b9be74b824057eb5670e1de0136a92baaccc637971e5616ff8e7e9cd7d09a5c1416d7efc39c8f92c635b2b84508ba6f438bec09810b569409a675842aaf3ef0886fdc74159ef9ba84a15fe2911314e3d18d1553ca8925029c4fa555cc4323130d8d34fe0fa7fc8bb4a364b9cf1de2d6f60b9288529a7a4d9735647ac4bcc46f0c02d97e37facc228e908e596e6f694d18ddcd6a9df4d7253a67f4ffec04e919aa54a18a2815c495b654e01e57f622ae6dec4916aaaed19aacc6d2049ab6a5d2eaaad8b93815a62fcecbe04ec3e3a935900fdee12086437113c9bb3711937752f54aee74921fb1b6989d98eb86c0edf64513e35c38f6ac67e751765564dd0cb4b4e9edfd6a89a14e7bb2779e36db233127172e02123de3c208cd83d84cd893fa95e9c236bf6c609acd9528c1f26fbcca9bf50af5237ebcccb29d192da0580ea719c7039fb455cd40797eb2e2b714398e727f13eb487d637bf05f1ea5fc419a06104e5cdeb669386911995dd6c7c6e259ff066f8921a26a25bc3a449ca19c8bb6dd8a8c25f592acab722a6007db8bba0394c01a62d73855a93d5b48d14ad18ed74da8267b82d425771e3a488851c3cc1a2ffb02bb977e29e14f153ceef59bbaa376f9e8681007493ef37e3414eb72402c3500e1f7513f163e2dd70c1564f5bd9ee021d0cbf7c45ecd93d7e6d9d64c478d6a2d617b02138168c253e059b686e2602a62586a247841e1f68259efbf874ebf8fe03c7302ed090bad7d430b9b15efef421880764a98d244556a9a132b86c4dac3032d01500d909fc7800c70119758893d968f0510696c6dafa57d8c8ed016df964a07ba00c62397fd74a0db015936b48efe2289c7ee24e2dceb6abfdc15924bafeb19603cab1891b0d2b24fcf6526d62d8090628655b0047ef13d720f7eb55405cce0cd0cae5c1311111fadb579eb1922a8c981e310a27ef83705537da3fc6c08b90bb42584e655238624335b594bb66cf75e3d095a1b97c9ea4a36755cfa3f73bc2908deafa2f20d025f3f82f50508ceec1bf780987ee332ac02432d602673eed0e711e58860519b2940112d72c955a2c127e8bc5790425c02ab3f6671a2a38931858c652bd77a58d170862d5c3387a24d5efc193251866598be6b43209ef339299b51b53321c0877d2ea084bdf8fd6e065cd7d796585f0a7256e8829b8383299960e98d82a51de1ca0377ad3d4c4e12d46a3b3419850d0a6aed9598da48b7e12cb71dcaa74e8572ae71704904816e2107e690a9913a98aa2dd87ed69800dc08c7c64553e344afd59ce742c1955005af2b1c4d9d87be514366f7f33fc1a4abcffc01699eca3777b10b4ee4a9bb10f2e794db3f85afeaeda7e01ad1b269b07a6766e487c2ee24bf4a4db43183e6e69021bb3f667f0801b0df7f1d578dea61547130968a17fedf37a181e4718668250fa42857258aef85e48c6a64691a9a460858b4690a9160692844e9941411d2abdb2dfde444d1c5ffcf0069aa3f8880c60883807db47193f6aed0a09b4d2bd9145dbdeeda28d80afa034a283b26743308030b181163ff50d7d96b675df072286dee075e3f64b284209098d6ebf5e8225845da2251c4a66a54e56de58a0165e42f6ceadab3aa2105a0da292e2d61f92598fbcb45a6c8b1c396303e9a12839e64e7ac931e60e847d1fd866bcda5d5c4ba3e581fe10ba99b4a45f40e3fcc00204f72c0f745932d052e9a56d8d524cb62230b23cd079f744b5389a35825bb9d2a9aa3fcfa197a073d15b29cc197191f5a9b1bb353b584328f6ae8712b41db5bc0e3dfe60fc746bfb01c855c4c7914267de9a429c5fdb91326ef0d4cd843e4c49721027db1655e7c099e7103168d42f3ae8467e4d788c1d0874294085307499503473c9abdda33a6a61a32aa0867d8fdaa0196db6fcac1f127ecedb00d2820b514844a52886bf6ce3c4d3c47a49dc6032cb29c953310160dd615038943434a478375a7ba90e99a2f8c12611e7ec13850a1ea4a13ef60c6e290779e657feabb185669a835311b6c0235c2c61b0790adfeaa8890ab762ebad010c10db58f84ff0e1616485f87f5609a2817a20a149f3678ae0580a581965e39f562705da09882fe6433217f54f8b64fa31a3480f1e4c809c907d9e138e9bce4404ab02bc55300d93224d12c1c53e3e3c65334720b880a4338904f3f2b8b5a70630118309c8cc6ad8f649c409e1f5f94de664d131a1f3eb724d1d55a1e818e4bcd530729de090dd3bb080d0a45dee2e09a84f5474a03a36945f1cc3ae34baf7cc706649d2cd4a9de4f65c7ee679ccf78a2d5ffff4bf4b595f8e9d5584a255279d0d99fd6efdb0b5c71292c927bfbac515341d83896401f8ec02b4e22af0dd7fb6ec79def4b7321f7f7245bd6b0831e2dda1c9a42f6c7930925fc612bd439afb201b32458555825e085aa69119e11d28370ea8b62c4d40d96a7990010e6f07ad0e9ba6fa275d108c0bfe4df7478be008a7438aa3de44d5348b514e034934df6d4980117cbc082aa112fc260f4bed08f7206cdb71964a971090032d7bb4652d9fceb7bb72337d7ac2fd32436d6bd209aeab38e5d6fbd479e1cf5b426cf59364e160b5f806dd8832917ff2c71cdf9f43a87c2b2c65942e2b13b0df944669a0eec7422c4e94587be9bcd00e955ef4ad1dd324fac0d2584393249e9a9ac80c67ac5b205aacce72154db281c1c554a3dcc3412eeb8b01303d072968714d7d656b9d0a180ee11df4a15bca936c8220849fad013931fd3a72aeb972a0fdf2aa45a7ea1cb83b69e7c6d31bf1c622354220536823a20a72a529c8940254b969179b44a29ec5a87ace8971fe31cc479cac16d575dd109205c7641d9b50ad5e83adb71c4447b8eb6ed9a6eeab69f8d7ff281444f58c9074d029dba8e74efd721c710609b7dc60f801d216738163b888c35e37a6480cbd62eeef63e0933da08f5591416daf58d13701d20530dc0a2b3c1c91c6b6d235090017486b0d537056ad70b178612cede01c81f03e5acf1e802e775e74dee04c0483b28b5e71605ae95565d94e29bac16984e78277dc5115758e48c5a79f7701736aaff7ae9e9d68c7cdfd738c004e4e3a2cd5b66c6c8453260205c777a3fac2596b397e15d56cec5bb23685f23255651d8c0aa4005d377b7307a2306de98222560f449a4f16df7a98caafbc624589f55292f8d95631cf571942f8218cea79739e46c77a8247fa669d52bbc3eea686abd5691ba2f13a61cddfdcd80a9fe37f481d08c6b3d60c75cac448e3605fa10fd053e68c0059ced27e5f2798583f84372cb7d414a830b06001a5db0cbd44b1c19c1c76e0cdc9f58082aec25db7f9e35210c824355565a1c466fe58825522942676163f166c86afa0bb10704aa68ac017fa9bc9b16a4e0dcf558a5415ceec4242bdd546b4ed9a0c3c23cc036ba02efe26911a9da67b5032f61c1403868f3db50f2b29c7f120bc0ecf0350f8dc3400c16e48150dcc70c80f130065ed795a419bf197fdeeace02234df4a22503be046c2339d100287ada6be61f210f7345f1e986d8925e1bb0b88a663872bfa4095fc3d61cfe665fb0b395ff5da3743bbc9f6640dbf135d77976ad0474c24817457d5868a330a1acb13f4bef7bf7d426eaab4a0d8eef32e78c6636842cacd731ddabcdf8f40bd50512cc16d42889e9df9e62d75ea0157e56fc9dc063e18b6c8fbcdab952ad67c978746fcc3a520808aeefc8c170e0f5e8e645a74e864a489f700d180d91471758050ed12a75c410c701ddb434190ba8ed9ddcd8b94f5d29652cbd96eea36290159c8387cd44b98be07502b7d1e9ba95fc7eff6509b398d99324a5c45631e4989a18d463d611e43200d4c2661702c0f334f7ffe3018cf7fa7c66e417bee5b9f2df30d516cb8fb70e53e0e8dcf183dd57a858b1ef3e8fc080c04cd53642ac9efe6a5e5388484fc277c67458bdb037b836c9131d1bb6c9c77b603e72b6894eb7f00690a96b292e6731f9af2566ad8829aa61706eddfbacfcb99ee88b86ea8373ca44330185f995956119da8921926a61c0274f06f6e501da71013ab79cd3d1804fffbfc80bc20419fc2b17598d5ce42fcbc0ac83242625341257809402557da8bf374da655211dddb51bc8d56c4f801a34d0ce795eaffb2f6257937fad756009fbdc686826d6a74ee9c31ebccbdab4209adfb56484bb67d30c4624c8ef6f06ec11557ab585ff35b5d7612726da4f21f11dab75b48a3c9fa2604211a8aa6e6e8a02fa22809a18af5e0abd27f38d6d710446a85a8287f94e2d053ddf6a5ad80a06fd766b154d6ddfa3381a4df327be2218bd4b18a04a475f0e765fe113eccf7bb617251167e6d5b19aa1f71997bfb2bd3c5be744281cbdebc48cc694d464ab60415c68f546043b8d4a047df3d97f31f9c40ec081876101c0b3a8aa21334642939499f6b1cc00fcccc2b248ef229234a42bd233d61ea2ac5982c950c41d9fa207545c9f7e5148df6df1d5197d401bf543905bc56c0cffa148480836987c5291664289fb66b6aadad0a76ce4d1d21786e6c26e429e80683f3d03f5961ac8905fa60d8281108740a48d211240f1c46d163152d5c7f22d2b81b9fb5d77043422f897680ed15be9e9148de15613611ce201d5a961a9749231c1f47ae07ea38e4443a72d30c67731abe7f77e50fb37648fd334f1438aa027ebd92654b1480832a62802e92520735f2ccaa2825048364791cd452470660f08c0de8f0f118e99c9a77122beb5901a26a341efcde9b81d31f92d973f9dbbfdbe49d595a578c31b49e93d9000da3bb53401b038832c9c1b88747706900fe43a244f7cd2e4fbf23f9aa6415cb25425f50b6fb5b4d9d951bd05f3f83d46f37d61e83e367900b9622864e7a0bb3241359494954182740e8394752c7519829de8672e64c3c22adb754146283f8f250b95239f2921e04721d65ca566f7e03cd06d04fb7576303d3dec845165cc38c381f573991e91541c3ff7f34ac5ec87971b68c51f9becc568c8cdf934ba7c0af4373f17f1d98d3236107d510ffa61eff1e07947bf270a15a1c3d22e439173e21bc3038fc0a82a3896e3d5c849cda46a089d744ccc4f5dc7c05f141d700c6c86e1732c56419fad4ae3445b3899f811393948827395650342e36c4a3d3f8815c93999b71e273cb4a194f7a3bf0355da632cc3c636de9db95086f9d9bbd3a948b26700d0dd7180ef6848603e451060544ed20901a85edfc04b1910f55d0d3516f1cd22c96c13c849fa33b5b4c5885002e407a89d275de5aac37ecebd2a4c2e5c016b837020f9de936e7deff1cd4d656129e7282773f948be900c52791977919ffc2e34b5a5f7c87b5f3b7998132ef958faf6d817747d4ff9148d704c660167515fdbb54a88ad5bccd3a9390d440a908b3c278229113e6a30b2a6372171792d57293a5fe056fc96505cda410735c530d22cfecd2e178c5af61482917403617d5408affe89ec58496b80fc12348e08c18da8caa976ddf95a86c6d43a94316d0d60356849595649bd88f11abf4c8e76cb6e0843b8fae5de2d25e05e2e7a1a6dc0b18ee91b815d64f17f938a05715ecc18f8ee136a8bd098fec477babe91a88e496787c7fd4d8a435c9a6931726252363aa99f55d0dad11c2d436d804a2df826e9410492aabe2abb2c061513c426e55cc015ee486e3fe5d2f35b8f977de98d0f1e41afc14a568c8ea6dbf23c81263d76f2114884a18f0eeaac6468577b1b9c5ca3ee4e0b013fc95557009fc39d04463f891f10b1653862a8c99100c19e80c4ae3736a4943ef74cc7812feb13c340069e0574d72e9fbace026ede6d22f836059b51b9f80270c7dcaefdb454dd4baac6613cef53d2b4338dd1f4c0f76774c284da00f8f0e08bee02357896b01bf6b2d73a09a9e98b256c1c0f8d70826d283b6d69e7bc6c97cf5f87f6e9e7a9595c2323f529e342bdbc2708299ed641209b03234c93c0097338293f8ae2134940291a309fa4b439281a651a206385ece16764de1e5886a68eb3efd2f3d1e5bf2592f917272f449ef43e3c3357885545a905982ea058ab204c3b310a018718bf4e983c156a84e94fc50d58ddc566bfaa0349df2101f02f64f0e149e552077a033abea414ce3193c7024b668ff57167037882976b501adee1a61e464a7ab48a160050f1a92b8be29be63ac97c4983b015888daaec9c773fd56b9669028d184ef8e1cca35df8b3ce2255a24ee6f9b72a4c1561b493abbb5510a044efbf84664c2e862a756732b8cfe4e015436508e7c6a6c6c365592aa697f073f70dde809197a894548d47765c517275e5225d8148381b2b58ef866ec477b329985da4ce4c960db3bbbdb4d4f5648032bd9b619356e1a2de3245bb753651d9648ae5c3dd63d536c7854a28fe08fe2234ceb0f7b50d881630fade1cdcf1fec7187253fa5efd8c0ab13469656849288fdb8f06a473031471cb288499e4ce6a87ce318f5e989f12881c7b071e440c60c32ffccff0cdd1e8b06ef509d1a7f1730fa3125bbd4b864e10f208d45d553642f0f2962b5087ee48bdfd5d3edcf64223ec36c3ebf2d9c652eed3983a2287c4673673114b24cfaf99ec3e8d32f93f4a0249c5505450439081859c6fb86f14ceb3fe3179e7800eb0189036698fec97002124724580b027abeecf20dc90080a2bb32567e09777b87bc60d240fac2311883b84e2687b2c9300e9a709d9a24f582db0ca6ea67dae8fa0ad5ad1712187a11e8efb0c1f385ef5c897a117710de7e77e18512859c6046f2b4a58dbbe856f6ce35d639bfa538c2d9049c59e04ad33dcb8e495a87bb38aaab4992f6dfc5efb13f00ecb829f40ebdf1ae3d52aa92efe283b42746826d62a9c88baa77c1a01e059d88b41b899bdc771127896ca7e658b12b1b8c40829ff8b7a6ab6a9555a7a07711773afd282a32ad08047d871b73704750f583f882b5144a3fa1e9d91ac54f473c70e6fccd46861a98b721df2c3a53831ffc6d54601ade56ae3d3822a9679098800a7af13282887078451d030d9eb38fc91ecfe35bcd8643917b1d66d5be28bea303137d04ac138280bcd0d8623d7c480908d6bde33263dfc8071f7b87cd39f8bd8602869a5054477d87542b1499e0c487d56a3398839c195abc43a55c278ae594c90a766eb63283c6f1f0b0c5bf7c4ba1b9d4f263062069d088ef3877debaaa7de3561fe2463fae109eacad45a26fa6b74fe19184bfb361fa44cc9ae29fe2a4bbc7d50292b2e21ae12e2de2bbcbc3465c82540cdc20e4a54152a4ad217807197f2ac9d030084eec584229a1f76d8cbeb7be5cec18c206018f0d40837c1893ae3ee424d650f1e367334ec9bf0dbb22dbe8a3eaf61ac5923844659970745e517c79857031547025181e0bf71e569966ad0e5b065c547318e581f22c8a9e0698a7586d1d2d5420afa295cfc24afe34ec4e0f1c84181260f1ffeca482f82279cfb097d644a487d0dce3d80b7d3264a7903c4187db4cb680681086ee323787b3520084cb121ca8efb5ddb7972454f980286d3bc985552e3fbce2ec06d24261a00c80411b15e40483eeb950e051438108dd908c80dd0e9fc6c2dc1f18d9f07568df2fd51074bf4fd22f07d0628ac8ab3850d4c803dfc873eacc853c0f7c65335c4f00e4d13d79cced8aec43d2ab4630e4b3b347ba728b30402ca2983f2c4574a5fa49a57e9a0baa8bc46ef340962542e08e719049f6636939e93312afbf0303938b1333ca941a345ffa537a7d13ca1441ddd4d9f0dffd85e3b261b09fddd6c82320d6f2cbbf29a484e5f071c20b9efeaf76a0aedf867fed1da6d823218968670a225d9afef1ec31e7aa6ddbd095c6a9882348ce9570bda81c788465f04e74087bb6583ad9313e085345c63fba92527cf3d71bde1ccb7b5ece6e1b2a6d0953e8e098d671a4f64543e336178206b9099422431520a9ac00f63746626a11792d61756148895c06a078dfe988b2a14d2f96e6270e89881ea2c25b481f4c48b1d6841e15d5c9766c5106cdafdde7447dfee522be9d819718313449f026d2dc4dc40c916faaab289d921c774e85ea7dbc28d21180c172c064f9923d2d0115a118dc6ab93552212e0f05dd9a540501445721e8fd4fd41dc447be645704f65ba5356188943851c84739ba4d26db4d59539819afa918b18fd674d614c8dfa75812344d5495ad928f50ba89c053a86082c0d414ecf18c8cf2fe567b39d8cd438f1ae2a2a0511905aa12cab31b1bd00799051a3b7796a48dc1c501fdf044ccbcbf98caaa55d7044f4adaba44e9bab458901e428ded6ca215409a9aa33678794aa0dcd7cbe0ebc7fab7a9ecccb0e7ad3a6e89d490d9f7f9bba4a2d617ca1eb6d1c069561a64ce764e8eef6bdccf7de4f60c735bcfa51082f44bb0bdb87cbb86e23831bdf0454270efd0be66faad605d6888d1efb772e861764b3c36edea9711c3af0d04dc385b7b680e2a5f028a957c9341729dd5b826b71e7d31abe53808b81ced989a748cb027f92b810c81d7adf6b13609430aa830ae5331d42754ddb8abf53c6a88ec4dbbc51b10c05870768c91069cc3676ab4d21c542facf204a849ed831615ee164d8967604916515b75ad4cfd7fdb831101d41a3f7d61143f8a1e2d73e3beb722cabaed4bb5ed97453156443c9f31f50e0432aa49b54fad215c34aca70d46cdd062f9b95eeb0ecc78c5a8ef1437866f2e968c3f5db238a81c050dc0c679b231750b6697216acf4e65d33c10c4fc316a4b11012ff1578c54f03b6a2bc8fd2312d8782e6a7d3009392289eb9fec04830dd52b3ce7ef407ad37f49f6c9813e2e4777961f653e476fa650f358cd01e436fa788c0f9370c36c0e677d19f43830f0c1c78b339da443aa4797686594a1a657f6e35cc75f551543f95e6528c8e75aa18ef80ad562666e364a030cdf82d272bae8d8a78a63561a70a4f67dadc3bce7097db4f6f9419e9f82cfeaa44ffc5990ccc3c1f4c5390f3e4ee8d961c0d8e781afd57c7e894608c733a3392b2df40181a0e66273deba0d962eb87dba75195cd647d32e5d8df1de721ad766cca79c1e514e1fb51cc689b6f8ea811f3c41eeb60b5685d8c67c545c8d4a4d63f04d3997d36024df4a200b17930cc3be6bfa5349768fe6a44e4c956aec1310b8c79e2a62265051089510f2ba5466df1252e3d29c4cefe875025a0dc73c3ae11e5dda46957a1f01554c04c48de29c18eed3b602aa937af486a7a43ae1da6b919a165a30455fd643f8888b556b759264d70322082c32927ba2b87f85bd221426d30dc6b15f0c341c4196195658a333ac5c5980c72468b9101f6156a59226280472c5640a53b5fb568420e88e1985ad93e70d63b84036876c2bc98c3820ab16fc214f66f82c4e4d38014bce6c726422096a2554d5ea2e0a9a48d66b0208ac41c0e0faa3dd55f37ed022f65c2bb953e672f0809f3885c034e9a8f478896970423645c226472661f078a2aed75f00cc4e2fad1162746d220605a95eb3ecce0cfbef5453266e34460c8c297298d82abeb1ccad8726ee097dd5d1c8d498d7d2780d6df64899e342f14cea831cf4d7adfe57c946abbe0cf6beb317c176f4a373c612356821a9588a21fa2467ec0d140fb3c05de2a418978d004cf180c4de05f9631bdd561edf01043e374bde20acf38a7568c20b94383f6fd055d1cccd251c1f40f19b2d6d9bb50c2d2a6a6f4135d0a67bc2c9ee7ac15a052084d6dd5286f3e2ed7008c2d97053d86897a8164efc175361a5de81ce48228b9aeaec7844f1f098fc2e684578ce0e44174f58409c0b801e6a037667244a59c3cbfc8539dff1a27369663ef7a6e5e9450ad6ffcc356bf502365b01b0dafec6e4b7e2e9fd538b04ed6614ed1cb3f6dea86c20a41cc23c0748eef9af7c420620ccde82e429e091a328f539875274160e0667a105542f23813b504cc761f036bb0048728223c34b85123d7ce6ace31880b0e1b69e4f093a95503ef70373f085a3bcece7772a3d8e726050245fe19a3fe0e4c3710b0b0ee10330243b2e013d31c4f60d379e45c347c98faf2124a2b030d35881040bdb8cc8641890cca51910e1813399c2ff9a03efc25331eab88d83fc676867d496d5a8ad73170f80803e40f34ec7ad900d57305744f8ee679107fedad6d056e46eccd6f7995d66ec99a924f10e8757609e5a0aaf449953c225c07405c7e3ce6b8e01c41bc9dfd3152442378b6e1d128b064b3ffd1a1614b95a3ac4d53934e5fb7a93e750fc50bd78411367eeade14718612a582718d335e5ddc78f940081b826596b3dfb3a424f4817b44de80582c0b544ee101ddf9ae9d2c78be9f23178b04370d62aa15caa7a3000a24b5f3dd6735c51cd0ed03df33e391274593155f44a8155f68ab21de62063cdcb6a0483e6cb4f948f7afac3b39e279e0437cfae008fd2bce69379f006032d48b49bfa2b9104594b2b3bb0fa020952ec82900e7c791480f5357fc34679464b4391ac0115e05ba1768250611c16970336bd206d4be5fd9aad7f1264640efe4191e0042e4c947af12433c4a7ebe16bf899e2c629dd26c2b30a39ee99d0210fd33b96c7693c5804cc8b934a94e848c1beca4f32a1d7c988f5348c4246f1d039c49964244e1fff38141d3557199fb253a2111855faf437ae013fb983f4d75354bd721e6f020ccf2afef0e3a97467361fd0fa4308e94e6df07c7496818ce0c31a2c6777fc32ed012d9eeeb435ecc877d7640102b9259d6932c6e9468bc69d50ca1550b1c9135d7c628f6c4b468396bd7b94738039d331dd391b3895a44244ce967a4add5042401b3aecd8218aa23766087ec7bb4af3a14ffbdb8376eca40e95fb77f3971e3bae4e387080dea0d731dbcee095b55e72e3828ce085df5b9f93c9708ec850c551ca1bfcddccc5a9cec7974d66579c6192800b6b2ceb353ab3b76315230e187461d30ce0e0e05d6ef2ccdac58b971e0001f0eb23cb78d85dabedde21a8cef41d5881def8bfab36347b5dc6ec724eeafbbf978b7abb7eb61bc1cf1b2d4b04d9596cf9111ea4199412d6494ade48de3086b5bafd4abd8f23d2b8c4695df2106fd582425ad5c7c8b10ff6c98357dcaca9144e1df0f211955aa49375dc626c3b722bd8d745d88b1de278941ee7e8c38d355f452d2129cd00ac607c0671dbf7ff7bd473e1332b7ad84c53f2deee9b3948e3792f1feb08111d8f72e744cd6c9bcf03e9a769a4c303213a1beafdc66c27a749702aab8575a644aad71d09c671166505addc5c42a4779e61bd8eea4913abb46a0624d58072407c1d27d88fcb28297d0b1dab744fac71e5eeb4df3acdb5ccab15db3054d8295122ca4419473134d2483b7db09c1309d8d8dd412592206fa23b71c42875304a7f59cb44251c53367a2d575a818c13e3a14689ab56a785d2229807294d8e447fceb5f075f34bd78180acd48089dd061fbd84990f0b291665d8ee1d7499035799a1a3a03bd5da4ff23bea40cfaa9b08daa184968da92963251bfdaebea13eed885e127b02ae48e21bc8c830c968b0818d10dc617e785fd454692455b4bc976a820784aea0c9a3f1eebd49a1d2568ee4187c49793dbba165dc5c2554335306fe2a5491507cc6038dc45ea037f103ab1a8d3d80b4f5090d5ccf55e5812475132a90e7c640bd3f26e45ab2666be38e3178133090b80579ec77d7288622e2470b1fecab8809a2b50db7a28cd79544f0f8d1e4f519d98f66b45445a05bfaa8ec6d7ab5df7d26deede736fe218e88d400ba117cf69855ea1be52f5633ecfaad1975c007ad88eba8648d290e781b75c80e5fb82ebea3d29adfb3baff97c844ce928c70d9c05e1a49513f50f18212035ea128c8e8893e54043aed4ab72219c9cbde17f563e009553eef1afbc673ab3c31063717c5358a9d74361c4de0d9286fc804138a8374a21c057d22ad1a94bbbd9b148efb0115ab8432a294e41668c415b984704b68c5aa585a760b74d2aa2829ba6534b14a2a15ee62a19ff4e3205e68bb5bd9bd4852f378d41192b9d206fb99cd2817f41e9954dbd21d48bedeb5b26d422ad180f3fd24b60dc8779c315518c4d0213a4dd6665a05c4e1f755ce1dc747fc5ee2c87ca146b12b104f5c473d7d2f6e55553fafc276846c092bcbb24a2e368a859989f268fa3c8a8c60ae76f6d3a37ca2d9b14982ea70ca121a30596a510143baa35427edb6d60f1dbd518b5a764c8610946d19fbf8afb62c8d669420d79bfd4e8309e790cdf46420f18c14c181bebb5b87bf225892f6c4ffcd88442057230fbf25a6cf48cd7b2256e4b8f9798d078b91149aba8edb290f99936c5743103bb7730d9cdb8ee63e192fa143c68ad9614041a82ff7fac63470264cb35531dec96b07498c3bc16d53e6b4e409c5a56baa4f5061c06d935e18526a45f0ed9e924824edd1b5e6a595549ec3b01418d1909cec184084e6dfdcdf4abd05790fbc099193a3e1a0f199affa652802a26ab4886c8fda9bfa1c11e0d3f3b43b1dc89b9cc94ec963c4fe78b1efe2c86c18b8aea56a8c3a2cf4d5d501d7c774dda59bf19a80e5958011110df98a51088905cb514fa3d97e39db052bf0238a9a23ca50f90ac000a0bd9b01938c7a1a0a6f56d8a5391904d4843f82d3e7c6f24c28261cadcb3346b1817280c30171f32241e01a04eda3830f924d122421ec2192d20968f6de42fa7139fb81c5adcc6572cd9f0f60efdbe2e6ebbddc11b6b8977401a46ac06a48086bd5a317dc50e50c2c82960b60900b2052b44972ce5e558c6c015c8fdfbd3dce04856390899f4a4a303462c18b3d11f253927ce4b096f18a28c79bfed7a966ba320fd684f4ab800dac06f371edcd625ff5ece7aa3f05a0dfd995e7325d9941351297c8a804f466dc8add36b1c1df4b2dd38c11e883c506dda02e75fc54ba3dad5d97da4b3544ffb3323161ecdd51af3d19f3016be372644ed7ee6376d8af751b5c5518fdcc0c2b2a0029e1991e5b87701cd0519287585c20712d1fbcb052506a36c061834ede64e228e2430de9012ea70801eda59d191ded38006381144e98cc47ea51a70a11dda385698a965c1521a7c43aca09d86452c7cb14134146d672f50424da030b2e1694c2e5ab35ba088e591e1df194d2b526ab743112c1c31ed0d1e7f3c622642bf73159959c3a5f27d1125a4a476b4b192ae735ade3ac8d73d111ba95e132eb889c1e9c3bc706b2e389cb310743e55a46b5fd7b216c9a38e0fd4dc4ec05ceecfc92b97ed8b6e416a3413f485cfc6796d8a5befdd7b9a9031fe2b6ad06b73ae5d9801ef533dbf7f9f387881818a11235c611ba4c43bcb4afdbf9f631331768dba9e364a1ed31dfd6099e09006ecc2ff3fdf48cf07c1d2d666d33e146fdb9f799372cb430eba0f174c04d18a40b0b1bd7f3cd9cb8ab322120ec5b708bd1b070407c8360c2aa3aa03e53e69c39de422bb1d6c2168e3676f08789e2904314c7c9bacc62dbea9fb2813bfd192c520e4bae8315b245385a0d6e1340e478a73c2b02f72d16081b2b2d3b4b78ae5850b9bd6da728630173ccb5d56861067c10cd8b3e4bb9e5296546c0bbf64bc48a45169dc58cd94e40b43aeea26754cc19955b51b9ed43f8d583046bf580030208bdefba4b2ac48760b64ae47e3f405a3156722db58dadbde02ad78f73e48dd7783c55bcaa5ae2a4af0b2e8fae333df1a83256adcc2269b4a7909434fd0bea7c1c4f14c81e96a408376b081e484af0c05648cccc98887317940bee0cbdc3bbc8a090083e37170de6038c118829f91ae9018a1f8895e3d36ad4432806d8e526cc3132d59760b13921ab49b73d4d41de89199d2fc8cda8c57ceeeb5fdcfcbb8418939392108c1ede87275fe910056a7f2920e80f71bbf6e86985250bf7684e753944e0a4307a2871867b1e1584882dd2da20df820f579054188bbe66db57b3b69d91f68dc321406ab27c6e0a1b3cb3269d1258399f73673ff45d4338064bea03a307f763147487dbad7b9d5cac3fdc3ba7fa877738613f418f9b633dea744c4d807f5b7f25e09a31e84d687292fd6e5ced23a92f5cbcd8c4e98cad4121a939d3a0a7b6db860659cb0832c3024acd071c7883cf60e36b1461dd0f8ef473fa3fb330c7d2038e8ce0804656c30a9969ea840b98322d74d6f146fe22411c9e61ca079e0498fb8c868713c15e329c1b48f4cca62149177d10038ed444d80d3c10b8dba403fcffffffffffffff31c2f0a325441b914694089229c94c910a55fdca1aff541f151f7fb2b5424a29a594522623cd5cd11020d6d62cd901b80f3b0d3d0dac4fb6e5ba5a07981dac940c9392ef56f25daa8911e443653e193976b4673420d6f07810e833981f6074a0d6be92721d4a95ee9826c695151f3e6c0851c9f19bcf901863ccb480548c6072b09833d6b4aba06a0caec613ead818c2e2f9f490df222446150c0e303858ec9915cbc98be5ea1e82b9817727a9f239e5da69490d17396ce0d8a2e56508286306c606dedbd8d7c27e724ef89aff649ed33cc4089311a311fec448024c0d927b8ab97be8abad26c3d0a0b5cda7e07ab2a5d7e61699417be67e95d47ba76fba11194cc9ecbb9d327dea2d05c5c0ed43eec96aa5760bca356090d8c2d55ecf7dd204d9650d86dfee39f84da92965d3c4c8e16c8aacf102b8e6763ff95ea1bb4ca197f74d31f866c3f578a9f939a25e53ded3c92477264f20234ae75b4ede53a143f5d44829b57a2fd36ca552264d8c20179eea5fcdfe9dae49c83431ba84dceaf69c62cc35976c5f13e30704e554523e3307254baf34316e3efe7e5bf9a7af7a8e5f13a30b1ceeba73dcd263c59cd4c4d8c22c3d6caa4df5a4bccfe281efedf54a3f5f97853c9e0eefdce476c1574f72b2b07c96dab3b4ba1a2ed3c44884b312eb72c7667bcbdd9e481165d5dcfcb4cbcb3bf4a01640f8da9baad7e76ab2d7c4e8fc9f1863ccfca6d3bfa45ccab42f4da51a5fff3a3de6e79e092e915d722d55cfdf594a6b4df5a5d69249cd7dce09aebc7b3f29f7ff39a9abcddf9cce3da5d39d4b8e3d964ce7392f2d1e19e3ca0a86a420887ca9bdd79eb9e78fa5d43431aee69279d0875bb6dfddfcf692f2949ec1d5c41863e885386ee9ded7a77dba1832f99c169721308dd97a7972a85e82f2fb33db2e695b7dbcd69c8fb100ba4eed1c36d953cd879a152cd5bc7ed35b70b57aadb9a10255ffd2abaa5a870f7a0a58976a123a94d0a5b65813a3737e33631412512055a74387f2bd8570361cce63c428e404ac094ae77fdbd0506eb05fbea552b7e77e86624f4dc956932e9f6af086196cc2c91e9b0eaed48d411f65789b6ff6ea53bc1abb152387f392c790f5abddb6f56bdfed4e17221f1c2a3b84c0c4aac1b7efdc75b553321cc2a4567fb186cf5dd3c4f84cb0df4faab66d6a4ec53ee3c5189ef6a7543753fb6a6fe21023b11bb0fddbda995263b82eff051b101197fe3082bcdf9676aed666e3b9aec9803631422049e74bdaccf536f5ed9a18637c96d7b4f087488c5f84c1deeec1e66e2d93ce4d258131e7949d1c6cca6a3147c4a57fe588ff5079221d2623c6129480f55add382597963e093ba0927ea4c51c4aa872f779a6f5a11a6c351564edda9b8d2f5c21f35765e9526de7399923ef9923ef9d11f478c5a694f2a55baaadd99a1875f0f05f95eb27b7d9effd343172f843e4b9a507dbb1b8b9f2f4d43ef90e932646df64bee52514e3ca4b07478c2f1d1ea2b23a52e25fbae073a56d2ea689716565e5b9a87c3ef44345478cfe9b96cf875ad839c6cf87361d5079c99c964c679369f17c3cc64fc893c33b1b231d974c0270a85773bdd9ae977c5d4b13e36a2e9910cb0c01099c3384d0b129df74cf5ae3c970382c637cd911a3d323c618412d1e3882a59e9874afb1a4d8d99cd393096d382d201c2badb85d33b66e176b565d78c19e64eb7e21b335d96a9a95a45c5e98d302daece0b4b06c54626c61d9c4c8e2d209a2f26652cc5a7f7b9c9ef2185f0d0fcb8a081ebfb7bd58334d6caad2c44fac1072fa9e9b56394d8c1fd648114076b8ab992fe51e73dd9a183f215066e58c943352422e2c1d50894f3a53eef7583684b3a126c6cce786639b5482aee93bb76c5b1363a6350675e963da4dfa92f035317e68f331a201b6ffd694ecebb5575d6a628c31c43263e4c082fef8d919fa27c68c3531fe7f260f055bbe67df991cbae4cf1223686642208f26f3317e915c136339b927afb5463c1f223736f53f5ccbd635afd49e2646ef7026e3f118635cc16163888a7c338c30194194572f86ed8a79fa6ca68991486753848b10b066ece9ddba6cef746962ccc4b86eb0783c01e842d364be5029b8144ac63431c6a86cf493dda494dc9e9f7cae89f1f31d102c56357f4ee7ef94a5d4c408f27f50c6ffc86666424f24c69514100b212346168f77f813a306c41a56017d80c5e3c910f9643201e0220372f9643212d8c2031e88400738f0810d48400316c840043090b2c50532608109548002149840062430432e1588c091d7643212b461a10004581eb099ffccccc713620a3860b50a34e0a5cd1cf11809598001443a1b0d8835290bf0800252029c16198d0c716710a08103283580070a10f27808b0c5006c0880880702f001002445e1e2e40226473e02259b4921c1848efc07221457986082149867e18e8909265ac10004741181362d5c002e0001223bb0810c38222eb6f0400738b0010d6400032917a800052620810840e0010e68000316a08004445a3c2025d2220894e802090d0810d9410d64c0114551f48128c2bc02f4b9f1f209753e042dccc2c3097488a2e83b9e2069a4a4113da1141b29d146ca74020c9c1072821929469a208b288a52a247632425c626d8a1095948e3f3e191464a87533a9e9434b8d8a2853f463a2e31a601d2845ade534273e32911f4b9f12126b251833b92090c6002880951282362d0e746184be04411c6074900a208f34544a413e22fc2a10c0783c1f0f8783e9f288acce0c98440cc017d6ebcb8b412d010b1c6484af44f097988803e373e1fe26ca6121aa0851230ef1ec9ffe9b8a4f8c74c442786400217a2774fe7640231e8a8c0868d152f52b838e29b2d42f3c388647cc408cd0f23252525a5a38235528ef826f344fc3345247fdad0031e9824c2c8774caa80041cbc94c08b95f99c8c733c273fbc5001414ae739998c733c5fa204942c84409ee2851729f263b285179ab2f20235a24de9c28b14cd8d27e2ffff0935c98608a838c1901f3a708c8143478a4aca11df04a0a4c5c40c9331809884a1c3849944208ac4e8fcf8908ea4468c2cf2480e278345a2e19d32a2088369e1cf101e1ecf10d07f0f229d4d103234d20588c733c4f3e99411fa785a76fc0b36428e301090ffcb233c9ce3f1f19ce690274814617a788678c7c5d323e409c2399ed39b1f214f100ec7a5c727d429c3bf53c67b826078ec2083c37109d229c33fb461d9841145911930f1f32097cfb7b808e910d9fce7d5f00809c222d18822cc4a4a47db1c5184c1b15ef8f3c1e1dfd9ccf74e19ede1e488224c23c108a295ffc88e474714619cc4f19cde10e9b8f4e8788278d17199518451d94c31382288224cfcd0a6b399bf238a30d0399b33b8d802f4b991518c247ec08328c258c0059b176c40cfb2f1b0a484d8c8773a2c8257c393027a11805a9012a48b147f0ee78da4f0e43c288d28c23c353621e68036443e0c0ecb4d47237f088fd0a65306873772e32e250881f8c308e1e04f8f50e79be581bcdc78cc60f174de593c1d4f8be73346873543fee3011245911930ce9f8583bc7470300b835cc070ce46a3e385d311238a30eee5c1c0b42fa2288ac5cb0e87409d1fa1e770d8cb17d1f4f26060563bf2dec12cf5f118097d27298fa2c80c9814fa704b1461f625b4e1f0c78533461461d484c523e43b9e20abb9e41012448c1f43740429a3041934700049e91049f1745232a0cf8d102c46013b6044146142104551006e44118b0b28e531281fcfe6f3a158b484e1d068683034161a0a8d84c6a2f19ff71145982e1e1aa0cf8d900b4bfb6f3e21941c390c200403443be8800e74e0052b38a1887b4a10819103154451b42608c2091754524ad65831f21d7763bb90af0687c32c208c4651e4c5242211456588410220390045a48407ac20d2e2069a288a8220e1080928c24a9128da18204a238a7c3ca77bd351400a222d6c008128fa7ca7b34911414ae1404ae7399ae20506523acf1962c4a3a30504071a582202b126c4633c0b778874e49b01dab4e0006d5a98d3c2b2017d6e24e5e2348001e8379fd08a8a7c3332a01feee94411093e1fda9cc81978144504f80426eac1ff09817ef3090189a22fa228ca445ac80006511463047d6e705a5836197743bb90afc60b7f3eec81288ad088b4880124223b10e96c3e1feafc1e0a41064c1a305788426488960f818a480f27a5b3f9ce7f9cb3d1c816cf90d8f981430a1820d18d972fc221d2d9d810800004208001e448b9117ad04be8b3f10fe3f816313c415e7103b4e1cf8e2b34e14121f0d7811a1e16010ca0e3f1805a5a3c1f0f81f83929214e7949e9e0c0810315445184880078668dce26251e31c4914d4451c48af062124535a0010cd688a2a8232d8c8022c421d016bc5386e7f32878c9d1f16c401b35363b3aff02cfa7870e1617964d6703c48887f3397eb3c3392e9e3340de71e9e109d2f9229e2163788210e96c3a1b6679323ecfe2c271710f9030429e8e2624060e06b9e4c1c3391d239e4e914e19a11697221e0f07d47149e93017463c9c971de6706f9102fa9f2fdc12da7036cd9d1fff7109b94c508a73a7843e9b4ee9704ae7464a87f99b39386e80fe8559441045181b63c07c60e5b9c448a4b3097d3c8d23c58535473ee5e5e3617996144c1491e185e3ef1ed9f994ff803e375e3a0302710b17a228d2445a7cf01045180b843adcd9b06c3e12b4f1141697cea7f88605c446401e1f2481007d4a1a41d248f93f636fa81ba90bf9ee915dbc84361f6ef187cb58fa8018e4f23da19697cf0a925a6d05ac715e8de770588d4c6c0171389bfefc18a0cce75b62949bd0b368429b0f675a4062e888228c169ccf8787f4703838381cd6e1df21b21102daecf0ccce8f28c2200063000c02a208738028c21800d31b2f3ca5b8c4113c114511e6079fb800179f13952f44514412068f1164e2071ef8c12aa2282a69c01008218a2530010e8688a2e8c409496005061151162488a208838954140106b058c2255c1445254f4803004dd0fca1059388a2a80502182d5ab408416404086788220ee8c361e95137f6c676c161e7f74007f41e27830d1d09a0a183830c9d8e2221f4a0b34614451088a2688b48035b78c009222d3a8288a2480ba0cf0dff10e83b99cf87361916cf67868e7c08e4abc1388c4387288a3c9f7f41ca4ba7bc6fd4d8384778d08208694411a600041800460042425f446e422c1be6e838b2c1117a213e5e8810efb8cc004451c4a0fff9e292834867e31c8f8f1f9f576343e4080f00648e30cbc7d382b2a3b321b2f988f1e1e7e8f87842443a1b9087a323d4f94dcb46fec73fd4f252e288cc1002bd8b0b9e8cceb788a1c3c8076961f9f88822cc09876508e461c984388331c194b0c82321d07b144569bc7c782337f33bdfa0d08340fcf9798459a20843f289220cc6379997cfbbb8e0411d4f0be8c978f9bc8b09fc431f4fcb737ec88e28c244a107817018e18f6b429b337af391a0760175cae0705e039a451131a22892c5e7c3238aa23462018b57b882155114ad228a22554451948a534451648a288a4a114511294411455128a228024514459f88a2c813511475228a224e4451b489288a3411455126a228c28425a228aa4414459488a26812511449228aa24844510489288a1e11459123a2286a4414458c88a26811511429228aa24468818334026efedc77ac5f82d086cae79ddf07860924a730c5fa395dbd5aadad96a0d4c448520a5e49dbb6f6f518645e4d8c3e484851009251f09acb7a29e7c4d4aff21dcf69178f3b8928946cea69dd74fcda96f2ce060c151c24a170eafedb10f2baa9dba0f8442b5e572bb54dab1aff16483cb1d059377ffdef4bbb13aaa4f27b4d7ba772f84f20e1044ff6d5966bf02d4ebb4e20d9c47aea74cd36374d7ead9a98ab92ba337d6bb29d539264e253534db197beffea1a09269ad3746ec9a632a5105ee2adc6de5367abccf15b9a33482ce1fc13544d3a645d70b94a4c5f95b01faa09bf156a280193ea5f4db9a50b5f5d13e34ae82731596be61e7c505f4bce3531c6184f402289f6f261d29fd33531ea2089c4b2b6e07a6ae9737e5d99ce6b4219202490589241989e2ed6a47e530bc923f6db6c2e156c56b6ce9ac692c7c8902fc29c1c2a322071847cdf6ffff9fb544d2a22248d60497a3f4f73364c5be43f0c95cd6748182a2a2720618464e8d6b3d4ba5f53872b61a890806411ce5ba64eaab7a77afa2fd2e9902882674bcfeea04be76b08074812b1eb98a5b9af528e1d2a3976f84b6e11f2f260a80c214104b7a4166c0dc2f99ab56796e4103c934bec7cb29373e1d3a480c4100fc2d4fddc6bcafd5ebe48c6ff478c2b24859012aedea4785d7aaf5713998410ee9653c7acde6a6bbb577ca8bc3c181a10fbc03c8164100a366fce4cb1e4d6254722886d8fddfb5beae05c2b69620cb9b06476401208f80fead3fde6da54d69a0c8841386e400208365d4ad9206cd8da946e06247fe8956dfdcdf65ec1574c08891f74d57bd225a7af6d9b521323cbcb80a40fabd53fd8ed3c2dc93c8d80840f53659373b2a40b57f3d5ece15d6bc6a67aaed664ed80d81862a30718367ca8e4d80144850c123db09d70ea2fc66f3d768d01491e946bfa93c1870adde7e3f087c70b48f0e0a4b756f8bd9a9f39be83affa5de57a32a73c5d1348ecd0e46ca8baa527999ceeeba0187bf0594fde6fe94e13e3cb27d4e111eab06433c820a1c3ec56d0f9ec65af10c22f48e6e01e7b4d25985a73d05d9283e4f9a473cca52bebc548e2f0bb5cbb97987ada6c190e3d77b97bb7f7dc0caaa6b3217943cf778f1793ef0e195c6e50f7aaa1ff53ccba5cdb007bc9670bddddb7dbec61a30c1b3f545a184888393d62ec7c28c49ca9f22161c32ab9b83d572cf52e630d1a2f0292352cf85e9d4fd8acde3fa118fd55583c9f1e2f9f21d143a2864d03091ae0d2d5cd293bf766bb75869d6fd5eab9da92ddee9a1849ccc072a9b5d85a4f5df34d246558eef7584339d372eb960c29ad4dceaaff41e88bc720135bde6bdb7a6e9f849a2511834ae62b216b6fceb92d6962840149181abe526a2e9b2939b81c0918b4274c8b25ab6dd79e6a62d441f2050697b25debabe0927079619d84ab5677f3d589bb074917b43df7e6adae909b4a122e24b6ef3bf1ae36db7b690b53a9fbfa6eaeaf49b12f86a8bc0b87d520d102eff62ec6eaced9b50c755e8d5f610149165eab5287ea3e35960eb1a08db9874f49e8cb25c5245778beaf7c59314faaadb3022f57fba64e7d6e77b526c60ca7b953468c1c0eeb60024915d8d2e4ab49f8d85a9fed1d974742852617dba9703595a4eed4c44832857e4f9b5cfc2d2df95e3531663a653cc6062452e8e51a32bf73bfa22346154c1348a2c0ae5e37ed7db9ea0d6a627c8e6f321d0fc77f74c4881154c4e3d1a48038850b1bca630b154902855f7335d5d6bbf9d292eb096f77d97fad752ced2b151f244e687eec2d37596312aaf5b409fe7c9983ca1f4ed654a93241613776d630b1e6d6722f4896d0dd7ed373fa4d719a50098c75927071823ba7f4e6628b1e2449d0d5d26d6a2a95154324b8e4d48d96ae35ed5e90e911dc99d5dbb573934a698de0503ae726177a6bc9a916c1a95b373ab1563967b3a7d2adac20052c202142532b79a996d27bdcd21cc24b50c9265d32a8ce0c85909a5b377afacae78eb9996482b3e3d9fc8831a5802408d03f2dc74e3205753910d872ebc6aad4ef6dba7d92e1531689c60fe062385d652f9b904de683b51ad46e6e397debb90a8b4423c69d243d788dd3ece50bbe6ea84be5f3fe1123c6334878a0ccd7fc552fd70e9c7be9c60a21d181ec9654aa255f39756ce6003226bff12eb94d3ea84870c0f82d73a9cd65723566243748b3f75df2c95e17b644620387db0c55fe844db59c243550d9f259bda99fcf956b0d4868b0d8aa1b102ec3a5899d7bf13e0d1a9859bcf6dc7b6ecedf5bef4a13e34a46816064017f366bd59cb3d92f5bd5079858a86c269dce7e6eb9f283059c70ddc3e4782df93ebd42beba6d726e820c4ad6342b2d9ecf8e185b3c1ff781718583fc8a2df86e539a2cb562aaa4d6b1e3245d3ab533c0b042b66e6fb6d9da29c8bc470e30ab78add8ec36d936f892b9a454d1bea162e6977aa5648cc1012615abdae467de8fa9d7d8d3c4b8528218836050f1de9a9a9e6a6aea29753b1e738a7706df33a85c7b70faaf668a7749c17f4bcea55c5a0eb1dc705a5cfc3dae0930a5604eec3f4128576aaf15299acb4d4aea92aa0ae76a62ec3c27235f8d8ce3c08c42bfe5749ae052cbe95b93e1705c7e7cc498fc619861030c1b211716961ff3c9980446144cf53198da3fe7143bc6c516a1686bae6e4a155bcfe04a6d41c1546763cb67f3ae55fc5c84603ee1a673d58a5ffd5c0927c7e3e932309ef06d6ff8ebbabbadfbe785bcf0191e2343621401a613bdb4fb417fc79292e9710226973c715adadc796a9b78523ae9af35cf953a77e5c7e78b7c982583e9014613ebda9363a952d2f53e9e964c0bb3ecc064a23599feb9eaf64afae73f64c448020c261e365c6b7152a5dacad6ac7cc7c3d111e3773c1c89c3460fcc25263f099553eff1eaf96ea5c70a0e9516602cb15eef6abb9cc276df26104c2564e2d59abbb4767bed91efc4b812e3182aa10da745c827c4020c25d6ae76525f92ceb5b927d1a46c73ce55d7ef498792785e5df532bd67b9988b44af365d3dc98da94ad5209160aba41ef3c7fa945c8f9070addb67ad92c1d6c9118a49c77465729adcf220292218928233308d6877df744e3667af299711ae4d1b32d656ce26153bf23e3e6fc41386c22c623f2565e98f2db6560301a388d7ef0caa36f76cb29289f8b750fd924e2af3d632473c46423d308898cb97f267ac4a3d7b4b13638c190ee78dc4b86a940073085fd6da995abcafe1274378b7c2f7f857399842b0a770d35be8925b8d6d0f308450ad20f4b66d59277f3b043388e494cdf5566a5ead1a042388f9a0ae55e993e4d5328d81094452aaadc365f3d7b16517ce6c1840a4a5f6bbbf9bd2b6d2d22ccc1f9aa5d7aabc4d06d7748cd34cc413c61321e209a3c5132446221e8ecac3f8c1c9349d5aefef17bbd9402a8ce9034c33a99c50353ff5dc0361f8a070e57a5ee709d56ad6983df084dfbf53579b53613b82d1837b0fb663eb395feeb679f84fac14eb62ea57555f3078586bdff26b756c7973abc1dc61eeb37fe57dcc566a0c038c1dda659a8cc9b69e4c4d360e3075505dbdd09f5cec4bc13f82a103e4b4fe947bd5a54931508c433073988e597bbfc9d66373ad1f122307c66037a5d69b9fbe4e0c0d307150ebe05a4caa35b9f9aac6317048fee0735d8da5d7da6cabb9bce17193eae782ac6afd6bcfecfc8801c60d0abe061fb3f35d925f9301a60dce256475c6967f39c79a18635cc1d11836305fc89f9abfdab76f0d0dd99c6c7d5a4d552f03bdf01820178c1a563b359743ff6d658fcd505901260d69a55a757638536b4dd190e4620ace34db35745ddff232e425e48239034fc7ad4da9da33fc5566780ba75b0adbc2d6b4189031d6aa11e3b3f067627a8029c3bb05a5e46e53beb4a1cc0a86a440c507860c8ba72e4cd0ede495528de15fb6c6186cb8ba1faa1876b9b7a6b6ecf952590a4373303d86adfff5aa7a302c0593d356fde4932c4d4d8c3188c17f5c401e8e5c638d18df5d38ff69f9826aec1aaaee9d3df79917d8e1afe76dedb1bba69a1855305d589b18b25ce5f39d6a6b860d3054629401860b93d7af67493687ddbf31c06ca1d59f3a27d5594e55416cf8501902460b4efe27c6bee7eefcc71ce21d58c06441616307ffa17afdd2312460b000df54ecaa57b9941e5ea1f164961aefbe5cc97a458509182bf4337399da648ebde75a157c5535a5aced6253bed7c4d861ce188ba182f29baad327e6d3212f9522982974626ecdd27f4367ca7560a4007b395cb33df89f74362460a2d02afd642fd9540e997250706e55ef544f49f8e44bff7103cc13d253eddabb833331a70aa3038c13a4ebc6143eb9cc7a629a00155b9e16fe339652934a0b304c689341f92ca1bea95cd9fc1d32c697900ecc12761bff73ca9ae3d77f221825a44ddd64ef4aaad5d24d8252134a574cbe7333b937468c9a10b7c4f8124a182424a74fdd83caa5a9ecb50c260f9823b42a26f51f4b4ffa4eae34967c805c30464872b1aa5b275daaf9ba084cb683afcaaadeba6c0886086ce9b3dff6bed6ed94426c9861030c951606d2c22c0f3304a6fb9c74af4c39e9fc3b3186382424468911826a4ddebc98933ff5d6c4f8031384768d575a734dc6d653100304d59a64c92974f556aed5c4e899185778645e421b34544a80f981fb7fa74af9ea26e7835f3cd753d55c33bfd21589e9c1940a197b93955d3b734d8c1fe3bb27488c31b6430c0f9eeae41fa3706d6e36de24fb41d95014abca57a765b8547aed11e96c5e769884a2997aff5663a7f22907353730018554fac9b909173b255d73d8c0a1126367c31af989a594748b994dc836ada562e28996939932e58b5bab529a18bdf342dca413ad2f3166cd4d31b99e3e1e8decf07f4860c2095fb87c996bdced716f42dd4b703275de1a4ef934e1cd89a5ece61c5c5e9f89f565bcfc6ea5836c35cd0e134cf074ed958b69f76f9a2697604d1b3bc97c26dfb4890c134b3c377536c554cbfef4d6874925166cf046ebbe56b79cdbc5a94d28e1d4ff4afd6f5b7bee5de885984c82d784cd96a5934a39bdc63f62ec309104b7066792adcd0d134c414c22d18a7b7b7d1bf2fbdc1d269070db9274c57cc1f61a04f2c2e4116f3194ae5bfb3793825313e3e2c0c411aeee3d25fb3de9d41f6a6274c1c7b8e2021e63c4d8c222cd306984db6d50a6d69fca4d4d8c78acb1b5d3bd97adcdd44c16d16ebe967edfae4ede07c4441153c9de4ed0fd2ec90f13f1e07b72364e69dd7cdd10f1de3b7775cd3915746ec8172687504b39dff932a94a6d394d8c1b151689c60f134398148235e7aad84d351532bf092156314eaff4f53567980cc27127b99a2575424c04c1dcf4799a6afe6afe4e029340385bfe891b4b8672bb57fbc204103fe19ccae7cfd4d6faf4875d2a29d6ce3d6650be663e9e8c11fe64424fc4c40f909d39a926956acb96a7a50c933ee84b093aa74e13ea6bcb62c207d5c6cd15af74a8dab63db8c4bcca2fa792a97dd3037bfd5e50b69f6c425e267950edd58dd95be7fc70e2e139b6aa29d91672e375877f3029d9eede3f9bbe1d649372fd5cce6cb99eb20eda782a832f973e7edb74e0b49662dbce3b95399c03b74badf9a6c75a72e7e4e0f6b525e59cf0675a6dc5a1b1fa4ebc9cf436a12738bc77f219db769792e27d83636be206985882ccf5b76b43394ddaf0ba1bd46736e1d2c9920d0bc2e9f337bd9a6c2a9335b07bd7d45a4f767bad654cd4f0cdb972aaedebb676c234b0326f9d1273c552da4783fe73f6e7269b26462e6cf88f2d9c6072863567eac76a49d58a1d3483eaa514ff928be77cee32aca7ec7659754f4f4f01c1840c9297522d59b9b97a6ebec9189aa92735534bdb94746b2286a7cf165407dfdc9e92352ea08c4918ba17fc05a1db872bbdff610286b9af3d3fabedc4edde13ca68402e2d61987c81a9e6b3b596be4db51233f1027c0d654a4db125e55ced9dcdcb0eabe11ce38a8e229243423e1913987441dd635e6c1533f692ad26c6344cb8a0eb2de7b72e4939d96f614275e7af935b139caf89310c132df85ba9f17c2fb9a6724d8d49161cd3d91e6b6d39ec87c9040b0e3e4c69f12bd654a7da5859b1b1f25cb6b0b1f25c3e3b62617205d798fe4ac92a31365f32b102930ebeb576e7bf572a995461d7ced5a654f66a99950a4dfe54af7ed52b7f4bfd309902abe9529b49269602943f1b74fd268490d9d2ac7c3c217e814914be616396e073dfeff44ca0a05c21cb965e3f053bb9ce863f9e3179829470219b922dd7b049e884e9e0c3c9d4724c597ba789d103ea7874bc983461d7cbf652e2c7ad94a9091364b3267fd0b172d5ee352b65982c81b99964d81cf232574f235760a204f6784de59c7beac136c32409fd7eae756d7e2dd73f132430b7b7986afd3d42fab71eee374b4f95731323f854add7bda7b46fe5dae0a1c2625284a95ef3768bf183d075a97866670d1322ec3ff69e6349cdb6e06f58362b406ce8b0314405e41d174f0eff239b1e317e3ec3228f083119823b08595255acbd72652204b70b2e57869339f4a53b3009427af97cb7f5f49eef12174a860d35c1169c16975006062640989fd6ddb2af9b4971ff80a5261393ab1c848ba99af8805b2ac6d493bad0a9f297d032e981a6c52674966c77b55e334c78f09ecfb92e5b6bff9093c90e7cd9bae6e072b7ec206b31d1c1eb345bd373b5ddd4539d4db3786432c9c162ac2d7bf796716bb834311ed9cc678283fea52677f7be5792491e263780c9bdb135f549971a261b784b2efb41f8d67dbf167201f29bcf9021263570e98f57ba43d84dbea789f1e3c97c67c3e96c625c11c38406f3a9c993dba92f566f352d99593cdd6750e16b6a7972b04416ee75eb4feb57697694c462a54cbd74bde96a29960ffd8871e5853f1f1c38545478c4a822df0c0e875984c4a862a404169ca0b37313b677e75cf97b425ff28ae9fef01f530ab99d95182aae909f145caae9957db71fa2127a211a10fb8025ad988df93d742d9d36f6c98ad6f6937752aeac494e4d8c2da0ce8647c92a5c726e8c9de2f7d6d34896b144154d993b259b4ad5c418e2cc4a492a1a536eeeeebfba316e54b4d9dfd8d437e15b394a4ec1dcbf32a65cd75b9d821253a85a10a67756ebe92a2b8582da2bbdb3247fd98a8d2e9490825fab5e6e7da576ea5b8c2095314a46c14efde47dcb5cee6a86841251f03fd8535572eb99c7b8f232379f37a38c9250b4b6cbc5cbbdfbf9ea12507c6be2f4df7c7b954a9f80eea91bcb925b4a327dffb56b4b3cc1d3f56b8ee563b02d4f4d8c669474422dd5e07acd164c0e27e444afb45a29b7f64b19849a18f9133af29a924d2875e8d69c4aaac79c743f5442ff852cd104dc75ec8e5bb71194644232e9bf78b9b4e02e6b2bc1c46237d35b5dddd479b3924b2ca7fb7829fda4ad2b35311e794d89259e75f9a5c6e6379d2e6962f4945442e57cdb64f3fc65a75c13a3094a28211bb35cfbdcaaf53b46c924563e742e29b69eb37f2689d76d6aab92f03597e4cb9248f8ecd5e0f4b752d2fd2652028987c96743675f3fdf27969247f4c20455a7279b9b4b4123943802aed65899e3091b2159a92ff5ed69fe72c5085e76eff5d36f2d2d7d8b90f3b9a4bc98ffd4e5b3855b18880a4a14f1cf76f73df8e67370be4a114a129194724e39c1d5dae2264b10d190f7a76a4b1def4f2f39446ad031974d17f34e89211a634eca94be367d991d79efd87095269414a299f3c3a458257fd7241a258448cf2777bfe652fa2b1ec44aeb35f9d0ff137c3071b1450a7c940862ea532e5f595f5b53d7c32909c4af6c0bd5946cf95dcabc12403853ba1e4c6eb2abfa1f20dcc92ee16a2b534bca0fcc98f2e79a42f90e26ed8392cebba77bcb565a2ef928e183ef2f9ebd5c7aef9cae3d38b79ce13f9838c957b2440f4ea5c2e9d682ba1264b3240faa95bec94a4228e15bfd82123cc0e5a95c7339e1b7552bdf0c2094dce1217bbcd27c0b726ad57678a76daea57cb66e7a85d141491dde33de6e6fae63b89e3b9b32543ca1ce0f2f4ae8c0164af65455ae620bb9640eefaa70b2b24fb5ea4939404e4b533f65b5e46fc7613da8aadfd69afa0fb294c0e1c1345b6349e76bfc98b694bc61a9edc79673cf746a732eb6b0a182c941891bd46aa96aa5c53ab9d4c6c3c60f9550c74523a448491b363d2ba666d2a7dde6beb42e256c60f07bdfc24fdd2e75aea139b5a4fcb174b545891ad284ce92fefb2aabe21694a4a1e5bfb4ca9282b2798f1234bc5f8db9aa656d2c695341c9197ca97790b1cfd70fba45a3c40c497d2639a1eaf2f65a65505d1036847397901232b0e64bbd345d3526dd1719256358770d5d4b73b919f70f2911c3cfc46d972fa598adda0c88332fb905f4f24542615095493e96124f85c93fe402448c1230a4b9aafeb2797aab774d8c2b2aef9e202a28f982522eb95b2fad722b5bd2c80e7fda0bcd3629d636795b6d9f349a922ef0924d7d3f5baa0c2a6b5a3c9f122eb8c470e9da86cd0edd6a625c79b9e108d902439f4bba3419644eab34314e7e4e8c2b3e546489165462bccba55227d5934e13232804628e0a4ab2b0d0d95b394f06a1dab0b08481a3040bfb9ad49b09b227e76e5b3a66945c0126d5b7d8316def255f9a18399ce11857e49b11248d122b40c79c2fe738ed324becdd13e44baae024a7a6dabaf51a5366ef9e202554d8fef496bffae4d2db19a30b7e878e9229a8f3d5349bf53d3515865c80c0a0440a2bb5fffe93ccbdda4a2551784c9bb5dca5dcfad78682329e132e3ba66f9f73c9139ef972cec15d8aade598c68c1227f47450727250265fbbec3f4a9af098be564db25c4fee7109133cb1bbf5f978b532e896a0040c942441c8054a90d02bf1bafcb4bcd34c4913e3f4643420f6b14a8eb069c1718618182831429249b526e7bef6ddbc4a8af0ada59974d5a9ce5f2582775a69b165ced44d6599d01309fd17b364089f1cb2f595fc0e5b2995e7341791afd131851221402597a69da9b5264621254100c20ffcdfb5345b3fe89cdca989b1c4079325c758da09d33ee659d203e54dbdd4de521743090fa074b339b9ff3ec9a98b4b76e0e4ab941c7bf5e4538d3d253a00a594e44032fffeb6ce9b9b4969090ef49f5b4e299996bf53a68931c6151394dce0b1326c4d5bf93e98541363118f47c3f18d0f4a6ca0ffa69b4a5d2d55ceb025c69597500b0b8f8e1836bcb301e3e5f32e26b061a30525359840090d949c3bdd52b29f6baf0f44320b4e4ba9e98cb5753c9b8d6f38ec051259f054faa6b636df9bb5a689f10624b1504bbdef730725536aa515fe848ef0b06183c3611d3654ca88110305125890bc42cd999aee4e663f7f2a3214892b5ab198dfedcf275f5d3ad3c448c28a0690ac82dd4b9d2dfbc9b7eb5cb322860e1b200e07870d336c848e7ca8e3d161e3d5d8d4804415fb3c257d9f2c57737a9a184940928aa6a9adf7aa72674939092a1e6a5cb8eda269179b34a56c91a23714bbb11538c1bc74170588422017324c304f845d887498130012d0e7a58b7b3e2d020012b5b0c836416945e841ac78353c2c2ccf02009455bcb448966741415185ca640280920ab509713420ce640480820ab5097d34204e2623009453706325196c8eddfb2917088a29b8d93e6bedb9df1f27cd17e1213182c24029c553b8eff519fc9518dc8c18a498e9579ab0f725c76fe9286693df58a9263fadc44a14c949b59a42a714fe32170a35d76c133a3fd89e540b144dce85df3a1fc3c92b3ff1e939b78ae76b2c392f4fc097da26e85eeddcb5be130b3ed45e754cfe5b2c714297d9faa9e0eab657de84da6f0535419ebd1a639a504f296eacf12f6eddcf842feba5ba789dfa3f63c2dbaaa7ad15f357cca54bb0d7a48249e5a6a98aad06c512709737e97213833a57aa84d3e4524a505b62c8f629b1a95f6ba558fb24526be75c72c7eff5845212cc0d7a7a8a41e6a0ff8b44eb6fd627593d4848a94eaee92e5d6beaf688c476f9674b72bdbdb91cc1fdaea64f995423e44af786f355ad5ad766a030c27bf683ad6df37d2dd317288bf8e5a6d6b9dd54a689c14051844a10ae94ad74cec99e27e23905e17b72a9ca04792162bef4e483cb9f7298563a84520b4ea789e99b3e1f33446bd2a9f54f4a57fd7c1642766a8d7f394e50f162427837db96ef9f1a264e8370cc31e8d237e8d6b247114493fc6e55fbbf9b2e3d9440e892ad4c3e5669654f06847c73e9aed4857f68eab9e96be17cfd6f991f1c3ab9647aaf5c2e65fb03a50f6abdc9d6cf6e727943c987b54ff152cc29aff9aff7c0ee56b973effeb83947d10343876c4a9514ca5e9662f402250ff2ed92bf4daea7507d62ce0850f0b0702ec59cbd394d4e7d51ee90927cf773aec54f7a6b767829599b5c704162a0d441cac94ae1936b39a81443a1036cc75aa5c46687e64603caa0ccc1b1a7cad6d7af279b7b463c3a629c287248aa966b49a65733b173287170d2df67af520837a54581c3d4e5e44aa6ebd4432551de00953fd7d2f324e15ad30d492e63cd35b65c2696300c9436246f69e12a08d54d095307850dce4165cfdb5bfbcbffade1c1d9b8b56763271fa3a84136e65232a6509f42222869980dba7a4acd94bea74b1434bc2475cad40bbd49b75c07e50c9d7e31d896ca5532f1ca7ccbcb901c2a3388467ef8411837a098012ad8a63f5d69aefa2ec35cf86eb27bc8cf1923837c70b6a70e26568c558fc131b83c5be2c5dce2b46268d35db59accabd62b158605593967b375ab2519fc0fb7a4810206a69eb36ccc5baf06dbd37c5280f2056fadeee562eb586305353172f843a04c118e4ba300c50b89d5f23a5567069d883f91cfb310e96c72384a17dc196cf916be427d6c6a6254a348c60c3080907801850bbacbab4d28a5dc750735285b600a273ba86baa5ad9a644d102e34f4bfa7be6285980fb2b255fbc982646100b7fb845080e142cfcfc9f8a3d98f413db47b9024ccdeb93520b97aa5b8d15bcdde56cd95e3535330d4a15dab57e6a8db7d9eaf6c440a182842e673b66e94c4da9384250a6a09493b2dd757e4bdd93c2e46e6faa666f2954f281a2d0f6dbf14bb6ca5e4287423bd84ef539b8ea2d7d423763cb579bba533de63a509c00d964d8ef9c7b0ba1a62f509ab03df535d98bb1f5664b4c902fa5d6544a69ade90d97b0debfd6b37bfc74ed54422b6ec834ed27f5e75a12d4540a2183abda3fa6bc0305099fbabd2a6b9adedfef113cf95cea395790fb41453102c39dce4ef957a63715175ba8284a11b8296b3b39a957d8e62302744bad7bac5ff2959e030da19f9a2d35d7c4522f9642e84eafc1c4e4eaf9b4a104c1f542f7bdbf332d5557060a10a4f7375b0bf71d9b50a1fcc07975b1c7ce57e14b89e203d91c4c337d62a8ce54f580bd74fdeca1ff83ebc113a0f0c0357409ae6bbff5246b64a0ec80db6b29ad9a9a5a2e569e50e7870e1c9b32a56df61c48d8dacd6ed656a5d75093d9cc39e4478f1e2c2036f2d231668ca0e0a0dd767ae72a2937339703e506eef9b34dce2d9e537da2d860eebfc9bc925bb39553941accf9a052c93b95746e8b42837d955231e5607a37ddd4c49889310c1c39c408e38b18fdbfc52341394e66c112cba65e49a9dff3b12cda3de59ce325e17ad0cd9358b8c397562d29d77b5a6dc1092c2483ef25297dad5ecee9e4157025d9694a7dde0fd39eb822a5d96fb9f996f9b17e1d25386945f2a64ab22a73ccdb72561ce064152f555a2ddd7450aea4709ea8626eeb245bb6675eec899ca4422eee991a2f05f777424d8c6a8071820ad814cad678f1a7967e59361a901a6bace1e3e414cab9c7bcdf4de55a621f7201e24e4ce1925a4dfd7a0917f27a9a183b3df4a414fee43276cd16fbc3e9a458707d3a7fbbfb2bf3289c4ff9d4bf753d5f4aed4414eb9aea3eb76eea52705d4e42e11032cbc44b290c4e40f1942db9de6a33c67ee7c927dabb37b854b5f6c838f1445bdf1e3e95763536a79e74e231fbe9bcee9413bdfbcadb4b5787eabb09f7563ed99e41d6d26c4b13cee04b6eaac63a1389359e9a94af5bdded30d1ea9957295e8ba5e7e4259e777ae933b15dce0b5aa213f3c66b429695804fb59da17c9468efdcf926d9dfbca99e845a6b4f242177baf5dc635dcbdbbd934848c64e356937b92d17ee4262bde5cdd96cc6e6548bd9e0e4116bae75f716640a75d7114eae8509b6b657df0c9e71d2886fed9b5bc95b72b3b13c678b13464ca9ea49b67465f393ade99345b8afb9543db9103ae7d689229e9c2eb9c2c7ecb9f53b4904d4e678b6aefae670f90411ef957bee7d5f63a6520fa15eb1bba9feb5d5e532f4e1315edc93e3e5c410abade19c3d7f2e36590bb1ee75976a7777c974ce1342a4a5a0beb95a95ba5f3608b9de0c3ea8cb25a594b99c08629552d5c5cc5b3fc81c88b756b7d5d649faa6b69a1340a86fbd7057ee7a6f7193c1c91ffaad9dea9827f50fb5854efcc03cadb2d458a50999ad3e34995e2d766e4e79c287f6bebd606b77c3c46ac7c91e78c96549216b0ff67ffb0b39d1c37bf5a4b24dfbed9ce5b69ce481bd87739553a88badab1e277880afb1c4d83f97eddc52179cdca1956c2edf41d689a9f52776504fe792eee1943b1deb933a249f2c556afde69b4eb917277458ce264b4e6aba25994e4d8c7c3287754cbab92cb55f90b5656324732287e549a5723dd5f4d68919f9ce9fc461aa4308613b93caea89052770f096a6cf9fcd2da5dcb99337347ddc92335606e57c5005276e58d3e173aad3a7cb491b94b6f94c5b826c504dbf3e13379f0dced7b09335b8b69458793ffca9aa6ad086cdada5eedf355c5013631ade7c53994c6eae5abb7c8c1334bc6350aae720a757dbf8431d26123a398363afbadeab36b19e0bc889199edc76ec6a5542674a65ca905aa9b5bd60d3397da91f2a9d0f754ec8f0968253595328156c5f276358b834296f4fcd666f77c18918a442a7be95b34efd260c83ab739f89df6b6ccd76203038a5cee1fc9d2b77d51ff40526174fb57ee7744ab2f49c782149e6e042e66f49a76a764ebae06b4ae7a6eae7d6cc05b7b43d3974d8e4b74b5b78d3e5fe466324e3099d68e11f4ac91a5370a74cfe4eb2d04ee1a6f50ee7f2e5ef18e116d00916a073b5de4cfdd4c91594b2769f5c4befe17cb682dc6e76d93cc125995a55e0097b657a4e1364c593e5840a3f5fb55ade8abd7f29a7901a5b6b3ed72ed5ead771f14821b9f66d31d6e65b69b56962cc60a2701205f786eb39d77ebaf3ba26c6222750f0c7eb3d872aada758ce969327787353d01f9bad3565eec4096a95576eafc4f45de326ac7ccade42c80cf6f2036d4e98305933f898f2dd061d36d0129c7d616b70d564fc2468c3f9d0466324137a224ad877b572bf395e8d2de6719204864ed7b69418ec954a9e20614df76b41d5e58e00dd635533412657a5572246902e5b7aeedd63d94d6a11963377289fdbe452bf3b21822bedb94aeabef4653e19425bb22997ad252ec18910bae77aad5bdb77f8e623721284e91a4a6deca65b9db3e500424b8d39bb07593373d3e3f9fcc90fd65466a88d4185ea2df3ce084e7cc09cbba4d4b1e41e6cc73dd84daba0aaeaf5e69a4e78009535b8a4aa36abcb79b283b9e633c9491f9b3b25b7e044078c5d8213aa564cbff1cb41abb78350b59554d7aec1090e52ef93afa5b3d7569a930627375049a5e75cbbb5ebe5fdc5890d54cddf9d4ab2e7b58cf138a9c1926ae9b67aee9eb576384e68f0cff96a572b93ba63ad89714563328bdfa593f9daf950d79b9a18631c81892ca49cf05d9f724aa6f63a16bce6f25b6f9dbe3575bd9bc0425b63cacf4aa9f4de539997fe8f2724a4e3d980402da020367ca8848ef08081c92b5ed792be9a63cdc1d7d626ae704ecfd4639dacc9c4b82224c81a26ad70a6ad94838b1dbffffa3061c54fb66f557b9edeafff55c8ff5d6dcd97bb4ac90dc34415cce684ae50e5b76489a542bdc650eaaec2d6da739a18a1092a3e39e6d2e3f7948685bf6324c3e414afedabe9ce3da589f1e51302bdece050f97c8c1925989862db2ba6e93506bbfdad492918538fb5c61af3df66d6c4e8384c48b18b4da6e4aef489936a4d8c992fc2a11841018fa2a844108623792410898281200642103a346604004311002030282412880412c18856ed1c1400025560546c4228281847439238200c06e3288882188661188622290ca2304529a70572ce17aa34df4519b01c7adfb7a2546be4a3f3edbc7e5df3f2a5cb5c3f82625af1a308da8ef45ac6e2ca9281651e422b142b0af118ffe4fe7dce3eff7758cd82664f96d21aedc3b7bd1a40c7d563f4b8c66ad0d709b6cdf920c3418c3db5712464af9c3a446db762b4ef9ae2649f5043bbe3b2e48ea356d0e93b19e20cf1b314592739d84d68bdf9a4e07da45aa4ddb8bba8843dfa6cc350aeccfdf444421fb812ee2287a847cc2c42237aab22c3ae98a332466e88236025aeceab87ed7d51205fa935693d03f85380d9b4b3ce38af92271298ab3ad2638e6a0c52f39f1fe77ae7772f384f4a694e838baa0fa71923de196ff15e5f4ed185847fe982544d99efc9b0548b3d5111ca71a546fdd0c1af0500053dcfd5a6c88e28e375ea961f44c2478065c30757cb50853b41d5453af7f38f14b8ac24a937da2f0569a62d3eb0af5ce8acdbce017b1774040ebf3bb66ca0c6a423ab648acae421b2872ffe1f3d6158d2b544076fe58e6242aa1e02ddf49b7a637a77601261eaa95373c43fc2e39ddc2eacd3f66446bc35bf0c6ce50df4932c7bdc0dd5b0754f66894c89755d4399793072c70c4042f44591636155e4922ded81a5698584ee496a54c37ab280bdb3cd8640642f6045dad5102cfb4ae6ddb9390cec444c2ed2e81c011f9d2d556c9eddc81b66d2bb3973aafc55dd79b9c56557c6f6792ef0a74482922dded87ff2905c5b4ed6cd3103894808e49f34e2c4d1f9d6730792534f0ce7d7fadc58982005eb8f80c01635c1986b983f03ad3b43c407d83e2a9d837f4d486f3b5f5c49ec17e5096715031b4ce374ae1d607df1a17d931e0b27965d2502d5d7a401db378d8705c8a813e20bdd078dce4cf4ff12e782a15686e7703399257342331d1d3c98c89576418dd0b88e0a5af0af9336dfab0caf7691d9f6fdba97833008d831baf3332694a9c6703876acd8638897d37563ac2f3e5c49f65b1354e29bc65fd31e199fc88c196d59a8c79545767810579a22f2e1e002bfe2c45eac9472fb1533ce79dbad6d39f44bcef35969e80742d34ecd995e0a7e34c00c87679431633b4363bf33d1207089754b495f55a1c0df1d574184a1d3ee9a99e425c4b9be4892bd334c11b668e0c1889db4108e3f195a1427a26f0f1e0f9411a7ab343ee214c0d21bdc001a6ae6a8cb4afb3d686c7575804c1c94990b7a6f09f74075b765d4ad92b50f7c4a9521f3c0bd986247c1649379dae0f16822d8bda98c42418fe714ff3a2cfafd2fe734b76f8922ec603493a8e08860963359e4644486cbec2c4d743292d60b14f97b652aa4a6ac6b69d771cc4a7b1c9c55f4e3cdd45b79bfc7dee5a00a34e7e5b0d92e7410dc8127586c0f8ad7f9dd53fbded2cbc434aa1c91cd50dee6661bb8782c97138ad6faf1fab9c73e63def36c0d7844095eddce8670aadd46bf45eab750b0e299c22f241b0f7f5c7f9bf90a3de24d4c3ff6ae6b732811b752c5b5d16356aa35edb4c404be84428b63d5644db51f50965ab573ab97b79c9a052017857df658d5af82df9757f0d14c5089af0930febb69dc9eb2eca574dff70f6a46bfee746451a2b6890210c2365f969cceb884b265cb130f4b5480d56790326d5aaad228c38db99221f1e359288b7809c4621d315a482e3ffdd7e284c0328a6d965970972825887ce6d3d0984323753b3a72fa8f8e248cd016135002c0abdfbc153e1bad27f9025149573b14fd6f75e9886e08c58679a1bb45a5da05f4ddb9be7a512f04a7307be432c6fdd2d9cf17164cc392a80db8fcdfd4f551892a0d5e97e0d429d8e020de8fa2b52df40f480a6ed52381d9424b5e91c645092735f4611e42340555b2b7c227b5b35da1206b881e2f5ab0ca69aa7cac213713de4a9c46a1a7e3e060ae613a7b9940faba873dfb07d438609e918c17204d673224fc2f5a9f8671dc1da5b96d58ef4af83e54b0b0e9394c9b2d645ed9fc6032cb92116686052ccab9cf8b694b6414063948b8cc30cfdb03ed62137792afb881640e9aa3d4422224973ce345216307c9bd51a2f2958b6da5d27f99e47eef114a105aaa5395a176cb6d1c092ab6bcf53a7164f6f85249cbb508afb9dcadc1c34a8299c1d04b1dc52da2ed3aeac41460cb23fde5ab4fedad4fb351c14f0e0959312cd4eca21bb4bb706e0c781854bdb804b014b405b337df436c25709112ac9109c11b3a451bcab6bc756af8cd5ef7c4577ba2be02161f69cc2fa0a83537250803d39a98a6cdcf238409cb54f86cdfe6110cd34c213fe254d149b45a650f436b1d8205bd18bd132ceb9755d0a37789e403f91c7420f59a0dcb78ab7ec39cd7ca01b63160a92781619c713d67d5adf91ebca97c87135e3bf535615077a535ed50365dee072bd2a470c6711252ba0da66433fdf56cd3472a86a46449db80c0aa5b8fac194f1877d0b0a949cb962b830c853feb2293a04ca7f7762e743469f8e62ce300869d2b29c21541c8b3e500094a026979cc12ac26ba50a6b212a2d22cef4ffaa735008bb4916c2d5a72100108df57889e377b5c8a5b7d88a9996f989063120d3e3dca0fcdc73947a78fd4e738b56771c895eb4599438fe60f5f536968f9e31d9f61a99c1ec2568ca6c6b525b9a22b36e56421e7c43af72e14c93e8f602c8468dfec08f92170225ee7057ba84259d6bb38ef84c23779ce8b00bac43985e2a1f10803b9764f066da14abf6549830e6322323a11356553d5f1c2a17a08eb2c1526751ce5ea3cb97f308dc8e0525226bc9512846a799a743faef154e2a42c575a0673981f24c81069d77f1cd6c0b4d9f800e670bad9ee8db3ac6938d3a40b24d5c5633417de31a35efbb203a8f6bea99837235b4ccbe3bc578e6d605fceafd2a13f2802d0971e16c177fafc47e602e68caf3e2ac89e0537b20c7193e4863508fd7f55b9288700227432d43f12b6999443d7a2eb3cf26edde8b66dcb0bc56409c61f1441065ec13ff381fe48de811b136480fe4ca6e4235b1aada71396d102730f3c5e227daf1b183fa16609028cd27490727066c9c5cda5172aabd26c62e547c28a5b84d09d873c7607b95f0c83b8bb09e2414269e3baa9a18a67a35ec9f969d6ff55589b6729a01f5cea86d97a06d57e98556c78e0dedb1ec30be610337a6a35dd0c78a8a3f589bcd078076243f1618e320b815a39dc8b8aa9e2a028ba96f2aa3cd1321c21a26df4f156bcd080307975da62ddf323b1c6ef38f3857310a142783889a543ac016f0d5e67b59be56faad8950f4a96e811f9826faf696ec73b3efbc2e1d44e4be062f13f5fc4804b7c5d4e0f010bb98b577169a3a65c730d9cdb2b1c32151166d0d40bb07fed5f6489ed66ea2299a90b00b9c0a7e01e6054f0b71b384dc147771945bab1f3629bb6d6729a946bf39f5e30e5688733872a6d5fbe8cd99652bc3400b9630dad5c1272384b978269b9ac5b70275f6a4936d366893957bace15c56349f0afb689f23b8027e670b5353020fa2b19e405332b7dbee0892700adc1e9c699263e074ce6c128732f036c4196c408fae6cb07ac2d1449b6aa08c271cb168eb9ef6e6a75cb78209a55c214e72ea18dd3beb4e436594c12ffa76b3cfb39deeb8ef7c602f1e86034f88e0aa9a7d46449f45d41eb0dc4602050fd748109cac9641f56b2f1fe2db6cd7fe41ecd61fea3095eb7ec6b2cdb74bb6474fd85457a3b635dca6bde52d59c13ae210afe6aa57f0ee3200e564fdd0a336506e500429ca783533ff0888924d0a5689996b087f7a3dc49251f728a0abc53748402b640bde320b5fcbf536ef6801c53c4764e8338ae54d04fd13b0e67b8106aaeabcb86ac812ecf678da086169682411115e86741d7bebda4cf396b7f7bcb7f8f1814daabac88fa967f001e3ec331609e25f8dddb04ba1bc9548bf5a959b9f434231c0878664481dc9cf972d5fc8dceec45c251bfc95df6ea161baf3814f4944a45c7095c40355112e1752519c587f58482ea89ae834c669db1653789dd48929cb46d7894455671c94b669d31c47fce0d85d11a229b84a9a0197279e2201025ef9176b95a1fc9cc11507f59ca02c8752c272bf7041909b11611fcd8d2085f1f222c1f582ddb361966842f8b08921f122d651b4314ca4dd4b060c118d45ecb944fa391f80bf0872f4ba6735d5a83a685796d0b4f7dd4740689796db0837cc614827b2200d171705528c23e3021a55c901152048d51eba2f9bc72b7c445f0ac44c6c38cf25bbf69399d8450804beba8eb4a9d483e300e48a9036f8b3f9341d41448fe112c9341e622646ada5e1b0db2c2b63d4740c7db20a591458ee091a5d2d947554acb010e0a3cab7b808001123b4ebe1e5259b6240d57863f1b5f0bfc03aa83d0e492cc1eaf17aba0c6ed33e9fb936f53d10bad33880a66e4bd9fc65665a3ee20b3be7ef568388abeb1e3acfad81b4e1d5d06e18f5b8c30d392fec4813e31d9cccb1f02852800a5abf9dd9ef1056ba2c4bf6f941e225831234137c7015bdd30743278817b87a0eb0638587a34bb1c5ef832ea5f2901d89d71a076f282cb4ac9a855e30e1aa054f147c4b8e2d9b731f667aa873ef05946596f35a158475140d1f2218b9833b0cb1e8792f805e93feae199bfefd4224b2eab02b61587e6da13dfd42b2169b917d0a5e57a8701087abcce6a0868f281f633c0ce74d29092f8cf5215e10cf2e0997499940e28fd85e9ad1fc9e595bf6f765f646caaa685291656e594f742ae45004d9b6348ed91cab4def32dae565bf52ba4d282363844526f2150bbd3c2b4a690895fcd44ff5ad116084b2890d43acb2d7a11384d06a97cc48147484d478497f11d5500c78a680341f5ffb4ddf08f6cefccc5e686983e9d9cc2fd2e8c7c3e4aa8bd796739500dbf4cdf2c293d0859f42fed39ae0384fd506c0372bd3ea10a1d99d673dd3df1a2fb5ac03dafb37663bc3bff3fdd9ef446b9fa4d3aacb3f3d0417bbe36ad6b13fffe9d8245b38f7af6611adac30326034592038dec1e0d05dbe1077d4d44d8c6c0014a9fb9b78f31b6d82815285486d283dbcd3f00515ad73fa6f5be50b6aa9a0cd23b646c2a0d0cfe18306866e5db060e6634fe1f16c00dfcc19b99010be6e4697c10f1d594563d7e0f606dac264b45be437d637feb66388ff708b587afc25cd1e0072a2587b389902abc2468fc8c0700f058ee1e45d198fdc6de79b27f5db5a1856cf5e4bbf3400e010f811a7c6027e8002bf8e7548f96152c49a6c0e3dbd86d6c369e64299e3922a1c17183f4a16fc8c65e7ce14454dfcdd7145c623486aac4e9716057e78dc2d0e84bd7ab75d61fd441353ed323621c48df2dbbaf6ebc379711f64e001f445b56b4858cde9e230d00d3a19b86347f66c5ef385a812e394703c05bfad87890ffa1b02a6fc84f2b382e412a5bd0c6707171012ae0c9ed286c24b032f533828652bd98b7e894693f4e5ec1887485bb7a59498fce48467caf218798b5546faa7f9d4dca2f6d8b76c6d583c7a9aa49e0e3b146e29ec3641b3b9e97e81ee1b0666fabb17d42bbe166e0419f61ca7b78bc3f4a142c9e81fdda7693419adcf4384b0113f1cb0f60f4d70f9bf9ec5cdcaa26b6f85aa07a1ae24851c7c4870bec2b4bc498ea04195fa573882ca9daefe25d316c7b6904f0cbb1a7da57d430cd745f8552c1bac4f25affd0c6498f4208a3cdda45871471f1a03023be269ac423b263b0f616a36bc085a78cdbfe2fe1395ec96741fb03a0722c3d369afc1a01633f0d7498a85c92915cf3e40ec394a47ff353432f05f4660a95b80833b280ea1f5f0300f41b9aeafa0a65bda0cc15860c80868c96a4f7e14441cff6f6a5a00b900762360a722b6a41e4ad8ccc2c1d325d3629f54cb9fcdce715c835db41a14224fde99c6bd88f7edb9f2b052bdbd03f8136f0c30769b33228e0d76ac12b4b700305b976ce6a40f3689a1ac54582601ab69cda93bb9a95a8b4aba71d3764f5229941b8680551be0055d55876f2ae36fdbaf27f9b13c6a33d580518be1fde5fdc854ecab05bc05c10eb6dd76767a1aeef8d778c1490cd7674ae1d0da61c21dc6cbf7180658fd1f3db6d55bb484d0db6799e99365ad3e61675af9742f3f596065e6bc0f4687640adf3fbc78c2e1e42c268816d01fd8590e4625d68be2c798aaefc68d2ab34ec39df2bcb0ab0e52eeed2ab74728199efbe0d42a36fc3c598178d64851279f9374e2fcf2eb1cd54513b6aeb25f96c8950a390689acfb50f02ba1a8f0baa23acb770e7de1b8be13746aa1605e50f1fea3a67e6b31f73857a6f7a7ffb1ca8d12a5d70828d566938128909b27dd007845c8d3569436027eb44c5858c42212d0a6bcb20cafec1b0c8043afbb84c9247661aa33ccb2d6702736b10b0fb69796c9197bf7205582bfd6b91934e0b7d2e56a67c4ea9c43c30d4083e49e387a8ecf2caf07ef0733028d48676503090e102d08461a9a0e4ab233c5ff551bde6ecb2cd6de4c23f0153d985d5b922d17f6bd3f82984eaa5739e8881fe5e9903104a99bf795780a4c2e558b8c7e6c25fff1870b77adfbe72bdfa648c1cdaa0cafdaa95e206153f06d461c8ca88f4c393216e112ccd54ba7c45f031eb2acdacc6a7b9349596e29941505779c19042f0fd334ddd6a8ec00a434629fd48c8f91b17c85b2611b57980061250d3056b5102c310434d9a33c097b293d55c3070646731b3521a09fee4882c89939e6e27a7af08ed0306ad5c8e5d50b4fa7989088165452a9dd3f4ad8eb1e151f6d2c79fe18c8f6fb83d3ef7acb3e2393d08c22663f05360c573140a82d11ac324e65564a5f376440dd138bb201ed9410519e0e0ad25078c751c9d9d546cbab26c91b279a172611dc5e26e4fd32ab2bba9891d61da65a87bab6586678e38e2b0b03dc621330f08998a0934f0a8499026ada4ff7ce5cd6e19dd1e31334624184761d7b5e7783a834594ddd7e70cfef32236ae1ca9d4e484ec3061a02130fb84de88149569aab587f3bda442139b4dccdfac4f78084ba93230740de608bfdbb449c5569dfa877e9d4f2f8d7ac0c6201ee6848a582505f6d78923c88c41ad0154ff3c8d310c63d9a9a155fcae7a793076a7769c54f6724e7ff42dc6205bd476ae0d63b05c5a479f9369a51546540b42c21189db637d99a02882b5c19c8013a6c3b2f85853bd2c9a025391017f0e1f525707074f9a06e607e501459a307136166784d74928416c8ea9b1b62d8beefe1d81016fd4b17b305700231ac429da782011e080d091191e7c6200ea81c105ae2575a9b682017571afb64b9b1826a5fe256df33e834d006a08cf8b52354ba18e1457b23a271876d77d987bf03931d371e9c50f8f92bdf3c0792d63618a37676dbd9687e14e31d9264c324a56b92628b84f59fdc0c2c5ee1ab1db73cacd6419e30465d44e3a243b669cd803803b86fae6329e7e19ff916ea4cd338c3034433c9aba9f58a66e79b18eea0d801034ed90bf7e3bdf600be8aa5e8eadcc063000eaa7b20d9b84c9602b04e6ed4cbd0f8bbd5fd5bae48fce32f78c9ae393a0e7c3474430813ad810aa4341b5aa1768bbd88c27f96c4d2bb98ea35ff727593609811dbea5baf8fbf3940379b6636f3e81b09aed4d979c3624b7637a257e6d07f944fd1eb730955dccc027985a4eda25be282f84047ad234ef5ee2245590fdaafb50bc92c0ca84e3c9e20c905cafcaa8ac9026eba8c4d24e498296d7ab55a498ef2e10b0a4b0e42dfb8afc3bcb578f1c83a23bee878830898b2f865e4acb25dcf28a50945391205cb3c28081eec105914de4fba31c286c6fefad6908ff69108f9cb6f668e767ce0745bd116231d2c4cfbd667d66416b049f8ea9cdae2a123a04ef66161718b0588d299ac6cf960a191bdd261c870264dcb3279c9f540dfe50d2f586754de0d5fec929d61501caec7b3a9896c720995011f4a26320c60b4275b96c9d3b2f28ffcc5fdad5cbb90e1652884263b4131a76384427909f8b211e8d10171694fed5d9776601d5e442697cbad52f87ee79c710a421db0be3441ad23c59636434e670d6b5bd7a317a197207f36e352d4086b1ce38b852613d00d8843089a73ae82322a7214fe2eb9d29625a269c3481363cd2724df05154326807c4750a530612db45e7d0a33fb6755542d090a52bb261c580ba5b17fd8f2348c215edf06f4312a0181da597367a063a4ea0acf29544ec38f19256248e289a9f2ff34c27c4ed82c00f8789e3f7b6bd8e33f7c4acc6f399d8a7f5f4d77dfa8a5099f79d568357c84153bdbe8b5d3185494cc7aee5e45a44b63b06116cb18205a832e0272c34c44fd0583bbc3d05a7cbe2cb05626e1a9b607546b90c2dfb7a1dd09c3785a6df261bb76f867b1061ac3d313565081edd576fa1d7853152943603e8e681d5ff9bb6fb4893b93272fde6efe74056077f7fd622208b200cd41c6eccdff36b011fe08ca54d20a1a4e80661ce5a4c148d5dd5c137d658f6d373776ec21c83ad929f59a4df868c80be216c4cd5484e74cc74d02095689c965ad134b5e5da33f6c04e0077a8eca494b3bedecab0e03c75bbaf28a26d46e41f3f5e902f9e0a2feaa78e312a9325b45ffba866e8c90cb91bb299fff2adea1c3815c2d430495710800697f7946988f2c6f664aa1dc582c5ebd22fb121ec7c488fc0cbf47c0d3dc16214d5545b050739da298779b42a6dce98244d8bbb915e9c627248e141a2a7092fa05565ac7ee96028866df311b312657d62b948a4cc6f02ab5b1afced3069b1c0b658efdc3365f3ee52aaeb232c95f5e97065d62e02eeb9199aab3246b4331d58fb792f8d342220ec6ef8000b8c98d878ec668472e2105b21ab03fd288e378acaabd51067dab3882570dcf07ad3a299693ee7fc273e9d97bfe2d26f9710a75b1ba04c584a3bb0663d01c63f61ecbd81236145e2ed9fe84a74494db4b56ccfb8d5a02c8a72e097bb0bcd260e9b78fa32091be8c318cdf76d949b423164277dd6e5bb1a4b7bbfffb3083adc55956723878d0e9fff2456d4b0245d32c32860a73d5a9c5ec84f64eac7b70bbad085afdbc4e7bf5cd86f1affb7fa217bc6391703dbe5254fc0351d02279cbc0260a8400d6f30d80d88fee5be6b334b1a2bc3b885df127760c0e3beed7e139088ec912daf319cb31f51121acc55fca45507a21b746cf662074c11be673fc1eb171202e6c70ab0dafb90d8f9f089140cf8a2472857c73ce5a1586f9888a137b14d0ce89feedeaa715b0685229cecc1c5b2452f746d74e4c9ccf4cd211c92d5da6a9606a2b033b647bce0dc1cd849d30ba49f7251384700a6aa77b89f6317cab26d1fe2041d34fcbe0f481b332644a6ad2eaf0b88fd6f2d42ce82cf3ace1b57afe42137ca1cf4ced7c321538a6f7a928e4bba45920f46b39134738be23bc72e65a3dd1f714d458e71535088a5fd2d64ad7f203860978b5ef2ffb9c12cc9f1da67c32612b0770902ee39865835dcdd797637b4362fa22c748b97a1a36b444a40385b5ab83cb87470d04ebef5833667ba88ff9e5a0a9a586608c89dc190b892ab2ee40cb55fc06b632ee660e7191006b8f7323be53b0551d8feb7b8d43a1835f7fb936995ae38745c1ed57894e58d580334cfd01a1b14b937282e933253e1b8f31f05639e38886c434101caf629f7ed6343af136bcdb4125301569fdd6f5e5d7f046e1430569c708a1e3eae322de41d37701cd36f5b5663ec2702972c6b6fc8888bc78146039e117c02535108205431bda2b8217829f4de6b3bf8eddcedb4515e6194366c94b72c9898846412e5a35a97b29071d550cb4f8e2ea286731acde3a38cd38fc75cd21eaf14f21c4e36a0c27aa0ddcca0b3636662071de068b721d22d00565192cc89f4fd57f39632cde06ae28c39f50a5c42779e4908f5b561973d85a510fe9d60075ab066e6651981666f2b739449c561464d93024a6050215f6aeb4b5b88797001ef62766cf7267194f2b1b0cb6157d202b5a751bf9a07a959af7ab5936450837a940750deb8cee160b40ab01aa9fa103554919a4910d48722efeec2452f0770690e95a791d89d1013e75eff2d885dc8a36ba9a1234f937f11bd93e2705d176ee847931c18c440e04f66cdbaf439ae1656ed1b881ad1b575394dc330c54751fac88ec26aaf252560649ba8d555d93b6cf636aafacc688af067a1e2e3d99fa6624d838c3e2fea61f8eaa3df0e06b0ca22159f5833dcd93902c0ce18a49e619bc675b03407563d2ec0668e2636edd8cc9761669e9acddbf20d6f08d988c33f7349c0a3de3acb84079318434411d198720cab68eaea3bc09154eede78c81c3affba9d9b67c2bd4a2425aa22c219b2adea91b4095b4e1b326d154819227af476fac047d9055b829bbe9fda875140368d6649708a53efbe856accf62a4cc928f808f2ddf309759825564f96f146e86e6f03ad5c4760b110cf03b62991e1fc2eae83abef253198c5f4a034ed42e8f0114bf93c69884c906103503e86d5ae14df8a3798cc45f796ad744286770f4623553755ad0bdd48c8cca866704d1aaaa0025d193aa1e404bb25416d5242b079e37ace3f03507c9b787d15e309a2bf71b57e8099e5bb8772926b0e9ae66b77fb92dfe9fdd4fbbc52e432a08cc7aa9197f400547c4bb9106132b28695c7091324eafd8e922d69831488eae16db5452ae464271f5ebf6fa3036930f690bb00e31a884c03d9a80a1229b9958d82193210c234b4dc06aa1d24feccd34914d60855cd32e3b7a5681fc82f9d0333603234082f1538beae1686a4d264aa3fac888d4ed02fddb465c75270f79dab7de7990a2e2165158a041a088ba0da4b327e48a8af2512ddfe21b4534f9297c99487639373be6fd5cdb510ebda12d7d0e57a50838bc4d714fda7d4cb5b0ac089461ae196805ab21edfa815bd7f0ea0bb52c5aa1f009eccf96c1dc6964014e33e616632181196b17c549374bd0784c04ddaede20be2c628d7d1b4ee3d99ed7ee2ae21f034b8f51374941df3b9c181f25189b7fd02154fffbd3bbd67e6a5379ae94c45c909f096ad27da443588e1fccd3091a5c7dbe483581a8dcf675e462f30a9d073b73a0b89f164b7266adcf503d6ae0cac932430eb06d0d67a6d19ccfa6c3f29e228d4b76fab91d0215a222a81a8d49f096ade86ad33ab72e0e7a1bb762fd26c09a4e19131a5e25e9448f47b12515e835c02259e7893d564860b7f00396f3bada08a8bef4fe419e9f64b066400ee6284c66170acb5ff3822c019b51cba00aa2be32b986412ef6983907734d43138162770413480332867494d033b3d4d437d5851b0abea67a4a19e8cf783b1220e68800c1a0674ae0a1627c08f4f8b2f2fc7e12877aeb007b030fa5b72711649d69888c4586b6988f083169f3203ac663023a663871431dc51225bad783014b4006ec44223fd346d6fc600c819c90eaa132ea209ad6b950d8db41bb6a21f340fc7335a9e9d0c2ecf3b819c5121c63c7c421ba843c148db4b4b323dd148eb2b28df30881fa43252535934d2078bbc8eaee920db359e6b502373f9c466bc65a4cb0c8d46ba842e206aa3910e458575991bd50f70703c0312baa3912e1d1f8d549e96ff5c78c29978485efa422ecf5f314bf5386209760d2303cbd27b5305ba67cf6ca9e857acc6cf40638847914ec571b4dc327f84c4e01f46e4528d64f1823960f84e466c5d55cc068957ba76c2443916ae25e697cb5b65c0b9dce3c80d21dbc06065054d2f7eacd4a99ac9fe9245c9af420e269b0e8c035320c7549f18ec5b317f047803dbff70b18f8bc9cc1324ce36b82de2af91fca54d0d7ec1d614d244542eb8d711e55bbe5816aa6af9f4be42e4226d552d33e8da12c99e0086160b69bc0dc20ba5b99a8e7ba344ff354309e516dc3c232720912142206742f43eb1e75b627e75331e73bb57e3ea816f792a311aa5e8d8554d90ad4826de721bc0dd7c73450646dcc3b044cfe1211e854ffc5d20aacc4ffe70225524455760a0f6037065be10389f86f30b9d87166080c70c7898d868286150c7c0e6cdce3736c2df62226354fa9f6a480686af2d9013157956c4191fbdfae467c435afe39f4dafcafe70be9d763080ba84497a54acdd5fe4b32344b0b4ce4708584c99a52997bef8b68f12ee7034555530d955a517203ce0b91aad2ce1da1ec712bb4d7297273547635e07e5e2ab7ef2a1b771cf3b3dea770ccfffe25783c45ef63ad564f61e7c1c6b6943c1a1538d41ebef6658e144c64da3d2fefb41385737b0c9c0837daa2da11418d455d29f1214d9ba566861eb2fe2f1dc1321707033e6beb40a87a02cf1e408a1391f7d880bd61edb2a5fc571294f47d0e2c76fd58bfd39e4c7530df0182fbf8fd08967b58fa0fb8af62ccf0ee824374785025745c7ea078793cdbe02db29d391dc777b303ccf4627e3537aaed9af522ffc456a814cef60cfcbbd71081077e279edc6688abec5faea056429fadfc51613df7d6a8f9bd7a978842371f552be1f1b37ecef5516bffef70909187ebb9c71ce5a7750777fccad05dbd2096592dd1885b1a8750b085b5594a8052adf6182f83d63547285d10d2e0d43cf54342c7382fa8c310e722b2b5d361960ef98e0108ccc73a83e3da11ba94a53995f7b453e4fc00e8ae354d84449fac7de861b3048e22237dec99d527fe1f8e803bcf9cbdd0714cd244d27fa733b9c9c0cdb3d979cb71b1c8bf439021945c5632054a4292ec6c1f74465c2df9e7ce4bc4f66837abcc1736a5ae0af5d9e2d4a4e7593a900a10d02c6dff7ec2e25097a0c5cca624a2e67150889e2f837b2a6a4ac0b420faae41c70f0cafd4b991ec23d4e408c0cca6f833ccbc2d3e5b4135d7c968e7d1166f56d6d821608416af3f489d89a0b8792ee000310fe3b007b1f76d8f79a35fa893a37a17aff71d683a9490a11ef35b46d46e5ee12d18756d48f80d2020fcda722df6600c69d16cce73ddcc9fb5d058be44377b5db386f704e72892e310c0b21ee610e114ad2d1bcc43774f7007f58e0ff4fd974b096fafa078e4f975548869a8cf3233a0c2fa09590f89c203d7daf7afd8f68cc74c1472ac441a4819f950f23cb5ad3edd977b363ee819e74b19baee623b73c0a753ff7f60e70a4bc6130d9da33af1c9c370e7a109bb84e97d906f5e91ec33d37de93656362d27b78df150d2434a8f736372cbd76ff5d455fba73fd25ff3e02cfc34f4d339a64500d93fce8ab0df118247133b5eb5834e3a07b440f7ae8d7123890b0461b8c84a1502b4df6d6c6edcb9c6cbac7bb9ea0b8eeaf7120bb5b005f8514718a204ae4d57021f6f34fb85b2c3c3d8332e846dc992d7e024ccd78b2c7202b197b3c3a67b823906eed092724128695769763553af540512345e5fd42929c22b872c0530ca20082553f46819139cee1c7e8cfde432a868d5449623311f9a9dfddb7a5e0c34b07f2a93e5eb42c472b23970a4eea3f9009c062a711d38057b67930c2f7dd69901860767bf477e4ee36e5a29f8583702a737ecb6ded4bf498676cd1119aedde656b9defaae6a827adbf9718958b50d6cdfe2819330a0ac95246c77a2abb1e4f2de03b18b2a1edffcd21b629694f6c04d5410c9caa196351bac8c445ef094efadc71b1f6a1cc926ec152362cab2b336f93c152df3644bf91ee6bf9b4897b13e6941f907bd78b4660be062f4bc0d5d0934ec5f7afe828f3be1e2e1fae1e9acd1a2dd9fb13df3c151d9777d85e4d7b9b6d8e8c6aee64b6454ee31728336c9977d69abdf58920680e60e8ef5b1e6a83a302f87447f3de1549ca7dbf5f4a32191d72ba0425270bb7bf6f17d282b3a172cb4287b954c625118da112319485a050f5cc97c79871098303bf669d6e8903fd98dc464d6374a24de1f8eb479ffcb2a901882577f95fe9a721cf4b48fce8c3667037dd3c2ed43ba138331e13dc30be40a6dd07ea7dc2b78f527f0e866c442575230a4e3152525cd3225a9280912b287502d9c24d0fecb9f0f8ed52f600a5cdaa157061e270445124d4960c0060e2b1c210c7f1ef6294924342b7c9c033dba5d3fadf6560b87e9b7616c2bfde253b7d2e31860d38bcaccc6699979696c47600bd6c1d8387d25e3b9a9a29d15dcf63d1b9a10717a5aba28948d55d353dc2302e3ee97a9315945d07caf75cf13d30b4c4b7881a0b2ba198336408db7eeff1a72ba35452f2229fd3392d264c1e046109a6a723f283fdd0b295067689d026bd26cc986d97e0da6179199b121565e2455a6fdc4b408e76e13494a23ea8b6ca8c438880a25aaeaa490434f660c58dd74be371b083b4e14cf1a029b8c6d5b7a3f8fb1994df0628a8c0567c43d0739da80aec11785634f9aec6d4bf7f7267ad42b74d0833628cc871718424334368cf24c9c980d22531c71c6329539926d1e3d33df6df9f24c1713a059a704ecf34927c4e4282856a38a0fe2a1685247e6f03de067fa84fafc8ecdf5bf62d68e8c49dafc3c2cc531a75db46bbfac79ff77e32795e2cbb45d2419bc2ea9ee37a2b4ef05da56daac9e55273dde8292e3bfde07d48c6fea5f6b947a8efd0ef5a0e0fe263fa229d1032c8b979f1fa9fd8420eddcdb6566302cd707c662bbc3c3efd024320814b97edd27fe60b29046652381948c605c66d4360dc7f915285d1559d27eb491bf6c58d400c4c2451653e522209f8c7597e5c375f32114510a522ff96350c0dd6e0527004432d4ac2da84a032c86a0e8b75ffe6f1348b4deaa238e09a8ff7a2cf2ce82beba3073d428f6361798ce59cdfbb347c55bc1a17361168841b072cb6c1041e27d0ab3de0f891b8ef913e4c59b23144fd1b84d06887f21a646b2d96bb8894a6759791c1b243812abeefa16a114d1f14ad5a5dd3834e3997cfe62d397371f953f4256def55fb9b88c783641868c5ae4fe4b61fec596ff97ad3662df86cb0b85950bb11929d4e1815247a60c37635afbf45f0ade00407f98f41cec52bb1b73ef2fe81fcb23a6541b8ea233abfea3ebb714439d54ca433ec96d32f40e762541cf9a67c750f7a643101aeb6bee2ce9e69a27f540db51fab67f55c5bc2dac4afd505b6ec7ebad2415cf3c3b311f0e15b4be4a9b1df76d14dc00cc4927ff8ba3d00276d8166dcf9a7c01ee0df2db64e818a821fe0205eeef49f056c3ffff051f94189c6bf6c7669402418417af5447d3b237c1eb9504cfad72fea840798d2077b66fee5e74b040515336957858afb47d595ee1ca3c7bdcf8b4bf477ec1dbd687f99a1209f885cc13c087ea133b42197edf66bfd8339e1f4322cf20b9ef9a5ef8938ff1aa71fdac3ba0698ffdbc3246016712e0e1523cb10bf7f1d0806bf35552b9691ddcd4f0710f6a2ca4a003e6409", - "0x3a65787472696e7369635f696e646578": "0x00000000", - "0x3a63": "0x", - "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", - "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000" - }, - "childrenDefault": {} - } - } -} diff --git a/cumulus/parachains/runtimes/starters/seedling/Cargo.toml b/cumulus/parachains/runtimes/starters/seedling/Cargo.toml deleted file mode 100644 index c76c09a3123..00000000000 --- a/cumulus/parachains/runtimes/starters/seedling/Cargo.toml +++ /dev/null @@ -1,79 +0,0 @@ -[package] -name = "seedling-runtime" -version = "0.7.0" -description = "Seedling parachain runtime. A starter runtime for solochain to parachain migration." -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" - -[lints] -workspace = true - -[dependencies] -codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } - -# Substrate -frame-executive = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-aura = { workspace = true } -pallet-balances = { workspace = true } -pallet-sudo = { workspace = true } -pallet-timestamp = { workspace = true } -sp-api = { workspace = true } -sp-block-builder = { workspace = true } -sp-consensus-aura = { workspace = true } -sp-core = { workspace = true } -sp-genesis-builder = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-runtime = { workspace = true } -sp-session = { workspace = true } -sp-transaction-pool = { workspace = true } -sp-version = { workspace = true } - -# Cumulus -cumulus-pallet-aura-ext = { workspace = true } -cumulus-pallet-parachain-system = { workspace = true } -cumulus-pallet-solo-to-para = { workspace = true } -cumulus-primitives-core = { workspace = true } -cumulus-primitives-timestamp = { workspace = true } -parachain-info = { workspace = true } -parachains-common = { workspace = true } - -[build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } - -[features] -default = ["std"] -std = [ - "codec/std", - "cumulus-pallet-aura-ext/std", - "cumulus-pallet-parachain-system/std", - "cumulus-pallet-solo-to-para/std", - "cumulus-primitives-core/std", - "cumulus-primitives-timestamp/std", - "frame-executive/std", - "frame-support/std", - "frame-system/std", - "pallet-aura/std", - "pallet-balances/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "parachain-info/std", - "parachains-common/std", - "scale-info/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-core/std", - "sp-genesis-builder/std", - "sp-inherents/std", - "sp-offchain/std", - "sp-runtime/std", - "sp-session/std", - "sp-transaction-pool/std", - "sp-version/std", - "substrate-wasm-builder", -] diff --git a/cumulus/parachains/runtimes/starters/seedling/build.rs b/cumulus/parachains/runtimes/starters/seedling/build.rs deleted file mode 100644 index 60f8a125129..00000000000 --- a/cumulus/parachains/runtimes/starters/seedling/build.rs +++ /dev/null @@ -1,26 +0,0 @@ -// 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(feature = "std")] -fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() -} - -#[cfg(not(feature = "std"))] -fn main() {} diff --git a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs b/cumulus/parachains/runtimes/starters/seedling/src/lib.rs deleted file mode 100644 index bffedd5bdf5..00000000000 --- a/cumulus/parachains/runtimes/starters/seedling/src/lib.rs +++ /dev/null @@ -1,400 +0,0 @@ -// 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 . - -//! # Seedling Runtime -//! -//! Seedling is a parachain meant to help parachain auction winners migrate a blockchain from -//! another consensus system into the consensus system of a given Relay Chain. - -#![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] - -// Make the WASM binary available. -#[cfg(feature = "std")] -include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); - -extern crate alloc; - -use alloc::{vec, vec::Vec}; -use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector}; -use sp_api::impl_runtime_apis; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - -// A few exports that help ease life for downstream crates. -pub use frame_support::{ - construct_runtime, derive_impl, - dispatch::DispatchClass, - genesis_builder_helper::{build_state, get_preset}, - parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, IsInVec, Randomness}, - weights::{ - constants::{ - BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, - }, - IdentityFee, Weight, - }, - StorageValue, -}; -use frame_system::limits::{BlockLength, BlockWeights}; -use parachains_common::{AccountId, Signature}; -#[cfg(any(feature = "std", test))] -pub use sp_runtime::BuildStorage; -pub use sp_runtime::{Perbill, Permill}; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -/// This runtime version. -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("seedling"), - impl_name: create_runtime_str!("seedling"), - authoring_version: 1, - spec_version: 1, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 2, - system_version: 1, -}; - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } -} - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; -/// 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; -/// Relay chain slot duration, in milliseconds. -const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - -/// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. -/// This is used to limit the maximal weight of a single extrinsic. -const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(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_div(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -parameter_types! { - pub const BlockHashCount: BlockNumber = 250; - pub const Version: RuntimeVersion = VERSION; - pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); - pub const SS58Prefix: u8 = 42; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Runtime { - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// The index type for storing how many extrinsics an account has signed. - type Nonce = Nonce; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The block type. - type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// Runtime version. - type Version = Version; - /// Converts a module to an index of this module in the runtime. - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = RuntimeBlockWeights; - type BlockLength = RuntimeBlockLength; - type SS58Prefix = SS58Prefix; - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl pallet_sudo::Config for Runtime { - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_sudo::weights::SubstrateWeight; -} - -impl cumulus_pallet_solo_to_para::Config for Runtime { - type RuntimeEvent = RuntimeEvent; -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type WeightInfo = (); - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = cumulus_pallet_solo_to_para::Pallet; - type SelfParaId = parachain_info::Pallet; - type OutboundXcmpMessageSource = (); - // Ignore all DMP messages by enqueueing them into `()`: - type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; - type ReservedDmpWeight = (); - type XcmpMessageHandler = (); - type ReservedXcmpWeight = (); - type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; - type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; -} - -impl parachain_info::Config for Runtime {} - -impl cumulus_pallet_aura_ext::Config for Runtime {} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type DisabledValidators = (); - type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = Aura; - type MinimumPeriod = ConstU64<0>; - type WeightInfo = (); -} - -construct_runtime! { - pub enum Runtime - { - System: frame_system, - Sudo: pallet_sudo, - Timestamp: pallet_timestamp, - - ParachainSystem: cumulus_pallet_parachain_system, - ParachainInfo: parachain_info, - SoloToPara: cumulus_pallet_solo_to_para, - Aura: pallet_aura, - AuraExt: cumulus_pallet_aura_ext, - } -} - -/// Index of a transaction in the chain. -pub type Nonce = u32; -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; -/// An index to a block. -pub type BlockNumber = u32; -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; -/// Block header type as expected by this runtime. -pub type Header = generic::Header; -/// Block type as expected by this runtime. -pub type Block = generic::Block; -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. -pub type SignedExtra = ( - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - pallet_sudo::CheckOnlySudoAccount, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; - -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, ->; - -impl_runtime_apis! { - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) - } - - fn authorities() -> Vec { - pallet_aura::Authorities::::get().into_inner() - } - } - - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: Block) { - Executive::execute_block(block) - } - - fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> alloc::vec::Vec { - Runtime::metadata_versions() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic( - extrinsic: ::Extrinsic, - ) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents(block: Block, data: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(seed: Option>) -> Vec { - SessionKeys::generate(seed) - } - - fn decode_session_keys( - encoded: Vec, - ) -> Option, KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info(header) - } - } - - impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { - fn core_selector() -> (CoreSelector, ClaimQueueOffset) { - ParachainSystem::core_selector() - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) - } - - fn preset_names() -> Vec { - vec![] - } - } -} - -cumulus_pallet_parachain_system::register_validate_block! { - Runtime = Runtime, - BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, -} diff --git a/cumulus/parachains/runtimes/starters/shell/Cargo.toml b/cumulus/parachains/runtimes/starters/shell/Cargo.toml deleted file mode 100644 index 8f3b2204cfe..00000000000 --- a/cumulus/parachains/runtimes/starters/shell/Cargo.toml +++ /dev/null @@ -1,99 +0,0 @@ -[package] -name = "shell-runtime" -version = "0.7.0" -description = "A minimal runtime to test Relay Chain consensus." -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" - -[lints] -workspace = true - -[dependencies] -codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } - -# Substrate -frame-executive = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -frame-try-runtime = { optional = true, workspace = true } -pallet-aura = { workspace = true } -pallet-timestamp = { workspace = true } -sp-api = { workspace = true } -sp-block-builder = { workspace = true } -sp-consensus-aura = { workspace = true } -sp-core = { workspace = true } -sp-genesis-builder = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-runtime = { workspace = true } -sp-session = { workspace = true } -sp-transaction-pool = { workspace = true } -sp-version = { workspace = true } -pallet-message-queue = { workspace = true } - -# Polkadot -xcm = { workspace = true } -xcm-builder = { workspace = true } -xcm-executor = { workspace = true } - -# Cumulus -cumulus-pallet-aura-ext = { workspace = true } -cumulus-pallet-parachain-system = { workspace = true } -cumulus-pallet-xcm = { workspace = true } -cumulus-primitives-core = { workspace = true } -parachain-info = { workspace = true } -parachains-common = { workspace = true } - -[build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } - -[features] -default = ["std"] -std = [ - "codec/std", - "cumulus-pallet-aura-ext/std", - "cumulus-pallet-parachain-system/std", - "cumulus-pallet-xcm/std", - "cumulus-primitives-core/std", - "frame-executive/std", - "frame-support/std", - "frame-system/std", - "frame-try-runtime?/std", - "pallet-aura/std", - "pallet-message-queue/std", - "pallet-timestamp/std", - "parachain-info/std", - "parachains-common/std", - "scale-info/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-core/std", - "sp-genesis-builder/std", - "sp-inherents/std", - "sp-offchain/std", - "sp-runtime/std", - "sp-session/std", - "sp-transaction-pool/std", - "sp-version/std", - "substrate-wasm-builder", - "xcm-builder/std", - "xcm-executor/std", - "xcm/std", -] -try-runtime = [ - "cumulus-pallet-aura-ext/try-runtime", - "cumulus-pallet-parachain-system/try-runtime", - "cumulus-pallet-xcm/try-runtime", - "frame-executive/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "frame-try-runtime/try-runtime", - "pallet-aura/try-runtime", - "pallet-message-queue/try-runtime", - "pallet-timestamp/try-runtime", - "parachain-info/try-runtime", - "sp-runtime/try-runtime", -] diff --git a/cumulus/parachains/runtimes/starters/shell/build.rs b/cumulus/parachains/runtimes/starters/shell/build.rs deleted file mode 100644 index 896fc0fecf1..00000000000 --- a/cumulus/parachains/runtimes/starters/shell/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 . - -#[cfg(feature = "std")] -fn main() { - substrate_wasm_builder::WasmBuilder::new() - .with_current_project() - .export_heap_base() - .import_memory() - .build() -} - -#[cfg(not(feature = "std"))] -fn main() {} diff --git a/cumulus/parachains/runtimes/starters/shell/src/lib.rs b/cumulus/parachains/runtimes/starters/shell/src/lib.rs deleted file mode 100644 index c7e81d67bba..00000000000 --- a/cumulus/parachains/runtimes/starters/shell/src/lib.rs +++ /dev/null @@ -1,453 +0,0 @@ -// 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 . - -//! # Shell Runtime -//! -//! The Shell runtime defines a minimal parachain. It can listen for a downward message authorizing -//! an upgrade into another parachain. -//! -//! Generally (so far) only used as the first parachain on a Relay. - -#![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] - -// Make the WASM binary available. -#[cfg(feature = "std")] -include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); - -pub mod xcm_config; - -extern crate alloc; - -use alloc::{vec, vec::Vec}; -use codec::{Decode, Encode}; -use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; -use frame_support::unsigned::TransactionValidityError; -use scale_info::TypeInfo; -use sp_api::impl_runtime_apis; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - -// A few exports that help ease life for downstream crates. -pub use frame_support::{ - construct_runtime, derive_impl, - dispatch::DispatchClass, - genesis_builder_helper::{build_state, get_preset}, - parameter_types, - traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, IsInVec, Randomness}, - weights::{ - constants::{ - BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND, - }, - IdentityFee, Weight, - }, - StorageValue, -}; -use frame_system::limits::{BlockLength, BlockWeights}; -use parachains_common::{AccountId, Signature}; -#[cfg(any(feature = "std", test))] -pub use sp_runtime::BuildStorage; -pub use sp_runtime::{Perbill, Permill}; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -/// This runtime version. -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: create_runtime_str!("shell"), - impl_name: create_runtime_str!("shell"), - authoring_version: 1, - spec_version: 2, - impl_version: 0, - apis: RUNTIME_API_VERSIONS, - transaction_version: 1, - system_version: 1, -}; - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } -} - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; -/// 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; -/// Relay chain slot duration, in milliseconds. -const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - -/// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. -/// This is used to limit the maximal weight of a single extrinsic. -const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(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_div(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -parameter_types! { - pub const BlockHashCount: BlockNumber = 250; - pub const Version: RuntimeVersion = VERSION; - pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); - pub const SS58Prefix: u8 = 42; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Runtime { - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The aggregated dispatch type that is available for extrinsics. - type RuntimeCall = RuntimeCall; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// The index type for storing how many extrinsics an account has signed. - type Nonce = Nonce; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The block type. - type Block = Block; - /// The ubiquitous event type. - type RuntimeEvent = RuntimeEvent; - /// The ubiquitous origin type. - type RuntimeOrigin = RuntimeOrigin; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// Runtime version. - type Version = Version; - /// Converts a module to an index of this module in the runtime. - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type SystemWeightInfo = (); - type BlockWeights = RuntimeBlockWeights; - type BlockLength = RuntimeBlockLength; - type SS58Prefix = SS58Prefix; - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -parameter_types! { - pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type WeightInfo = (); - type RuntimeEvent = RuntimeEvent; - type OnSystemEvent = (); - type SelfParaId = parachain_info::Pallet; - type OutboundXcmpMessageSource = (); - type DmpQueue = frame_support::traits::EnqueueWithOrigin; - type ReservedDmpWeight = ReservedDmpWeight; - type XcmpMessageHandler = (); - type ReservedXcmpWeight = (); - type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; - type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; -} - -impl parachain_info::Config for Runtime {} - -parameter_types! { - pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; -} - -impl pallet_message_queue::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - // NOTE: Changes are needed here if you add benchmarking to the runtime. - type MessageProcessor = xcm_builder::ProcessXcmMessage< - AggregateMessageOrigin, - xcm_executor::XcmExecutor, - RuntimeCall, - >; - type Size = u32; - // These need to be configured to the XCMP pallet - if it is deployed. - type QueueChangeHandler = (); - type QueuePausedQuery = (); - type HeapSize = sp_core::ConstU32<{ 103 * 1024 }>; - type MaxStale = sp_core::ConstU32<8>; - type ServiceWeight = MessageQueueServiceWeight; - type IdleMaxServiceWeight = MessageQueueServiceWeight; -} - -impl cumulus_pallet_aura_ext::Config for Runtime {} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type DisabledValidators = (); - type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = Aura; - type MinimumPeriod = ConstU64<0>; - type WeightInfo = (); -} - -construct_runtime! { - pub enum Runtime - { - System: frame_system, - Timestamp: pallet_timestamp, - - ParachainSystem: cumulus_pallet_parachain_system, - ParachainInfo: parachain_info, - - CumulusXcm: cumulus_pallet_xcm, - MessageQueue: pallet_message_queue, - - Aura: pallet_aura, - AuraExt: cumulus_pallet_aura_ext, - } -} - -/// Simple implementation which fails any transaction which is signed. -#[derive(Eq, PartialEq, Clone, Default, sp_core::RuntimeDebug, Encode, Decode, TypeInfo)] -pub struct DisallowSigned; -impl sp_runtime::traits::SignedExtension for DisallowSigned { - const IDENTIFIER: &'static str = "DisallowSigned"; - type AccountId = AccountId; - type Call = RuntimeCall; - type AdditionalSigned = (); - type Pre = (); - fn additional_signed( - &self, - ) -> core::result::Result<(), sp_runtime::transaction_validity::TransactionValidityError> { - Ok(()) - } - fn pre_dispatch( - self, - who: &Self::AccountId, - call: &Self::Call, - info: &DispatchInfoOf, - len: usize, - ) -> Result { - self.validate(who, call, info, len).map(|_| ()) - } - fn validate( - &self, - _who: &Self::AccountId, - _call: &Self::Call, - _info: &sp_runtime::traits::DispatchInfoOf, - _len: usize, - ) -> TransactionValidity { - let i = sp_runtime::transaction_validity::InvalidTransaction::BadProof; - Err(sp_runtime::transaction_validity::TransactionValidityError::Invalid(i)) - } -} - -/// Index of a transaction in the chain. -pub type Nonce = u32; -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; -/// An index to a block. -pub type BlockNumber = u32; -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; -/// Block header type as expected by this runtime. -pub type Header = generic::Header; -/// Block type as expected by this runtime. -pub type Block = generic::Block; -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. -pub type SignedExtra = DisallowSigned; -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, ->; - -impl_runtime_apis! { - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) - } - - fn authorities() -> Vec { - pallet_aura::Authorities::::get().into_inner() - } - } - - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: Block) { - Executive::execute_block(block) - } - - fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> alloc::vec::Vec { - Runtime::metadata_versions() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic( - extrinsic: ::Extrinsic, - ) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents(block: Block, data: sp_inherents::InherentData) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(seed: Option>) -> Vec { - SessionKeys::generate(seed) - } - - fn decode_session_keys( - encoded: Vec, - ) -> Option, KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info(header) - } - } - - impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { - fn core_selector() -> (CoreSelector, ClaimQueueOffset) { - ParachainSystem::core_selector() - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) - } - - fn preset_names() -> Vec { - vec![] - } - } -} - -cumulus_pallet_parachain_system::register_validate_block! { - Runtime = Runtime, - BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, -} diff --git a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs b/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs deleted file mode 100644 index 741b3bcd752..00000000000 --- a/cumulus/parachains/runtimes/starters/shell/src/xcm_config.rs +++ /dev/null @@ -1,100 +0,0 @@ -// 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. - -use super::{ - AccountId, AllPalletsWithSystem, ParachainInfo, Runtime, RuntimeCall, RuntimeEvent, - RuntimeOrigin, -}; -use frame_support::{ - parameter_types, - traits::{Contains, Everything, Nothing}, - weights::Weight, -}; -use xcm::latest::prelude::*; -use xcm_builder::{ - AllowExplicitUnpaidExecutionFrom, FixedWeightBounds, ParentAsSuperuser, ParentIsPreset, - SovereignSignedViaLocation, -}; - -parameter_types! { - pub const RococoLocation: Location = Location::parent(); - pub const RococoNetwork: NetworkId = NetworkId::Rococo; - pub UniversalLocation: InteriorLocation = [GlobalConsensus(RococoNetwork::get()), Parachain(ParachainInfo::parachain_id().into())].into(); -} - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// bias the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, RuntimeOrigin>, - // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a - // transaction from the Root origin. - ParentAsSuperuser, -); - -pub struct JustTheParent; -impl Contains for JustTheParent { - fn contains(location: &Location) -> bool { - matches!(location.unpack(), (1, [])) - } -} - -parameter_types! { - // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; -} - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = (); // sending XCM not supported - type AssetTransactor = (); // balances not supported - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = (); // balances not supported - type IsTeleporter = (); // balances not supported - type UniversalLocation = UniversalLocation; - type Barrier = AllowExplicitUnpaidExecutionFrom; - type Weigher = FixedWeightBounds; // balances not supported - type Trader = (); // balances not supported - type ResponseHandler = (); // Don't handle responses for now. - type AssetTrap = (); // don't trap for now - type AssetClaims = (); // don't claim for now - type SubscriptionService = (); // don't handle subscriptions for now - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = (); - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); - type XcmRecorder = (); -} - -impl cumulus_pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = xcm_executor::XcmExecutor; -} diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index d52bc639b3b..fb95853c492 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -24,9 +24,7 @@ serde_json = { workspace = true, default-features = true } # Local polkadot-parachain-lib = { features = ["rococo-native", "westend-native"], workspace = true } rococo-parachain-runtime = { workspace = true } -shell-runtime = { workspace = true } glutton-westend-runtime = { workspace = true } -seedling-runtime = { workspace = true } asset-hub-rococo-runtime = { workspace = true, default-features = true } asset-hub-westend-runtime = { workspace = true } collectives-westend-runtime = { workspace = true } @@ -100,7 +98,6 @@ try-runtime = [ "penpal-runtime/try-runtime", "people-rococo-runtime/try-runtime", "people-westend-runtime/try-runtime", - "shell-runtime/try-runtime", ] fast-runtime = [ "bridge-hub-rococo-runtime/fast-runtime", diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index ff85bdf9401..350dcfee1cd 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -26,7 +26,7 @@ use crate::{ NodeBlock, NodeExtraArgs, }, fake_runtime_api, - nodes::{shell::ShellNode, DynNodeSpecExt}, + nodes::DynNodeSpecExt, runtime::BlockNumber, }; #[cfg(feature = "runtime-benchmarks")] @@ -77,7 +77,6 @@ fn new_node_spec( let runtime = runtime_resolver.runtime(config.chain_spec.as_ref())?; Ok(match runtime { - Runtime::Shell => Box::new(ShellNode), Runtime::Omni(block_number, consensus) => match (block_number, consensus) { (BlockNumber::U32, Consensus::Aura(aura_id)) => new_aura_node_spec::>(aura_id, extra_args), diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs index a4e157e8721..85665c9b220 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs @@ -40,27 +40,6 @@ pub(crate) trait BuildRpcExtensions { ) -> sc_service::error::Result; } -pub(crate) struct BuildEmptyRpcExtensions(PhantomData<(Block, RuntimeApi)>); - -impl - BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::FullPool>, - > for BuildEmptyRpcExtensions -where - RuntimeApi: - ConstructNodeRuntimeApi> + Send + Sync + 'static, -{ - fn build_rpc_extensions( - _client: Arc>, - _backend: Arc>, - _pool: Arc>>, - ) -> sc_service::error::Result { - Ok(RpcExtension::new(())) - } -} - pub(crate) struct BuildParachainRpcExtensions(PhantomData<(Block, RuntimeApi)>); impl diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs index bddbb0a85d0..509d13b9d7a 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs @@ -49,8 +49,6 @@ pub enum Runtime { /// None of the system-chain runtimes, rather the node will act agnostic to the runtime ie. be /// an omni-node, and simply run a node with the given consensus algorithm. Omni(BlockNumber, Consensus), - /// Shell - Shell, } /// Helper trait used for extracting the Runtime variant from the chain spec ID. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs index 36f54fa3d05..ab13322e80a 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs @@ -16,7 +16,6 @@ pub mod aura; mod manual_seal; -pub mod shell; use crate::common::spec::{DynNodeSpec, NodeSpec as NodeSpecT}; use cumulus_primitives_core::ParaId; diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs deleted file mode 100644 index 5f9c671d710..00000000000 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/shell.rs +++ /dev/null @@ -1,152 +0,0 @@ -// 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 . - -use crate::{ - common::{ - rpc::BuildEmptyRpcExtensions, - spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, - types::{Block, Hash, ParachainBackend, ParachainBlockImport, ParachainClient}, - NodeExtraArgs, - }, - fake_runtime_api::aura_sr25519::RuntimeApi as FakeRuntimeApi, -}; -#[docify::export(slot_based_colator_import)] -#[allow(deprecated)] -use cumulus_client_service::old_consensus; -use cumulus_client_service::CollatorSybilResistance; -use cumulus_primitives_core::ParaId; -use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; -use polkadot_primitives::CollatorPair; -use prometheus_endpoint::Registry; -use sc_consensus::DefaultImportQueue; -use sc_service::{Configuration, Error, TaskManager}; -use sc_telemetry::TelemetryHandle; -use sc_transaction_pool::FullPool; -use sp_keystore::KeystorePtr; -use std::{sync::Arc, time::Duration}; - -/// Build the import queue for the shell runtime. -pub(crate) struct BuildShellImportQueue; - -impl BuildImportQueue, FakeRuntimeApi> for BuildShellImportQueue { - fn build_import_queue( - client: Arc, FakeRuntimeApi>>, - block_import: ParachainBlockImport, FakeRuntimeApi>, - config: &Configuration, - _telemetry_handle: Option, - task_manager: &TaskManager, - ) -> sc_service::error::Result>> { - cumulus_client_consensus_relay_chain::import_queue( - client, - block_import, - |_, _| async { Ok(()) }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - ) - .map_err(Into::into) - } -} - -/// Start relay-chain consensus that is free for all. Everyone can submit a block, the relay-chain -/// decides what is backed and included. -pub(crate) struct StartRelayChainConsensus; - -impl StartConsensus, FakeRuntimeApi> for StartRelayChainConsensus { - fn start_consensus( - client: Arc, FakeRuntimeApi>>, - block_import: ParachainBlockImport, FakeRuntimeApi>, - prometheus_registry: Option<&Registry>, - telemetry: Option, - task_manager: &TaskManager, - relay_chain_interface: Arc, - transaction_pool: Arc, ParachainClient, FakeRuntimeApi>>>, - _keystore: KeystorePtr, - _relay_chain_slot_duration: Duration, - para_id: ParaId, - collator_key: CollatorPair, - overseer_handle: OverseerHandle, - announce_block: Arc>) + Send + Sync>, - _backend: Arc>>, - _node_extra_args: NodeExtraArgs, - ) -> Result<(), Error> { - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( - task_manager.spawn_handle(), - client.clone(), - transaction_pool, - prometheus_registry, - telemetry, - ); - - let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( - cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { - para_id, - proposer_factory, - block_import, - relay_chain_interface: relay_chain_interface.clone(), - create_inherent_data_providers: move |_, (relay_parent, validation_data)| { - let relay_chain_interface = relay_chain_interface.clone(); - async move { - let parachain_inherent = - cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( - relay_parent, - &relay_chain_interface, - &validation_data, - para_id, - ).await; - let parachain_inherent = parachain_inherent.ok_or_else(|| { - Box::::from( - "Failed to create parachain inherent", - ) - })?; - Ok(parachain_inherent) - } - }, - }, - ); - - let spawner = task_manager.spawn_handle(); - - // Required for free-for-all consensus - #[allow(deprecated)] - old_consensus::start_collator_sync(old_consensus::StartCollatorParams { - para_id, - block_status: client.clone(), - announce_block, - overseer_handle, - spawner, - key: collator_key, - parachain_consensus: free_for_all, - runtime_api: client.clone(), - }); - - Ok(()) - } -} - -pub(crate) struct ShellNode; - -impl BaseNodeSpec for ShellNode { - type Block = Block; - type RuntimeApi = FakeRuntimeApi; - type BuildImportQueue = BuildShellImportQueue; -} - -impl NodeSpec for ShellNode { - type BuildRpcExtensions = BuildEmptyRpcExtensions, Self::RuntimeApi>; - type StartConsensus = StartRelayChainConsensus; - - const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Unresistant; -} diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index adb3eb2c0e0..16fdbdc3865 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -34,8 +34,6 @@ pub mod glutton; pub mod penpal; pub mod people; pub mod rococo_parachain; -pub mod seedling; -pub mod shell; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; @@ -75,10 +73,6 @@ impl LoadSpec for ChainSpecLoader { &include_bytes!("../../chain-specs/track.json")[..], )?), - // -- Starters - "shell" => Box::new(shell::get_shell_chain_spec()), - "seedling" => Box::new(seedling::get_seedling_chain_spec()), - // -- Asset Hub Polkadot "asset-hub-polkadot" | "statemint" => Box::new(GenericChainSpec::from_json_bytes( &include_bytes!("../../chain-specs/asset-hub-polkadot.json")[..], @@ -202,8 +196,6 @@ impl LoadSpec for ChainSpecLoader { #[derive(Debug, PartialEq)] enum LegacyRuntime { Omni, - Shell, - Seedling, AssetHubPolkadot, AssetHub, Penpal, @@ -218,11 +210,7 @@ impl LegacyRuntime { fn from_id(id: &str) -> LegacyRuntime { let id = id.replace('_', "-"); - if id.starts_with("shell") { - LegacyRuntime::Shell - } else if id.starts_with("seedling") { - LegacyRuntime::Seedling - } else if id.starts_with("asset-hub-polkadot") | id.starts_with("statemint") { + if id.starts_with("asset-hub-polkadot") | id.starts_with("statemint") { LegacyRuntime::AssetHubPolkadot } else if id.starts_with("asset-hub-kusama") | id.starts_with("statemine") | @@ -277,7 +265,6 @@ impl RuntimeResolverT for RuntimeResolver { LegacyRuntime::Penpal | LegacyRuntime::Omni => Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519)), - LegacyRuntime::Shell | LegacyRuntime::Seedling => Runtime::Shell, }) } } @@ -336,15 +323,6 @@ mod tests { #[test] fn test_legacy_runtime_for_different_chain_specs() { - let chain_spec = create_default_with_extensions("shell-1", Extensions1::default()); - assert_eq!(LegacyRuntime::Shell, LegacyRuntime::from_id(chain_spec.id())); - - let chain_spec = create_default_with_extensions("shell-2", Extensions2::default()); - assert_eq!(LegacyRuntime::Shell, LegacyRuntime::from_id(chain_spec.id())); - - let chain_spec = create_default_with_extensions("seedling", Extensions2::default()); - assert_eq!(LegacyRuntime::Seedling, LegacyRuntime::from_id(chain_spec.id())); - let chain_spec = create_default_with_extensions("penpal-rococo-1000", Extensions2::default()); assert_eq!(LegacyRuntime::Penpal, LegacyRuntime::from_id(chain_spec.id())); diff --git a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs b/cumulus/polkadot-parachain/src/chain_spec/seedling.rs deleted file mode 100644 index a104b58db5d..00000000000 --- a/cumulus/polkadot-parachain/src/chain_spec/seedling.rs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 . - -use crate::chain_spec::get_account_id_from_seed; -use cumulus_primitives_core::ParaId; -use parachains_common::{AccountId, AuraId}; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; -use sc_service::ChainType; -use sp_core::sr25519; - -use super::get_collator_keys_from_seed; - -pub fn get_seedling_chain_spec() -> GenericChainSpec { - GenericChainSpec::builder( - seedling_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { relay_chain: "westend".into(), para_id: 2000 }, - ) - .with_name("Seedling Local Testnet") - .with_id("seedling_local_testnet") - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(seedling_testnet_genesis( - get_account_id_from_seed::("Alice"), - 2000.into(), - vec![get_collator_keys_from_seed::("Alice")], - )) - .with_boot_nodes(Vec::new()) - .build() -} - -fn seedling_testnet_genesis( - root_key: AccountId, - parachain_id: ParaId, - collators: Vec, -) -> serde_json::Value { - serde_json::json!({ - "sudo": { "key": Some(root_key) }, - "parachainInfo": { - "parachainId": parachain_id, - }, - "aura": { "authorities": collators }, - }) -} diff --git a/cumulus/polkadot-parachain/src/chain_spec/shell.rs b/cumulus/polkadot-parachain/src/chain_spec/shell.rs deleted file mode 100644 index 0a7816ab319..00000000000 --- a/cumulus/polkadot-parachain/src/chain_spec/shell.rs +++ /dev/null @@ -1,45 +0,0 @@ -// 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 . - -use cumulus_primitives_core::ParaId; -use parachains_common::AuraId; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; -use sc_service::ChainType; - -use super::get_collator_keys_from_seed; - -pub fn get_shell_chain_spec() -> GenericChainSpec { - GenericChainSpec::builder( - shell_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { relay_chain: "westend".into(), para_id: 1000 }, - ) - .with_name("Shell Local Testnet") - .with_id("shell_local_testnet") - .with_chain_type(ChainType::Local) - .with_genesis_config_patch(shell_testnet_genesis( - 1000.into(), - vec![get_collator_keys_from_seed::("Alice")], - )) - .with_boot_nodes(Vec::new()) - .build() -} - -fn shell_testnet_genesis(parachain_id: ParaId, collators: Vec) -> serde_json::Value { - serde_json::json!({ - "parachainInfo": { "parachainId": parachain_id}, - "aura": { "authorities": collators }, - }) -} diff --git a/prdoc/pr_5911.prdoc b/prdoc/pr_5911.prdoc new file mode 100644 index 00000000000..8b063242f24 --- /dev/null +++ b/prdoc/pr_5911.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: Removed the possibility to start a shell parachain node + +doc: + - audience: Node Dev + description: | + Removed the possibility to start a shell parachain node using the polkadot-parachain-lib or + polkadot-parachain-bin. + +crates: + - name: polkadot-parachain-lib + bump: minor + - name: polkadot-parachain-bin + bump: minor -- GitLab From 826f16f31693c6080fc1c94f37fba61c240243fb Mon Sep 17 00:00:00 2001 From: Egor_P Date: Fri, 4 Oct 2024 10:19:33 +0200 Subject: [PATCH 041/127] Add stable-rc tags to the docker images (#5896) This PR adds the `stableYYMM-rcX` or `stableYYMM-X-rcX` tags to the docker images, so that they could be published with the new tag naming scheme. Closes: https://github.com/paritytech/release-engineering/issues/224 --- .github/scripts/common/lib.sh | 3 +- .../workflows/release-50_publish-docker.yml | 50 +++++++++++++----- .../polkadot/polkadot_injected.Dockerfile | 52 +++++++++++++++++++ docker/scripts/build-injected.sh | 2 +- docker/scripts/polkadot/build-injected.sh | 1 + 5 files changed, 94 insertions(+), 14 deletions(-) create mode 100644 docker/dockerfiles/polkadot/polkadot_injected.Dockerfile diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index 5361db398ae..d2a4baf12fa 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -242,6 +242,7 @@ fetch_release_artifacts() { # - GITHUB_TOKEN # - REPO in the form paritytech/polkadot fetch_release_artifacts_from_s3() { + BINARY=$1 echo "Version : $VERSION" echo "Repo : $REPO" echo "Binary : $BINARY" @@ -461,7 +462,7 @@ function get_polkadot_node_version_from_code() { validate_stable_tag() { tag="$1" - pattern='^stable[0-9]+(-[0-9]+)?$' + pattern="^stable[0-9]{4}(-[0-9]+)?(-rc[0-9]+)?$" if [[ $tag =~ $pattern ]]; then echo $tag diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index 72e01a4833e..ecaf3295d40 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -86,7 +86,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Validate inputs id: validate_inputs @@ -111,7 +111,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 #TODO: this step will be needed when automated triggering will work #this step runs only if the workflow is triggered automatically when new release is published @@ -134,7 +134,14 @@ jobs: . ./.github/scripts/common/lib.sh VERSION="${{ needs.validate-inputs.outputs.VERSION }}" - fetch_release_artifacts_from_s3 + if [[ ${{ inputs.binary }} == 'polkadot' ]]; then + bins=(polkadot polkadot-prepare-worker polkadot-execute-worker) + for bin in "${bins[@]}"; do + fetch_release_artifacts_from_s3 $bin + done + else + fetch_release_artifacts_from_s3 $BINARY + fi - name: Fetch chain-spec-builder rc artifacts or release artifacts based on release id #this step runs only if the workflow is triggered manually and only for chain-spec-builder @@ -159,7 +166,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -187,15 +194,14 @@ jobs: run: | . ./.github/scripts/common/lib.sh - release="release-${{ needs.validate-inputs.outputs.RELEASE_ID }}" && \ + release="${{ needs.validate-inputs.outputs.stable_tag }}" && \ echo "release=${release}" >> $GITHUB_OUTPUT commit=$(git rev-parse --short HEAD) && \ echo "commit=${commit}" >> $GITHUB_OUTPUT - tag=$(git name-rev --tags --name-only $(git rev-parse HEAD)) && \ - [ "${tag}" != "undefined" ] && echo "tag=${tag}" >> $GITHUB_OUTPUT || \ - echo "No tag, doing without" + tag="${{ needs.validate-inputs.outputs.version }}" && \ + echo "tag=${tag}" >> $GITHUB_OUTPUT - name: Fetch release tags working-directory: release-artifacts @@ -215,8 +221,20 @@ jobs: echo "release=${release}" >> $GITHUB_OUTPUT echo "stable=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT - - name: Build Injected Container image for polkadot rc or chain-spec-builder - if: ${{ env.BINARY == 'polkadot' || env.BINARY == 'chain-spec-builder' }} + - name: Build Injected Container image for polkadot rc + if: ${{ env.BINARY == 'polkadot' }} + env: + ARTIFACTS_FOLDER: release-artifacts + IMAGE_NAME: ${{ env.BINARY }} + OWNER: ${{ env.DOCKER_OWNER }} + TAGS: ${{ join(steps.fetch_rc_refs.outputs.*, ',') || join(steps.fetch_release_refs.outputs.*, ',') }} + run: | + ls -al + echo "Building container for $BINARY" + ./docker/scripts/polkadot/build-injected.sh $ARTIFACTS_FOLDER + + - name: Build Injected Container image chain-spec-builder + if: ${{ env.BINARY == 'chain-spec-builder' }} env: ARTIFACTS_FOLDER: release-artifacts IMAGE_NAME: ${{ env.BINARY }} @@ -243,7 +261,15 @@ jobs: echo "Building container for $BINARY" ./docker/scripts/build-injected.sh - - name: Login to Dockerhub + - name: Login to Dockerhub to publish polkadot + if: ${{ env.BINARY == 'polkadot' }} + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ secrets.POLKADOT_DOCKERHUB_USERNAME }} + password: ${{ secrets.POLKADOT_DOCKERHUB_TOKEN }} + + - name: Login to Dockerhub to puiblish polkadot-parachain/chain-spec-builder + if: ${{ env.BINARY == 'polkadot-parachain' || env.BINARY == 'chain-spec-builder' }} uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: username: ${{ secrets.CUMULUS_DOCKERHUB_USERNAME }} @@ -295,7 +321,7 @@ jobs: environment: release steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 diff --git a/docker/dockerfiles/polkadot/polkadot_injected.Dockerfile b/docker/dockerfiles/polkadot/polkadot_injected.Dockerfile new file mode 100644 index 00000000000..3dbede4966a --- /dev/null +++ b/docker/dockerfiles/polkadot/polkadot_injected.Dockerfile @@ -0,0 +1,52 @@ +FROM docker.io/parity/base-bin + +# metadata +ARG VCS_REF +ARG BUILD_DATE +ARG IMAGE_NAME +# That can be a single one or a comma separated list +ARG BINARY=polkadot + +LABEL io.parity.image.authors="devops-team@parity.io" \ + io.parity.image.vendor="Parity Technologies" \ + io.parity.image.title="parity/polkadot" \ + io.parity.image.description="Polkadot: a platform for web3. This is the official Parity image with an injected binary." \ + io.parity.image.source="https://github.com/paritytech/polkadot-sdk/blob/${VCS_REF}/docker/dockerfiles/polkadot/polkadot_injected.Dockerfile" \ + io.parity.image.revision="${VCS_REF}" \ + io.parity.image.created="${BUILD_DATE}" \ + io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/" + +# show backtraces +ENV RUST_BACKTRACE 1 + +USER root +WORKDIR /app + +# add polkadot and polkadot-*-worker binaries to the docker image +COPY bin/* /usr/local/bin/ +COPY entrypoint.sh . + + +RUN chmod -R a+rx "/usr/local/bin"; \ + mkdir -p /data /polkadot/.local/share && \ + chown -R parity:parity /data && \ + ln -s /data /polkadot/.local/share/polkadot + +USER parity + +# check if executable works in this container +RUN /usr/local/bin/polkadot --version +RUN /usr/local/bin/polkadot-prepare-worker --version +RUN /usr/local/bin/polkadot-execute-worker --version + + +EXPOSE 30333 9933 9944 9615 +VOLUME ["/polkadot"] + +ENV BINARY=${BINARY} + +# ENTRYPOINT +ENTRYPOINT ["/app/entrypoint.sh"] + +# We call the help by default +CMD ["--help"] diff --git a/docker/scripts/build-injected.sh b/docker/scripts/build-injected.sh index 749d0fa335c..c37ea916c83 100755 --- a/docker/scripts/build-injected.sh +++ b/docker/scripts/build-injected.sh @@ -40,7 +40,7 @@ VCS_REF=${VCS_REF:-01234567} echo "Using engine: $ENGINE" echo "Using Dockerfile: $DOCKERFILE" echo "Using context: $CONTEXT" -echo "Building ${IMAGE}:latest container image for ${BINARY} v${VERSION} from ${ARTIFACTS_FOLDER} hang on!" +echo "Building ${IMAGE}:latest container image for ${BINARY} ${VERSION} from ${ARTIFACTS_FOLDER} hang on!" echo "ARTIFACTS_FOLDER=$ARTIFACTS_FOLDER" echo "CONTEXT=$CONTEXT" diff --git a/docker/scripts/polkadot/build-injected.sh b/docker/scripts/polkadot/build-injected.sh index 7cc6db43a54..8f4e7005b81 100755 --- a/docker/scripts/polkadot/build-injected.sh +++ b/docker/scripts/polkadot/build-injected.sh @@ -9,5 +9,6 @@ PROJECT_ROOT=`git rev-parse --show-toplevel` export BINARY=polkadot,polkadot-execute-worker,polkadot-prepare-worker export ARTIFACTS_FOLDER=$1 +export DOCKERFILE="docker/dockerfiles/polkadot/polkadot_injected.Dockerfile" $PROJECT_ROOT/docker/scripts/build-injected.sh -- GitLab From 88570c263f1835780f446b9fd2814134a927f8b0 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 4 Oct 2024 11:02:53 +0200 Subject: [PATCH 042/127] Update "@polkadot/api": "^14.0" for bridges zombienet tests (#5918) Relates to: https://github.com/paritytech/polkadot-sdk/pull/5916 Relates to: https://github.com/polkadot-js/api/pull/5976 --------- Co-authored-by: Javier Viola --- .../package-lock.json | 759 ------------------ .../generate_hex_encoded_call/package.json | 4 +- 2 files changed, 2 insertions(+), 761 deletions(-) delete mode 100644 bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json deleted file mode 100644 index ca3abcc528c..00000000000 --- a/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json +++ /dev/null @@ -1,759 +0,0 @@ -{ - "name": "y", - "version": "y", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "y", - "version": "y", - "license": "MIT", - "dependencies": { - "@polkadot/api": "^10.11", - "@polkadot/util": "^12.6" - } - }, - "node_modules/@noble/curves": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", - "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", - "dependencies": { - "@noble/hashes": "1.3.3" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/api": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.11.2.tgz", - "integrity": "sha512-AorCZxCWCoTtdbl4DPUZh+ACe/pbLIS1BkdQY0AFJuZllm0x/yWzjgampcPd5jQAA/O3iKShRBkZqj6Mk9yG/A==", - "dependencies": { - "@polkadot/api-augment": "10.11.2", - "@polkadot/api-base": "10.11.2", - "@polkadot/api-derive": "10.11.2", - "@polkadot/keyring": "^12.6.2", - "@polkadot/rpc-augment": "10.11.2", - "@polkadot/rpc-core": "10.11.2", - "@polkadot/rpc-provider": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-augment": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/types-create": "10.11.2", - "@polkadot/types-known": "10.11.2", - "@polkadot/util": "^12.6.2", - "@polkadot/util-crypto": "^12.6.2", - "eventemitter3": "^5.0.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-augment": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.11.2.tgz", - "integrity": "sha512-PTpnqpezc75qBqUtgrc0GYB8h9UHjfbHSRZamAbecIVAJ2/zc6CqtnldeaBlIu1IKTgBzi3FFtTyYu+ZGbNT2Q==", - "dependencies": { - "@polkadot/api-base": "10.11.2", - "@polkadot/rpc-augment": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-augment": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-base": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.11.2.tgz", - "integrity": "sha512-4LIjaUfO9nOzilxo7XqzYKCNMtmUypdk8oHPdrRnSjKEsnK7vDsNi+979z2KXNXd2KFSCFHENmI523fYnMnReg==", - "dependencies": { - "@polkadot/rpc-core": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/util": "^12.6.2", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-derive": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.11.2.tgz", - "integrity": "sha512-m3BQbPionkd1iSlknddxnL2hDtolPIsT+aRyrtn4zgMRPoLjHFmTmovvg8RaUyYofJtZeYrnjMw0mdxiSXx7eA==", - "dependencies": { - "@polkadot/api": "10.11.2", - "@polkadot/api-augment": "10.11.2", - "@polkadot/api-base": "10.11.2", - "@polkadot/rpc-core": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/util": "^12.6.2", - "@polkadot/util-crypto": "^12.6.2", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/keyring": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz", - "integrity": "sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw==", - "dependencies": { - "@polkadot/util": "12.6.2", - "@polkadot/util-crypto": "12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "12.6.2", - "@polkadot/util-crypto": "12.6.2" - } - }, - "node_modules/@polkadot/networks": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz", - "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==", - "dependencies": { - "@polkadot/util": "12.6.2", - "@substrate/ss58-registry": "^1.44.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-augment": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.11.2.tgz", - "integrity": "sha512-9AhT0WW81/8jYbRcAC6PRmuxXqNhJje8OYiulBQHbG1DTCcjAfz+6VQBke9BwTStzPq7d526+yyBKD17O3zlAA==", - "dependencies": { - "@polkadot/rpc-core": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-core": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.11.2.tgz", - "integrity": "sha512-Ot0CFLWx8sZhLZog20WDuniPA01Bk2StNDsdAQgcFKPwZw6ShPaZQCHuKLQK6I6DodOrem9FXX7c1hvoKJP5Ww==", - "dependencies": { - "@polkadot/rpc-augment": "10.11.2", - "@polkadot/rpc-provider": "10.11.2", - "@polkadot/types": "10.11.2", - "@polkadot/util": "^12.6.2", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-provider": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.11.2.tgz", - "integrity": "sha512-he5jWMpDJp7e+vUzTZDzpkB7ps3H8psRally+/ZvZZScPvFEjfczT7I1WWY9h58s8+ImeVP/lkXjL9h/gUOt3Q==", - "dependencies": { - "@polkadot/keyring": "^12.6.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-support": "10.11.2", - "@polkadot/util": "^12.6.2", - "@polkadot/util-crypto": "^12.6.2", - "@polkadot/x-fetch": "^12.6.2", - "@polkadot/x-global": "^12.6.2", - "@polkadot/x-ws": "^12.6.2", - "eventemitter3": "^5.0.1", - "mock-socket": "^9.3.1", - "nock": "^13.4.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@substrate/connect": "0.7.35" - } - }, - "node_modules/@polkadot/types": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.11.2.tgz", - "integrity": "sha512-d52j3xXni+C8GdYZVTSfu8ROAnzXFMlyRvXtor0PudUc8UQHOaC4+mYAkTBGA2gKdmL8MHSfRSbhcxHhsikY6Q==", - "dependencies": { - "@polkadot/keyring": "^12.6.2", - "@polkadot/types-augment": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/types-create": "10.11.2", - "@polkadot/util": "^12.6.2", - "@polkadot/util-crypto": "^12.6.2", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-augment": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.11.2.tgz", - "integrity": "sha512-8eB8ew04wZiE5GnmFvEFW1euJWmF62SGxb1O+8wL3zoUtB9Xgo1vB6w6xbTrd+HLV6jNSeXXnbbF1BEUvi9cNg==", - "dependencies": { - "@polkadot/types": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-codec": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.11.2.tgz", - "integrity": "sha512-3xjOQL+LOOMzYqlgP9ROL0FQnzU8lGflgYewzau7AsDlFziSEtb49a9BpYo6zil4koC+QB8zQ9OHGFumG08T8w==", - "dependencies": { - "@polkadot/util": "^12.6.2", - "@polkadot/x-bigint": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-create": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.11.2.tgz", - "integrity": "sha512-SJt23NxYvefRxVZZm6mT9ed1pR6FDoIGQ3xUpbjhTLfU2wuhpKjekMVorYQ6z/gK2JLMu2kV92Ardsz+6GX5XQ==", - "dependencies": { - "@polkadot/types-codec": "10.11.2", - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-known": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.11.2.tgz", - "integrity": "sha512-kbEIX7NUQFxpDB0FFGNyXX/odY7jbp56RGD+Z4A731fW2xh/DgAQrI994xTzuh0c0EqPE26oQm3kATSpseqo9w==", - "dependencies": { - "@polkadot/networks": "^12.6.2", - "@polkadot/types": "10.11.2", - "@polkadot/types-codec": "10.11.2", - "@polkadot/types-create": "10.11.2", - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-support": { - "version": "10.11.2", - "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.11.2.tgz", - "integrity": "sha512-X11hoykFYv/3efg4coZy2hUOUc97JhjQMJLzDhHniFwGLlYU8MeLnPdCVGkXx0xDDjTo4/ptS1XpZ5HYcg+gRw==", - "dependencies": { - "@polkadot/util": "^12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz", - "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==", - "dependencies": { - "@polkadot/x-bigint": "12.6.2", - "@polkadot/x-global": "12.6.2", - "@polkadot/x-textdecoder": "12.6.2", - "@polkadot/x-textencoder": "12.6.2", - "@types/bn.js": "^5.1.5", - "bn.js": "^5.2.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz", - "integrity": "sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg==", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "12.6.2", - "@polkadot/util": "12.6.2", - "@polkadot/wasm-crypto": "^7.3.2", - "@polkadot/wasm-util": "^7.3.2", - "@polkadot/x-bigint": "12.6.2", - "@polkadot/x-randomvalues": "12.6.2", - "@scure/base": "^1.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "12.6.2" - } - }, - "node_modules/@polkadot/wasm-bridge": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz", - "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==", - "dependencies": { - "@polkadot/wasm-util": "7.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz", - "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==", - "dependencies": { - "@polkadot/wasm-bridge": "7.3.2", - "@polkadot/wasm-crypto-asmjs": "7.3.2", - "@polkadot/wasm-crypto-init": "7.3.2", - "@polkadot/wasm-crypto-wasm": "7.3.2", - "@polkadot/wasm-util": "7.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-asmjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz", - "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-init": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz", - "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==", - "dependencies": { - "@polkadot/wasm-bridge": "7.3.2", - "@polkadot/wasm-crypto-asmjs": "7.3.2", - "@polkadot/wasm-crypto-wasm": "7.3.2", - "@polkadot/wasm-util": "7.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-wasm": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz", - "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==", - "dependencies": { - "@polkadot/wasm-util": "7.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-util": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz", - "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/x-bigint": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz", - "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-fetch": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz", - "integrity": "sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "node-fetch": "^3.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-global": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.6.2.tgz", - "integrity": "sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-randomvalues": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz", - "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "12.6.2", - "@polkadot/wasm-util": "*" - } - }, - "node_modules/@polkadot/x-textdecoder": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz", - "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-textencoder": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz", - "integrity": "sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-ws": { - "version": "12.6.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.6.2.tgz", - "integrity": "sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw==", - "dependencies": { - "@polkadot/x-global": "12.6.2", - "tslib": "^2.6.2", - "ws": "^8.15.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@substrate/connect": { - "version": "0.7.35", - "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.35.tgz", - "integrity": "sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "@substrate/connect-extension-protocol": "^1.0.1", - "smoldot": "2.0.7" - } - }, - "node_modules/@substrate/connect-extension-protocol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz", - "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==", - "optional": true - }, - "node_modules/@substrate/ss58-registry": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz", - "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A==" - }, - "node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.10.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", - "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/mock-socket": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", - "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nock": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz", - "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==", - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/smoldot": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.7.tgz", - "integrity": "sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA==", - "optional": true, - "dependencies": { - "ws": "^8.8.1" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - } - } -} diff --git a/bridges/testing/framework/utils/generate_hex_encoded_call/package.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json index ecf0a2483db..d3406c97c61 100644 --- a/bridges/testing/framework/utils/generate_hex_encoded_call/package.json +++ b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json @@ -5,7 +5,7 @@ "main": "index.js", "license": "MIT", "dependencies": { - "@polkadot/api": "^10.11", - "@polkadot/util": "^12.6" + "@polkadot/api": "^14.0", + "@polkadot/util": "^13.1" } } -- GitLab From 111b2447a0ce9122a31ad67c4a8ad1479db159a0 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Fri, 4 Oct 2024 11:55:18 +0200 Subject: [PATCH 043/127] Prevents EthereumBlobExporter from consuming dest when returning NotApplicable (#5789) # Description The EthereumBlobExporter consumes the `dest` parameter when the destination is not `Here`. Subsequent exporters will receive a `None` value for the destination instead of the original destination value, which is incorrect. Closes #5788 ## Integration Minor fix related to the exporter behaviour. ## Review Notes Verified that tests `exporter_validate_with_invalid_dest_does_not_alter_destination` and `exporter_validate_with_invalid_universal_source_does_not_alter_universal_source` fail without the fix in the exporter. --------- Co-authored-by: Adrian Catangiu --- .../primitives/router/src/outbound/mod.rs | 22 ++-- .../primitives/router/src/outbound/tests.rs | 116 +++++++++++++++++- prdoc/pr_5789.prdoc | 19 +++ 3 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_5789.prdoc diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index b23bce0e218..efc1ef56f30 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -69,13 +69,15 @@ where return Err(SendError::NotApplicable) } - let dest = destination.take().ok_or(SendError::MissingArgument)?; + // Cloning destination to avoid modifying the value so subsequent exporters can use it. + let dest = destination.clone().take().ok_or(SendError::MissingArgument)?; if dest != Here { log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); return Err(SendError::NotApplicable) } - let (local_net, local_sub) = universal_source + // Cloning universal_source to avoid modifying the value so subsequent exporters can use it. + let (local_net, local_sub) = universal_source.clone() .take() .ok_or_else(|| { log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); @@ -84,7 +86,7 @@ where .split_global() .map_err(|()| { log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); - SendError::Unroutable + SendError::NotApplicable })?; if Ok(local_net) != universal_location.global_consensus() { @@ -96,25 +98,25 @@ where [Parachain(para_id)] => *para_id, _ => { log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); - return Err(SendError::MissingArgument) + return Err(SendError::NotApplicable) }, }; - let message = message.take().ok_or_else(|| { - log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); - SendError::MissingArgument - })?; - let source_location = Location::new(1, local_sub.clone()); let agent_id = match AgentHashedDescription::convert_location(&source_location) { Some(id) => id, None => { log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); - return Err(SendError::Unroutable) + return Err(SendError::NotApplicable) }, }; + let message = message.take().ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); + SendError::MissingArgument + })?; + let mut converter = XcmConverter::::new(&message, expected_network, agent_id); let (command, message_id) = converter.convert().map_err(|err|{ diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 6e4fd594634..8bd3fa24df5 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -148,7 +148,7 @@ fn exporter_validate_without_universal_source_yields_missing_argument() { } #[test] -fn exporter_validate_without_global_universal_location_yields_unroutable() { +fn exporter_validate_without_global_universal_location_yields_not_applicable() { let network = BridgedNetwork::get(); let channel: u32 = 0; let mut universal_source: Option = Here.into(); @@ -163,7 +163,7 @@ fn exporter_validate_without_global_universal_location_yields_unroutable() { AgentIdOf, MockTokenIdConvert, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::Unroutable)); + assert_eq!(result, Err(XcmSendError::NotApplicable)); } #[test] @@ -206,7 +206,7 @@ fn exporter_validate_with_remote_universal_source_yields_not_applicable() { } #[test] -fn exporter_validate_without_para_id_in_source_yields_missing_argument() { +fn exporter_validate_without_para_id_in_source_yields_not_applicable() { let network = BridgedNetwork::get(); let channel: u32 = 0; let mut universal_source: Option = Some(GlobalConsensus(Polkadot).into()); @@ -221,11 +221,11 @@ fn exporter_validate_without_para_id_in_source_yields_missing_argument() { AgentIdOf, MockTokenIdConvert, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); + assert_eq!(result, Err(XcmSendError::NotApplicable)); } #[test] -fn exporter_validate_complex_para_id_in_source_yields_missing_argument() { +fn exporter_validate_complex_para_id_in_source_yields_not_applicable() { let network = BridgedNetwork::get(); let channel: u32 = 0; let mut universal_source: Option = @@ -241,7 +241,7 @@ fn exporter_validate_complex_para_id_in_source_yields_missing_argument() { AgentIdOf, MockTokenIdConvert, >::validate(network, channel, &mut universal_source, &mut destination, &mut message); - assert_eq!(result, Err(XcmSendError::MissingArgument)); + assert_eq!(result, Err(XcmSendError::NotApplicable)); } #[test] @@ -1163,3 +1163,107 @@ fn xcm_converter_transfer_native_token_with_invalid_location_will_fail() { let result = converter.convert(); assert_eq!(result.err(), Some(XcmConverterError::InvalidAsset)); } + +#[test] +fn exporter_validate_with_invalid_dest_does_not_alter_destination() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Parachain(1000).into(); + + let universal_source: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} + +#[test] +fn exporter_validate_with_invalid_universal_source_does_not_alter_universal_source() { + let network = BridgedNetwork::get(); + let destination: InteriorLocation = Here.into(); + + let universal_source: InteriorLocation = [GlobalConsensus(Westend), Parachain(1000)].into(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: Assets = vec![Asset { + id: AssetId([AccountKey20 { network: None, key: token_address }].into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: AssetFilter = assets.clone().into(); + let msg: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut msg_wrapper: Option> = Some(msg.clone()); + let mut dest_wrapper = Some(destination.clone()); + let mut universal_source_wrapper = Some(universal_source.clone()); + + let result = + EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + MockTokenIdConvert, + >::validate( + network, channel, &mut universal_source_wrapper, &mut dest_wrapper, &mut msg_wrapper + ); + + assert_eq!(result, Err(XcmSendError::NotApplicable)); + + // ensure mutable variables are not changed + assert_eq!(Some(destination), dest_wrapper); + assert_eq!(Some(msg), msg_wrapper); + assert_eq!(Some(universal_source), universal_source_wrapper); +} diff --git a/prdoc/pr_5789.prdoc b/prdoc/pr_5789.prdoc new file mode 100644 index 00000000000..9a808fc89d5 --- /dev/null +++ b/prdoc/pr_5789.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: Prevents EthereumBlobExporter from consuming parameters when returning NotApplicable + +doc: + - audience: Node Dev + description: | + When the EthereumBlobExporter returned a NotApplicable error, it consumed parameters `universal_source`, + `destination` and `message`. As a result, subsequent exporters could not use these values. This PR corrects + this incorrect behaviour. It also changes error type from `Unroutable` to `NotApplicable` when the global consensus + system cannot be extracted from the `universal_source`, or when the source location cannot be converted to an agent + ID. Lastly, it changes the error type from `MissingArgument` to `NotApplicable` when the parachain ID cannot be + extracted from the location. These changes should have no effect - it is purely to correct behvaiour should + multiple exporters be used. + +crates: + - name: snowbridge-router-primitives + bump: patch -- GitLab From dada6cea6447ce2730a3f3b43a3b48b7a5c26cf6 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:18:40 +0300 Subject: [PATCH 044/127] Remove jaeger everywhere (#5875) Jaeger tracing went mostly unused and it created bigger problems like wasting CPU or memory leaks, so remove it entirely. Fixes: https://github.com/paritytech/polkadot-sdk/issues/4995 --------- Signed-off-by: Alexandru Gheorghe --- Cargo.lock | 65 +-- Cargo.toml | 3 - .../src/lib.rs | 1 - polkadot/cli/src/cli.rs | 6 - polkadot/cli/src/command.rs | 37 +- .../node/collation-generation/src/tests.rs | 1 - .../core/approval-voting-parallel/Cargo.toml | 1 - polkadot/node/core/av-store/Cargo.toml | 1 - polkadot/node/core/av-store/src/lib.rs | 7 - .../node/core/bitfield-signing/src/lib.rs | 54 +- .../node/core/bitfield-signing/src/tests.rs | 9 +- .../core/candidate-validation/src/tests.rs | 1 - polkadot/node/core/provisioner/src/lib.rs | 20 +- polkadot/node/jaeger/Cargo.toml | 23 - polkadot/node/jaeger/src/config.rs | 73 --- polkadot/node/jaeger/src/errors.rs | 28 - polkadot/node/jaeger/src/lib.rs | 169 ------ polkadot/node/jaeger/src/spans.rs | 520 ------------------ .../availability-distribution/src/lib.rs | 27 +- .../src/pov_requester/mod.rs | 13 - .../src/requester/fetch_task/mod.rs | 41 +- .../src/requester/fetch_task/tests.rs | 1 - .../src/requester/mod.rs | 40 +- .../src/requester/tests.rs | 18 +- .../src/responder.rs | 10 +- .../network/availability-recovery/src/lib.rs | 6 - .../network/bitfield-distribution/src/lib.rs | 35 +- .../bitfield-distribution/src/tests.rs | 14 +- polkadot/node/network/bridge/src/rx/mod.rs | 2 +- polkadot/node/network/bridge/src/rx/tests.rs | 8 +- .../src/collator_side/mod.rs | 26 +- .../src/collator_side/tests/mod.rs | 2 - .../tests/prospective_parachains.rs | 3 +- .../src/validator_side/collation.rs | 14 - .../src/validator_side/mod.rs | 27 +- .../tests/prospective_parachains.rs | 3 +- polkadot/node/network/protocol/Cargo.toml | 1 - polkadot/node/network/protocol/src/lib.rs | 29 +- .../src/legacy_v1/mod.rs | 32 +- .../src/legacy_v1/requester.rs | 14 +- .../src/legacy_v1/tests.rs | 2 - polkadot/node/overseer/src/dummy.rs | 1 - polkadot/node/overseer/src/lib.rs | 28 +- polkadot/node/service/src/lib.rs | 47 +- polkadot/node/service/src/overseer.rs | 3 - .../src/lib/availability/mod.rs | 7 +- .../node/subsystem-bench/src/lib/mock/mod.rs | 1 - .../node/subsystem-test-helpers/src/mock.rs | 9 +- polkadot/node/subsystem-types/Cargo.toml | 1 - polkadot/node/subsystem-types/src/errors.rs | 4 - polkadot/node/subsystem-types/src/lib.rs | 10 +- polkadot/node/subsystem-util/Cargo.toml | 1 - polkadot/node/subsystem/Cargo.toml | 1 - polkadot/node/subsystem/src/lib.rs | 3 - polkadot/node/test/service/src/lib.rs | 2 - .../adder/collator/src/main.rs | 1 - .../undying/collator/src/main.rs | 1 - prdoc/pr_5875.prdoc | 47 ++ umbrella/Cargo.toml | 7 +- umbrella/src/lib.rs | 4 - 60 files changed, 139 insertions(+), 1426 deletions(-) delete mode 100644 polkadot/node/jaeger/Cargo.toml delete mode 100644 polkadot/node/jaeger/src/config.rs delete mode 100644 polkadot/node/jaeger/src/errors.rs delete mode 100644 polkadot/node/jaeger/src/lib.rs delete mode 100644 polkadot/node/jaeger/src/spans.rs create mode 100644 prdoc/pr_5875.prdoc diff --git a/Cargo.lock b/Cargo.lock index 2747a3dece0..568d642879b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7732,12 +7732,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "integer-encoding" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" - [[package]] name = "integer-sqrt" version = "0.1.5" @@ -9564,17 +9558,6 @@ dependencies = [ "sp-core 28.0.0", ] -[[package]] -name = "mick-jaeger" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69672161530e8aeca1d1400fbf3f1a1747ff60ea604265a4e906c2442df20532" -dependencies = [ - "futures", - "rand", - "thrift", -] - [[package]] name = "mime" version = "0.3.17" @@ -10585,15 +10568,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ordered-float" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-float" version = "2.10.1" @@ -14274,7 +14248,6 @@ dependencies = [ "parking_lot 0.12.3", "polkadot-approval-distribution", "polkadot-node-core-approval-voting", - "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -14317,7 +14290,6 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "polkadot-erasure-coding", - "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", @@ -14686,23 +14658,6 @@ dependencies = [ "tracing-gum", ] -[[package]] -name = "polkadot-node-jaeger" -version = "7.0.0" -dependencies = [ - "log", - "mick-jaeger", - "parity-scale-codec", - "parking_lot 0.12.3", - "polkadot-node-primitives", - "polkadot-primitives", - "sc-network", - "sc-network-types", - "sp-core 28.0.0", - "thiserror", - "tokio", -] - [[package]] name = "polkadot-node-metrics" version = "7.0.0" @@ -14743,7 +14698,6 @@ dependencies = [ "futures", "hex", "parity-scale-codec", - "polkadot-node-jaeger", "polkadot-node-primitives", "polkadot-primitives", "rand", @@ -14787,7 +14741,6 @@ dependencies = [ name = "polkadot-node-subsystem" version = "7.0.0" dependencies = [ - "polkadot-node-jaeger", "polkadot-node-subsystem-types", "polkadot-overseer", ] @@ -14823,7 +14776,6 @@ dependencies = [ "fatality", "futures", "orchestra", - "polkadot-node-jaeger", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-primitives", @@ -14862,7 +14814,6 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "polkadot-erasure-coding", - "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -15447,7 +15398,6 @@ dependencies = [ "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", - "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -20308,7 +20258,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float 2.10.1", + "ordered-float", "serde", ] @@ -24709,19 +24659,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "thrift" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b82ca8f46f95b3ce96081fe3dd89160fdea970c254bb72925255d1b62aae692e" -dependencies = [ - "byteorder", - "integer-encoding", - "log", - "ordered-float 1.1.1", - "threadpool", -] - [[package]] name = "tikv-jemalloc-ctl" version = "0.5.4" diff --git a/Cargo.toml b/Cargo.toml index 8c9bf848c5a..df662ac2698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,7 +175,6 @@ members = [ "polkadot/node/core/runtime-api", "polkadot/node/gum", "polkadot/node/gum/proc-macro", - "polkadot/node/jaeger", "polkadot/node/malus", "polkadot/node/metrics", "polkadot/node/network/approval-distribution", @@ -843,7 +842,6 @@ merkleized-metadata = { version = "0.1.0" } merlin = { version = "3.0", default-features = false } messages-relay = { path = "bridges/relays/messages" } metered = { version = "0.6.1", default-features = false, package = "prioritized-metered-channel" } -mick-jaeger = { version = "0.1.8" } milagro-bls = { version = "1.5.4", default-features = false, package = "snowbridge-milagro-bls" } minimal-template-node = { path = "templates/minimal/node" } minimal-template-runtime = { path = "templates/minimal/runtime" } @@ -1047,7 +1045,6 @@ polkadot-node-core-pvf-common = { path = "polkadot/node/core/pvf/common", defaul polkadot-node-core-pvf-execute-worker = { path = "polkadot/node/core/pvf/execute-worker", default-features = false } polkadot-node-core-pvf-prepare-worker = { path = "polkadot/node/core/pvf/prepare-worker", default-features = false } polkadot-node-core-runtime-api = { path = "polkadot/node/core/runtime-api", default-features = false } -polkadot-node-jaeger = { path = "polkadot/node/jaeger", default-features = false } polkadot-node-metrics = { path = "polkadot/node/metrics", default-features = false } polkadot-node-network-protocol = { path = "polkadot/node/network/protocol", default-features = false } polkadot-node-primitives = { path = "polkadot/node/primitives", default-features = false } diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index f0a082dce53..3a204b0f383 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -363,7 +363,6 @@ fn build_polkadot_full_node( // Disable BEEFY. It should not be required by the internal relay chain node. enable_beefy: false, force_authoring_backoff: false, - jaeger_agent: None, telemetry_worker_handle, // Cumulus doesn't spawn PVF workers, so we can disable version checks. diff --git a/polkadot/cli/src/cli.rs b/polkadot/cli/src/cli.rs index 1445ade08e2..eb67a395634 100644 --- a/polkadot/cli/src/cli.rs +++ b/polkadot/cli/src/cli.rs @@ -93,12 +93,6 @@ pub struct RunCmd { #[arg(long)] pub force_authoring_backoff: bool, - /// Add the destination address to the 'Jaeger' agent. - /// - /// Must be valid socket address, of format `IP:Port` (commonly `127.0.0.1:6831`). - #[arg(long)] - pub jaeger_agent: Option, - /// Add the destination address to the `pyroscope` agent. /// /// Must be valid socket address, of format `IP:Port` (commonly `127.0.0.1:4040`). diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 34ada58bbcd..d124c8fb7eb 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -23,14 +23,15 @@ use polkadot_service::{ benchmarking::{benchmark_inherent_data, RemarkBuilder, TransferKeepAliveBuilder}, HeaderBackend, IdentifyVariant, }; +#[cfg(feature = "pyroscope")] +use pyroscope_pprofrs::{pprof_backend, PprofConfig}; use sc_cli::SubstrateCli; use sp_core::crypto::Ss58AddressFormatRegistry; use sp_keyring::Sr25519Keyring; -use std::net::ToSocketAddrs; pub use crate::error::Error; #[cfg(feature = "pyroscope")] -use pyroscope_pprofrs::{pprof_backend, PprofConfig}; +use std::net::ToSocketAddrs; type Result = std::result::Result; @@ -195,18 +196,6 @@ where info!("----------------------------"); } - let jaeger_agent = if let Some(ref jaeger_agent) = cli.run.jaeger_agent { - Some( - jaeger_agent - .to_socket_addrs() - .map_err(Error::AddressResolutionFailure)? - .next() - .ok_or_else(|| Error::AddressResolutionMissing)?, - ) - } else { - None - }; - let node_version = if cli.run.disable_worker_version_check { None } else { Some(NODE_VERSION.to_string()) }; @@ -227,7 +216,6 @@ where is_parachain_node: polkadot_service::IsParachainNode::No, enable_beefy, force_authoring_backoff: cli.run.force_authoring_backoff, - jaeger_agent, telemetry_worker_handle: None, node_version, secure_validator_mode, @@ -306,7 +294,7 @@ pub fn run() -> Result<()> { runner.async_run(|mut config| { let (client, _, import_queue, task_manager) = - polkadot_service::new_chain_ops(&mut config, None)?; + polkadot_service::new_chain_ops(&mut config)?; Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager)) }) }, @@ -318,8 +306,7 @@ pub fn run() -> Result<()> { Ok(runner.async_run(|mut config| { let (client, _, _, task_manager) = - polkadot_service::new_chain_ops(&mut config, None) - .map_err(Error::PolkadotService)?; + polkadot_service::new_chain_ops(&mut config).map_err(Error::PolkadotService)?; Ok((cmd.run(client, config.database).map_err(Error::SubstrateCli), task_manager)) })?) }, @@ -330,8 +317,7 @@ pub fn run() -> Result<()> { set_default_ss58_version(chain_spec); Ok(runner.async_run(|mut config| { - let (client, _, _, task_manager) = - polkadot_service::new_chain_ops(&mut config, None)?; + let (client, _, _, task_manager) = polkadot_service::new_chain_ops(&mut config)?; Ok((cmd.run(client, config.chain_spec).map_err(Error::SubstrateCli), task_manager)) })?) }, @@ -343,7 +329,7 @@ pub fn run() -> Result<()> { Ok(runner.async_run(|mut config| { let (client, _, import_queue, task_manager) = - polkadot_service::new_chain_ops(&mut config, None)?; + polkadot_service::new_chain_ops(&mut config)?; Ok((cmd.run(client, import_queue).map_err(Error::SubstrateCli), task_manager)) })?) }, @@ -359,7 +345,7 @@ pub fn run() -> Result<()> { Ok(runner.async_run(|mut config| { let (client, backend, _, task_manager) = - polkadot_service::new_chain_ops(&mut config, None)?; + polkadot_service::new_chain_ops(&mut config)?; let task_handle = task_manager.spawn_handle(); let aux_revert = Box::new(|client, backend, blocks| { polkadot_service::revert_backend(client, backend, blocks, config, task_handle) @@ -392,22 +378,21 @@ pub fn run() -> Result<()> { .into()), #[cfg(feature = "runtime-benchmarks")] BenchmarkCmd::Storage(cmd) => runner.sync_run(|mut config| { - let (client, backend, _, _) = - polkadot_service::new_chain_ops(&mut config, None)?; + let (client, backend, _, _) = polkadot_service::new_chain_ops(&mut config)?; let db = backend.expose_db(); let storage = backend.expose_storage(); cmd.run(config, client.clone(), db, storage).map_err(Error::SubstrateCli) }), BenchmarkCmd::Block(cmd) => runner.sync_run(|mut config| { - let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config, None)?; + let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?; cmd.run(client.clone()).map_err(Error::SubstrateCli) }), // These commands are very similar and can be handled in nearly the same way. BenchmarkCmd::Extrinsic(_) | BenchmarkCmd::Overhead(_) => runner.sync_run(|mut config| { - let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config, None)?; + let (client, _, _, _) = polkadot_service::new_chain_ops(&mut config)?; let header = client.header(client.info().genesis_hash).unwrap().unwrap(); let inherent_data = benchmark_inherent_data(header) .map_err(|e| format!("generating inherent data: {:?}", e))?; diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index 0feee79e763..7f76988bb03 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -1090,7 +1090,6 @@ mod helpers { unpin_handle: polkadot_node_subsystem_test_helpers::mock::dummy_unpin_handle( activated_hash, ), - span: Arc::new(overseer::jaeger::Span::Disabled), }), ..Default::default() }))) diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml index e62062eab40..3a98cce80e9 100644 --- a/polkadot/node/core/approval-voting-parallel/Cargo.toml +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -24,7 +24,6 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index c867180e541..1d14e4cfba3 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -25,7 +25,6 @@ polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } sp-consensus = { workspace = true } -polkadot-node-jaeger = { workspace = true, default-features = true } [dev-dependencies] log = { workspace = true, default-features = true } diff --git a/polkadot/node/core/av-store/src/lib.rs b/polkadot/node/core/av-store/src/lib.rs index 7b245c9e3c5..9473040e8f5 100644 --- a/polkadot/node/core/av-store/src/lib.rs +++ b/polkadot/node/core/av-store/src/lib.rs @@ -39,7 +39,6 @@ use polkadot_node_subsystem_util::database::{DBTransaction, Database}; use sp_consensus::SyncOracle; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; -use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{AvailableData, ErasureChunk}; use polkadot_node_subsystem::{ errors::{ChainApiError, RuntimeApiError}, @@ -1315,10 +1314,6 @@ fn store_available_data( }, }; - let erasure_span = jaeger::Span::new(candidate_hash, "erasure-coding") - .with_candidate(candidate_hash) - .with_pov(&available_data.pov); - // Important note: This check below is critical for consensus and the `backing` subsystem relies // on it to ensure candidate validity. let chunks = polkadot_erasure_coding::obtain_chunks_v1(n_validators, &available_data)?; @@ -1328,8 +1323,6 @@ fn store_available_data( return Err(Error::InvalidErasureRoot) } - drop(erasure_span); - let erasure_chunks: Vec<_> = chunks .iter() .zip(branches.map(|(proof, _)| proof)) diff --git a/polkadot/node/core/bitfield-signing/src/lib.rs b/polkadot/node/core/bitfield-signing/src/lib.rs index e3effb7949e..474de1c66ab 100644 --- a/polkadot/node/core/bitfield-signing/src/lib.rs +++ b/polkadot/node/core/bitfield-signing/src/lib.rs @@ -27,10 +27,9 @@ use futures::{ FutureExt, }; use polkadot_node_subsystem::{ - jaeger, messages::{AvailabilityStoreMessage, BitfieldDistributionMessage}, - overseer, ActivatedLeaf, FromOrchestra, OverseerSignal, PerLeafSpan, SpawnedSubsystem, - SubsystemError, SubsystemResult, + overseer, ActivatedLeaf, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, + SubsystemResult, }; use polkadot_node_subsystem_util::{ self as util, request_availability_cores, runtime::recv_runtime, Validator, @@ -80,11 +79,8 @@ async fn get_core_availability( core: &CoreState, validator_index: ValidatorIndex, sender: &Mutex<&mut impl overseer::BitfieldSigningSenderTrait>, - span: &jaeger::Span, ) -> Result { if let CoreState::Occupied(core) = core { - let _span = span.child("query-chunk-availability"); - let (tx, rx) = oneshot::channel(); sender .lock() @@ -118,15 +114,12 @@ async fn get_core_availability( /// prone to false negatives) async fn construct_availability_bitfield( relay_parent: Hash, - span: &jaeger::Span, validator_idx: ValidatorIndex, sender: &mut impl overseer::BitfieldSigningSenderTrait, ) -> Result { // get the set of availability cores from the runtime - let availability_cores = { - let _span = span.child("get-availability-cores"); - recv_runtime(request_availability_cores(relay_parent, sender).await).await? - }; + let availability_cores = + { recv_runtime(request_availability_cores(relay_parent, sender).await).await? }; // Wrap the sender in a Mutex to share it between the futures. // @@ -140,7 +133,7 @@ async fn construct_availability_bitfield( let results = future::try_join_all( availability_cores .iter() - .map(|core| get_core_availability(core, validator_idx, &sender, span)), + .map(|core| get_core_availability(core, validator_idx, &sender)), ) .await?; @@ -234,8 +227,6 @@ async fn handle_active_leaves_update( where Sender: overseer::BitfieldSigningSenderTrait, { - let span = PerLeafSpan::new(leaf.span, "bitfield-signing"); - let span_delay = span.child("delay"); let wait_until = Instant::now() + SPAWNED_TASK_DELAY; // now do all the work we can before we need to wait for the availability store @@ -253,28 +244,16 @@ where // SPAWNED_TASK_DELAY each time. let _timer = metrics.time_run(); - drop(span_delay); - let span_availability = span.child("availability"); - - let bitfield = match construct_availability_bitfield( - leaf.hash, - &span_availability, - validator.index(), - &mut sender, - ) - .await - { - Err(Error::Runtime(runtime_err)) => { - // Don't take down the node on runtime API errors. - gum::warn!(target: LOG_TARGET, err = ?runtime_err, "Encountered a runtime API error"); - return Ok(()) - }, - Err(err) => return Err(err), - Ok(bitfield) => bitfield, - }; - - drop(span_availability); - let span_signing = span.child("signing"); + let bitfield = + match construct_availability_bitfield(leaf.hash, validator.index(), &mut sender).await { + Err(Error::Runtime(runtime_err)) => { + // Don't take down the node on runtime API errors. + gum::warn!(target: LOG_TARGET, err = ?runtime_err, "Encountered a runtime API error"); + return Ok(()) + }, + Err(err) => return Err(err), + Ok(bitfield) => bitfield, + }; let signed_bitfield = match validator.sign(keystore, bitfield).map_err(|e| Error::Keystore(e))? { @@ -290,9 +269,6 @@ where metrics.on_bitfield_signed(); - drop(span_signing); - let _span_gossip = span.child("gossip"); - sender .send_message(BitfieldDistributionMessage::DistributeBitfield(leaf.hash, signed_bitfield)) .await; diff --git a/polkadot/node/core/bitfield-signing/src/tests.rs b/polkadot/node/core/bitfield-signing/src/tests.rs index eeaa524d1c6..c08018375cf 100644 --- a/polkadot/node/core/bitfield-signing/src/tests.rs +++ b/polkadot/node/core/bitfield-signing/src/tests.rs @@ -40,13 +40,8 @@ fn construct_availability_bitfield_works() { let validator_index = ValidatorIndex(1u32); let (mut sender, mut receiver) = polkadot_node_subsystem_test_helpers::sender_receiver(); - let future = construct_availability_bitfield( - relay_parent, - &jaeger::Span::Disabled, - validator_index, - &mut sender, - ) - .fuse(); + let future = + construct_availability_bitfield(relay_parent, validator_index, &mut sender).fuse(); pin_mut!(future); let hash_a = CandidateHash(Hash::repeat_byte(1)); diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 17ca67889f6..46de55e4836 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -1215,7 +1215,6 @@ fn dummy_active_leaves_update(hash: Hash) -> ActiveLeavesUpdate { hash, number: 10, unpin_handle: polkadot_node_subsystem_test_helpers::mock::dummy_unpin_handle(hash), - span: Arc::new(overseer::jaeger::Span::Disabled), }), ..Default::default() } diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index ffc5859b775..9a06d9cff0c 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -27,13 +27,12 @@ use futures_timer::Delay; use schnellru::{ByLength, LruMap}; use polkadot_node_subsystem::{ - jaeger, messages::{ Ancestors, CandidateBackingMessage, ChainApiMessage, ProspectiveParachainsMessage, ProvisionableData, ProvisionerInherentData, ProvisionerMessage, RuntimeApiRequest, }, - overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan, - SpawnedSubsystem, SubsystemError, + overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, + SubsystemError, }; use polkadot_node_subsystem_util::{ has_required_runtime, request_availability_cores, request_persisted_validation_data, @@ -95,13 +94,10 @@ pub struct PerRelayParent { signed_bitfields: Vec, is_inherent_ready: bool, awaiting_inherent: Vec>, - span: PerLeafSpan, } impl PerRelayParent { fn new(leaf: ActivatedLeaf, per_session: &PerSession) -> Self { - let span = PerLeafSpan::new(leaf.span.clone(), "provisioner"); - Self { leaf, backed_candidates: Vec::new(), @@ -110,7 +106,6 @@ impl PerRelayParent { signed_bitfields: Vec::new(), is_inherent_ready: false, awaiting_inherent: Vec::new(), - span, } } } @@ -270,12 +265,11 @@ async fn handle_communication( }, ProvisionerMessage::ProvisionableData(relay_parent, data) => { if let Some(state) = per_relay_parent.get_mut(&relay_parent) { - let span = state.span.child("provisionable-data"); let _timer = metrics.time_provisionable_data(); gum::trace!(target: LOG_TARGET, ?relay_parent, "Received provisionable data: {:?}", &data); - note_provisionable_data(state, &span, data); + note_provisionable_data(state, data); } }, } @@ -295,12 +289,10 @@ async fn send_inherent_data_bg( let backed_candidates = per_relay_parent.backed_candidates.clone(); let mode = per_relay_parent.prospective_parachains_mode; let elastic_scaling_mvp = per_relay_parent.elastic_scaling_mvp; - let span = per_relay_parent.span.child("req-inherent-data"); let mut sender = ctx.sender().clone(); let bg = async move { - let _span = span; let _timer = metrics.time_request_inherent_data(); gum::trace!( @@ -359,7 +351,6 @@ async fn send_inherent_data_bg( fn note_provisionable_data( per_relay_parent: &mut PerRelayParent, - span: &jaeger::Span, provisionable_data: ProvisionableData, ) { match provisionable_data { @@ -373,10 +364,7 @@ fn note_provisionable_data( para = ?backed_candidate.descriptor().para_id, "noted backed candidate", ); - let _span = span - .child("provisionable-backed") - .with_candidate(candidate_hash) - .with_para_id(backed_candidate.descriptor().para_id); + per_relay_parent.backed_candidates.push(backed_candidate); }, // We choose not to punish these forms of misbehavior for the time being. diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml deleted file mode 100644 index 8615041bdaf..00000000000 --- a/polkadot/node/jaeger/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "polkadot-node-jaeger" -version = "7.0.0" -authors.workspace = true -edition.workspace = true -license.workspace = true -description = "Polkadot Jaeger primitives, but equally useful for Grafana/Tempo" - -[lints] -workspace = true - -[dependencies] -mick-jaeger = { workspace = true } -parking_lot = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -thiserror = { workspace = true } -tokio = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } -codec = { workspace = true } diff --git a/polkadot/node/jaeger/src/config.rs b/polkadot/node/jaeger/src/config.rs deleted file mode 100644 index 702a22e1245..00000000000 --- a/polkadot/node/jaeger/src/config.rs +++ /dev/null @@ -1,73 +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 . - -//! Polkadot Jaeger configuration. - -/// Configuration for the jaeger tracing. -#[derive(Clone)] -pub struct JaegerConfig { - pub(crate) node_name: String, - pub(crate) agent_addr: std::net::SocketAddr, -} - -impl std::default::Default for JaegerConfig { - fn default() -> Self { - Self { - node_name: "unknown_".to_owned(), - agent_addr: "127.0.0.1:6831" - .parse() - .expect(r#"Static "127.0.0.1:6831" is a valid socket address string. qed"#), - } - } -} - -impl JaegerConfig { - /// Use the builder pattern to construct a configuration. - pub fn builder() -> JaegerConfigBuilder { - JaegerConfigBuilder::default() - } -} - -/// Jaeger configuration builder. -#[derive(Default)] -pub struct JaegerConfigBuilder { - inner: JaegerConfig, -} - -impl JaegerConfigBuilder { - /// Set the name for this node. - pub fn named(mut self, name: S) -> Self - where - S: AsRef, - { - self.inner.node_name = name.as_ref().to_owned(); - self - } - - /// Set the agent address to send the collected spans to. - pub fn agent(mut self, addr: U) -> Self - where - U: Into, - { - self.inner.agent_addr = addr.into(); - self - } - - /// Construct the configuration. - pub fn build(self) -> JaegerConfig { - self.inner - } -} diff --git a/polkadot/node/jaeger/src/errors.rs b/polkadot/node/jaeger/src/errors.rs deleted file mode 100644 index adedda34c7f..00000000000 --- a/polkadot/node/jaeger/src/errors.rs +++ /dev/null @@ -1,28 +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 . - -//! Polkadot Jaeger error definitions. - -/// A description of an error during jaeger initialization. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum JaegerError { - #[error("Already launched the collector thread")] - AlreadyLaunched, - - #[error("Missing jaeger configuration")] - MissingConfiguration, -} diff --git a/polkadot/node/jaeger/src/lib.rs b/polkadot/node/jaeger/src/lib.rs deleted file mode 100644 index 2ce5b8b6eb6..00000000000 --- a/polkadot/node/jaeger/src/lib.rs +++ /dev/null @@ -1,169 +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 . - -//! Polkadot Jaeger related primitives -//! -//! Provides primitives used by Polkadot for interfacing with Jaeger. -//! -//! # Integration -//! -//! See for an introduction. -//! -//! The easiest way to try Jaeger is: -//! -//! - Start a docker container with the all-in-one docker image (see below). -//! - Open your browser and navigate to to access the UI. -//! -//! The all-in-one image can be started with: -//! -//! ```not_rust -//! podman login docker.io -//! podman run -d --name jaeger \ -//! -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ -//! -p 5775:5775/udp \ -//! -p 6831:6831/udp \ -//! -p 6832:6832/udp \ -//! -p 5778:5778 \ -//! -p 16686:16686 \ -//! -p 14268:14268 \ -//! -p 14250:14250 \ -//! -p 9411:9411 \ -//! docker.io/jaegertracing/all-in-one:1.21 -//! ``` - -#![forbid(unused_imports)] - -mod config; -mod errors; -mod spans; - -pub use self::{ - config::{JaegerConfig, JaegerConfigBuilder}, - errors::JaegerError, - spans::{hash_to_trace_identifier, PerLeafSpan, Span, Stage}, -}; - -use self::spans::TraceIdentifier; - -use sp_core::traits::SpawnNamed; - -use parking_lot::RwLock; -use std::{ - result, - sync::{Arc, LazyLock}, -}; - -static INSTANCE: LazyLock> = LazyLock::new(|| RwLock::new(Jaeger::None)); - -/// Stateful convenience wrapper around [`mick_jaeger`]. -pub enum Jaeger { - /// Launched and operational state. - Launched { - /// [`mick_jaeger`] provided API to record spans to. - traces_in: Arc, - }, - /// Preparation state with the necessary config to launch the collector. - Prep(JaegerConfig), - /// Uninitialized, suggests wrong API usage if encountered. - None, -} - -impl Jaeger { - /// Spawn the jaeger instance. - pub fn new(cfg: JaegerConfig) -> Self { - Jaeger::Prep(cfg) - } - - /// Spawn the background task in order to send the tracing information out via UDP - #[cfg(target_os = "unknown")] - pub fn launch(self, _spawner: S) -> result::Result<(), JaegerError> { - Ok(()) - } - - /// Provide a no-thrills test setup helper. - #[cfg(test)] - pub fn test_setup() { - let mut instance = INSTANCE.write(); - match *instance { - Self::Launched { .. } => {}, - _ => { - let (traces_in, _traces_out) = mick_jaeger::init(mick_jaeger::Config { - service_name: "polkadot-jaeger-test".to_owned(), - }); - *instance = Self::Launched { traces_in }; - }, - } - } - - /// Spawn the background task in order to send the tracing information out via UDP - #[cfg(not(target_os = "unknown"))] - pub fn launch(self, spawner: S) -> result::Result<(), JaegerError> { - let cfg = match self { - Self::Prep(cfg) => Ok(cfg), - Self::Launched { .. } => return Err(JaegerError::AlreadyLaunched), - Self::None => Err(JaegerError::MissingConfiguration), - }?; - - let jaeger_agent = cfg.agent_addr; - - log::info!("🐹 Collecting jaeger spans for {:?}", &jaeger_agent); - - let (traces_in, mut traces_out) = mick_jaeger::init(mick_jaeger::Config { - service_name: format!("polkadot-{}", cfg.node_name), - }); - - // Spawn a background task that pulls span information and sends them on the network. - spawner.spawn( - "jaeger-collector", - Some("jaeger"), - Box::pin(async move { - match tokio::net::UdpSocket::bind("0.0.0.0:0").await { - Ok(udp_socket) => loop { - let buf = traces_out.next().await; - // UDP sending errors happen only either if the API is misused or in case of - // missing privilege. - if let Err(e) = udp_socket.send_to(&buf, jaeger_agent).await { - log::debug!(target: "jaeger", "UDP send error: {}", e); - } - }, - Err(e) => { - log::warn!(target: "jaeger", "UDP socket open error: {}", e); - }, - } - }), - ); - - *INSTANCE.write() = Self::Launched { traces_in }; - Ok(()) - } - - /// Create a span, but defer the evaluation/transformation into a `TraceIdentifier`. - /// - /// The deferral allows to avoid the additional CPU runtime cost in case of - /// items that are not a pre-computed hash by themselves. - pub(crate) fn span(&self, lazy_hash: F, span_name: &'static str) -> Option - where - F: Fn() -> TraceIdentifier, - { - if let Self::Launched { traces_in, .. } = self { - let ident = lazy_hash(); - let trace_id = std::num::NonZeroU128::new(ident)?; - Some(traces_in.span(trace_id, span_name)) - } else { - None - } - } -} diff --git a/polkadot/node/jaeger/src/spans.rs b/polkadot/node/jaeger/src/spans.rs deleted file mode 100644 index efc1a9f91d1..00000000000 --- a/polkadot/node/jaeger/src/spans.rs +++ /dev/null @@ -1,520 +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 . - -//! Polkadot Jaeger span definitions. -//! -//! ```rust -//! # use polkadot_primitives::{CandidateHash, Hash}; -//! # fn main() { -//! use polkadot_node_jaeger as jaeger; -//! -//! let relay_parent = Hash::default(); -//! let candidate = CandidateHash::default(); -//! -//! #[derive(Debug, Default)] -//! struct Foo { -//! a: u8, -//! b: u16, -//! c: u32, -//! }; -//! -//! let foo = Foo::default(); -//! -//! let span = -//! jaeger::Span::new(relay_parent, "root_of_aaall_spans") -//! // explicit well defined items -//! .with_candidate(candidate) -//! // anything that implements `trait std::fmt::Debug` -//! .with_string_fmt_debug_tag("foo", foo) -//! // anything that implements `trait std::str::ToString` -//! .with_string_tag("again", 1337_u32) -//! // add a `Stage` for [`dot-jaeger`](https://github.com/paritytech/dot-jaeger) -//! .with_stage(jaeger::Stage::CandidateBacking); -//! // complete by design, no completion required -//! # } -//! ``` -//! -//! In a few cases additional annotations might want to be added -//! over the course of a function, for this purpose use the non-consuming -//! `fn` variants, i.e. -//! ```rust -//! # use polkadot_primitives::{CandidateHash, Hash}; -//! # fn main() { -//! # use polkadot_node_jaeger as jaeger; -//! -//! # let relay_parent = Hash::default(); -//! # let candidate = CandidateHash::default(); -//! -//! # #[derive(Debug, Default)] -//! # struct Foo { -//! # a: u8, -//! # b: u16, -//! # c: u32, -//! # }; -//! # -//! # let foo = Foo::default(); -//! -//! let root_span = -//! jaeger::Span::new(relay_parent, "root_of_aaall_spans"); -//! -//! // the preferred way of adding additional delayed information: -//! let span = root_span.child("inner"); -//! -//! // ... more operations ... -//! -//! // but this is also possible: -//! -//! let mut root_span = root_span; -//! root_span.add_string_fmt_debug_tag("foo_constructed", &foo); -//! root_span.add_string_tag("bar", true); -//! # } -//! ``` - -use codec::Encode; -use polkadot_node_primitives::PoV; -use polkadot_primitives::{ - BlakeTwo256, CandidateHash, ChunkIndex, Hash, HashT, Id as ParaId, ValidatorIndex, -}; -use sc_network_types::PeerId; - -use std::{fmt, sync::Arc}; - -use super::INSTANCE; - -/// A special "per leaf span". -/// -/// Essentially this span wraps two spans: -/// -/// 1. The span that is created per leaf in the overseer. -/// 2. Some child span of the per-leaf span. -/// -/// This just works as auxiliary structure to easily store both. -#[derive(Debug)] -pub struct PerLeafSpan { - leaf_span: Arc, - span: Span, -} - -impl PerLeafSpan { - /// Creates a new instance. - /// - /// Takes the `leaf_span` that is created by the overseer per leaf and a name for a child span. - /// Both will be stored in this object, while the child span is implicitly accessible by using - /// the [`Deref`](std::ops::Deref) implementation. - pub fn new(leaf_span: Arc, name: &'static str) -> Self { - let span = leaf_span.child(name); - - Self { span, leaf_span } - } - - /// Returns the leaf span. - pub fn leaf_span(&self) -> &Arc { - &self.leaf_span - } -} - -/// Returns a reference to the child span. -impl std::ops::Deref for PerLeafSpan { - type Target = Span; - - fn deref(&self) -> &Span { - &self.span - } -} - -/// A helper to annotate the stage with a numerical value -/// to ease the life of the tooling team creating viable -/// statistical metrics for which stage of the inclusion -/// pipeline drops a significant amount of candidates, -/// statistically speaking. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -#[non_exhaustive] -pub enum Stage { - CandidateBacking = 2, - StatementDistribution = 3, - PoVDistribution = 4, - AvailabilityDistribution = 5, - AvailabilityRecovery = 6, - BitfieldDistribution = 7, - ApprovalChecking = 8, - ApprovalDistribution = 9, - // Expand as needed, numbers should be ascending according to the stage - // through the inclusion pipeline, or according to the descriptions - // in [the path of a para chain block] - // (https://polkadot.network/the-path-of-a-parachain-block/) - // see [issue](https://github.com/paritytech/polkadot/issues/2389) -} - -/// A wrapper type for a span. -/// -/// Handles running with and without jaeger. -pub enum Span { - /// Running with jaeger being enabled. - Enabled(mick_jaeger::Span), - /// Running with jaeger disabled. - Disabled, -} - -/// Alias for the 16 byte unique identifier used with jaeger. -pub(crate) type TraceIdentifier = u128; - -/// A helper to convert the hash to the fixed size representation -/// needed for jaeger. -#[inline] -pub fn hash_to_trace_identifier(hash: Hash) -> TraceIdentifier { - let mut buf = [0u8; 16]; - buf.copy_from_slice(&hash.as_ref()[0..16]); - // The slice bytes are copied in reading order, so if interpreted - // in string form by a human, that means lower indices have higher - // values and hence corresponds to BIG endian ordering of the individual - // bytes. - u128::from_be_bytes(buf) as TraceIdentifier -} - -/// Helper to unify lazy proxy evaluation. -pub trait LazyIdent { - /// Evaluate the type to a unique trace identifier. - /// Called lazily on demand. - fn eval(&self) -> TraceIdentifier; - - /// Annotate a new root item with these additional spans - /// at construction. - fn extra_tags(&self, _span: &mut Span) {} -} - -impl<'a> LazyIdent for &'a [u8] { - fn eval(&self) -> TraceIdentifier { - hash_to_trace_identifier(BlakeTwo256::hash_of(self)) - } -} - -impl LazyIdent for &PoV { - fn eval(&self) -> TraceIdentifier { - hash_to_trace_identifier(self.hash()) - } - - fn extra_tags(&self, span: &mut Span) { - span.add_pov(self) - } -} - -impl LazyIdent for Hash { - fn eval(&self) -> TraceIdentifier { - hash_to_trace_identifier(*self) - } - - fn extra_tags(&self, span: &mut Span) { - span.add_string_fmt_debug_tag("relay-parent", self); - } -} - -impl LazyIdent for &Hash { - fn eval(&self) -> TraceIdentifier { - hash_to_trace_identifier(**self) - } - - fn extra_tags(&self, span: &mut Span) { - span.add_string_fmt_debug_tag("relay-parent", self); - } -} - -impl LazyIdent for CandidateHash { - fn eval(&self) -> TraceIdentifier { - hash_to_trace_identifier(self.0) - } - - fn extra_tags(&self, span: &mut Span) { - span.add_string_fmt_debug_tag("candidate-hash", &self.0); - // A convenience for usage with the grafana tempo UI, - // not a technical requirement. It merely provides an easy anchor - // where the true trace identifier of the span is not based on - // a candidate hash (which it should be!), but is required to - // continue investigating. - span.add_string_tag("traceID", self.eval().to_string()); - } -} - -impl Span { - /// Creates a new span builder based on anything that can be lazily evaluated - /// to and identifier. - /// - /// Attention: The primary identifier will be used for identification - /// and as such should be - pub fn new(identifier: I, span_name: &'static str) -> Span { - let mut span = INSTANCE - .read_recursive() - .span(|| ::eval(&identifier), span_name) - .into(); - ::extra_tags(&identifier, &mut span); - span - } - - /// Creates a new span builder based on an encodable type. - /// The encoded bytes are then used to derive the true trace identifier. - pub fn from_encodable(identifier: I, span_name: &'static str) -> Span { - INSTANCE - .read_recursive() - .span( - move || { - let bytes = identifier.encode(); - LazyIdent::eval(&bytes.as_slice()) - }, - span_name, - ) - .into() - } - - /// Derive a child span from `self`. - pub fn child(&self, name: &str) -> Self { - match self { - Self::Enabled(inner) => Self::Enabled(inner.child(name)), - Self::Disabled => Self::Disabled, - } - } - - /// Attach a 'traceID' tag set to the decimal representation of the candidate hash. - #[inline(always)] - pub fn with_trace_id(mut self, candidate_hash: CandidateHash) -> Self { - self.add_string_tag("traceID", hash_to_trace_identifier(candidate_hash.0)); - self - } - - #[inline(always)] - pub fn with_string_tag(mut self, tag: &'static str, val: V) -> Self { - self.add_string_tag::(tag, val); - self - } - - /// Attach a peer-id tag to the span. - #[inline(always)] - pub fn with_peer_id(self, peer: &PeerId) -> Self { - self.with_string_tag("peer-id", &peer.to_base58()) - } - - /// Attach a `peer-id` tag to the span when peer is present. - #[inline(always)] - pub fn with_optional_peer_id(self, peer: Option<&PeerId>) -> Self { - if let Some(peer) = peer { - self.with_peer_id(peer) - } else { - self - } - } - - /// Attach a candidate hash to the span. - #[inline(always)] - pub fn with_candidate(self, candidate_hash: CandidateHash) -> Self { - self.with_string_fmt_debug_tag("candidate-hash", &candidate_hash.0) - } - - /// Attach a para-id to the span. - #[inline(always)] - pub fn with_para_id(self, para_id: ParaId) -> Self { - self.with_int_tag("para-id", u32::from(para_id) as i64) - } - - /// Attach a candidate stage. - /// Should always come with a `CandidateHash`. - #[inline(always)] - pub fn with_stage(self, stage: Stage) -> Self { - self.with_string_tag("candidate-stage", stage as u8) - } - - #[inline(always)] - pub fn with_validator_index(self, validator: ValidatorIndex) -> Self { - self.with_string_tag("validator-index", &validator.0) - } - - #[inline(always)] - pub fn with_chunk_index(self, chunk_index: ChunkIndex) -> Self { - self.with_string_tag("chunk-index", &chunk_index.0) - } - - #[inline(always)] - pub fn with_relay_parent(self, relay_parent: Hash) -> Self { - self.with_string_fmt_debug_tag("relay-parent", relay_parent) - } - - #[inline(always)] - pub fn with_claimed_validator_index(self, claimed_validator_index: ValidatorIndex) -> Self { - self.with_string_tag("claimed-validator", &claimed_validator_index.0) - } - - #[inline(always)] - pub fn with_pov(mut self, pov: &PoV) -> Self { - self.add_pov(pov); - self - } - - /// Add an additional int tag to the span without consuming. - /// - /// Should be used sparingly, introduction of new types is preferred. - #[inline(always)] - pub fn with_int_tag(mut self, tag: &'static str, i: i64) -> Self { - self.add_int_tag(tag, i); - self - } - - #[inline(always)] - pub fn with_uint_tag(mut self, tag: &'static str, u: u64) -> Self { - self.add_uint_tag(tag, u); - self - } - - #[inline(always)] - pub fn with_string_fmt_debug_tag(mut self, tag: &'static str, val: V) -> Self { - self.add_string_tag(tag, format!("{:?}", val)); - self - } - - /// Adds the `FollowsFrom` relationship to this span with respect to the given one. - #[inline(always)] - pub fn add_follows_from(&mut self, other: &Self) { - match (self, other) { - (Self::Enabled(ref mut inner), Self::Enabled(ref other_inner)) => - inner.add_follows_from(&other_inner), - _ => {}, - } - } - - /// Add a PoV hash meta tag with lazy hash evaluation, without consuming the span. - #[inline(always)] - pub fn add_pov(&mut self, pov: &PoV) { - if self.is_enabled() { - // avoid computing the PoV hash if jaeger is not enabled - self.add_string_fmt_debug_tag("pov", pov.hash()); - } - } - - #[inline(always)] - pub fn add_para_id(&mut self, para_id: ParaId) { - self.add_int_tag("para-id", u32::from(para_id) as i64); - } - - /// Add a string tag, without consuming the span. - pub fn add_string_tag(&mut self, tag: &'static str, val: V) { - match self { - Self::Enabled(ref mut inner) => inner.add_string_tag(tag, val.to_string().as_str()), - Self::Disabled => {}, - } - } - - /// Add a string tag, without consuming the span. - pub fn add_string_fmt_debug_tag(&mut self, tag: &'static str, val: V) { - match self { - Self::Enabled(ref mut inner) => - inner.add_string_tag(tag, format!("{:?}", val).as_str()), - Self::Disabled => {}, - } - } - - pub fn add_int_tag(&mut self, tag: &'static str, value: i64) { - match self { - Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value), - Self::Disabled => {}, - } - } - - pub fn add_uint_tag(&mut self, tag: &'static str, value: u64) { - match self { - Self::Enabled(ref mut inner) => inner.add_int_tag(tag, value as i64), - Self::Disabled => {}, - } - } - - /// Check whether jaeger is enabled - /// in order to avoid computational overhead. - pub const fn is_enabled(&self) -> bool { - match self { - Span::Enabled(_) => true, - _ => false, - } - } - - /// Obtain the trace identifier for this set of spans. - pub fn trace_id(&self) -> Option { - match self { - Span::Enabled(inner) => Some(inner.trace_id().get()), - _ => None, - } - } -} - -impl std::fmt::Debug for Span { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "") - } -} - -impl From> for Span { - fn from(src: Option) -> Self { - if let Some(span) = src { - Self::Enabled(span) - } else { - Self::Disabled - } - } -} - -impl From for Span { - fn from(src: mick_jaeger::Span) -> Self { - Self::Enabled(src) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Jaeger; - - // make sure to not use `::repeat_*()` based samples, since this does not verify endianness - const RAW: [u8; 32] = [ - 0xFF, 0xAA, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x78, 0x89, 0x9A, 0xAB, 0xBC, 0xCD, 0xDE, - 0xEF, 0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, - 0x0E, 0x0F, - ]; - - #[test] - fn hash_derived_identifier_is_leading_16bytes() { - let candidate_hash = dbg!(Hash::from(&RAW)); - let trace_id = dbg!(hash_to_trace_identifier(candidate_hash)); - for (idx, (a, b)) in candidate_hash - .as_bytes() - .iter() - .take(16) - .zip(trace_id.to_be_bytes().iter()) - .enumerate() - { - assert_eq!(*a, *b, "Index [{}] does not match: {} != {}", idx, a, b); - } - } - - #[test] - fn extra_tags_do_not_change_trace_id() { - Jaeger::test_setup(); - let candidate_hash = dbg!(Hash::from(&RAW)); - let trace_id = hash_to_trace_identifier(candidate_hash); - - let span = Span::new(candidate_hash, "foo"); - - assert_eq!(span.trace_id(), Some(trace_id)); - - let span = span.with_int_tag("tag", 7i64); - - assert_eq!(span.trace_id(), Some(trace_id)); - } -} diff --git a/polkadot/node/network/availability-distribution/src/lib.rs b/polkadot/node/network/availability-distribution/src/lib.rs index d3185e0af80..43845381497 100644 --- a/polkadot/node/network/availability-distribution/src/lib.rs +++ b/polkadot/node/network/availability-distribution/src/lib.rs @@ -22,11 +22,9 @@ use polkadot_node_network_protocol::request_response::{ v1, v2, IncomingRequestReceiver, ReqProtocolNames, }; use polkadot_node_subsystem::{ - jaeger, messages::AvailabilityDistributionMessage, overseer, FromOrchestra, OverseerSignal, + messages::AvailabilityDistributionMessage, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, }; -use polkadot_primitives::{BlockNumber, Hash}; -use std::collections::HashMap; /// Error and [`Result`] type for this subsystem. mod error; @@ -104,7 +102,6 @@ impl AvailabilityDistributionSubsystem { /// Start processing work as passed on from the Overseer. async fn run(self, mut ctx: Context) -> std::result::Result<(), FatalError> { let Self { mut runtime, recvs, metrics, req_protocol_names } = self; - let mut spans: HashMap = HashMap::new(); let IncomingRequestReceivers { pov_req_receiver, @@ -156,24 +153,16 @@ impl AvailabilityDistributionSubsystem { }; match message { FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => { - let cloned_leaf = match update.activated.clone() { - Some(activated) => activated, - None => continue, - }; - let span = - jaeger::PerLeafSpan::new(cloned_leaf.span, "availability-distribution"); - spans.insert(cloned_leaf.hash, (cloned_leaf.number, span)); log_error( requester .get_mut() - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await, "Error in Requester::update_fetching_heads", &mut warn_freq, )?; }, - FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, finalized_number)) => { - spans.retain(|_hash, (block_number, _span)| *block_number > finalized_number); + FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, _finalized_number)) => { }, FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()), FromOrchestra::Communication { @@ -187,15 +176,6 @@ impl AvailabilityDistributionSubsystem { tx, }, } => { - let span = spans - .get(&relay_parent) - .map(|(_, span)| span.child("fetch-pov")) - .unwrap_or_else(|| jaeger::Span::new(&relay_parent, "fetch-pov")) - .with_trace_id(candidate_hash) - .with_candidate(candidate_hash) - .with_relay_parent(relay_parent) - .with_stage(jaeger::Stage::AvailabilityDistribution); - log_error( pov_requester::fetch_pov( &mut ctx, @@ -207,7 +187,6 @@ impl AvailabilityDistributionSubsystem { pov_hash, tx, metrics.clone(), - &span, ) .await, "pov_requester::fetch_pov", diff --git a/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs b/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs index 6c632fa7efe..5e26ae4b7a7 100644 --- a/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs @@ -25,7 +25,6 @@ use polkadot_node_network_protocol::request_response::{ }; use polkadot_node_primitives::PoV; use polkadot_node_subsystem::{ - jaeger, messages::{IfDisconnected, NetworkBridgeTxMessage}, overseer, }; @@ -52,18 +51,7 @@ pub async fn fetch_pov( pov_hash: Hash, tx: oneshot::Sender, metrics: Metrics, - span: &jaeger::Span, ) -> Result<()> { - let _span = span - .child("fetch-pov") - .with_trace_id(candidate_hash) - .with_validator_index(from_validator) - .with_candidate(candidate_hash) - .with_para_id(para_id) - .with_relay_parent(parent) - .with_string_tag("pov-hash", format!("{:?}", pov_hash)) - .with_stage(jaeger::Stage::AvailabilityDistribution); - let info = &runtime.get_session_info(ctx.sender(), parent).await?.session_info; let authority_id = info .discovery_keys @@ -189,7 +177,6 @@ mod tests { pov_hash, tx, Metrics::new_dummy(), - &jaeger::Span::Disabled, ) .await .expect("Should succeed"); diff --git a/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs b/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs index 278608cc858..5be6f2d223a 100644 --- a/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs +++ b/polkadot/node/network/availability-distribution/src/requester/fetch_task/mod.rs @@ -31,7 +31,6 @@ use polkadot_node_network_protocol::request_response::{ }; use polkadot_node_primitives::ErasureChunk; use polkadot_node_subsystem::{ - jaeger, messages::{AvailabilityStoreMessage, IfDisconnected, NetworkBridgeTxMessage}, overseer, }; @@ -129,9 +128,6 @@ struct RunningTask { /// Prometheus metrics for reporting results. metrics: Metrics, - /// Span tracking the fetching of this chunk. - span: jaeger::Span, - /// Expected chunk index. We'll validate that the remote did send us the correct chunk (only /// important for v2 requests). chunk_index: ChunkIndex, @@ -154,21 +150,9 @@ impl FetchTaskConfig { metrics: Metrics, session_info: &SessionInfo, chunk_index: ChunkIndex, - span: jaeger::Span, req_v1_protocol_name: ProtocolName, req_v2_protocol_name: ProtocolName, ) -> Self { - let span = span - .child("fetch-task-config") - .with_trace_id(core.candidate_hash) - .with_string_tag("leaf", format!("{:?}", leaf)) - .with_validator_index(session_info.our_index) - .with_chunk_index(chunk_index) - .with_uint_tag("group-index", core.group_responsible.0 as u64) - .with_relay_parent(core.candidate_descriptor.relay_parent) - .with_string_tag("pov-hash", format!("{:?}", core.candidate_descriptor.pov_hash)) - .with_stage(jaeger::Stage::AvailabilityDistribution); - let live_in = vec![leaf].into_iter().collect(); // Don't run tasks for our backing group: @@ -190,7 +174,6 @@ impl FetchTaskConfig { relay_parent: core.candidate_descriptor.relay_parent, metrics, sender, - span, chunk_index, req_v1_protocol_name, req_v2_protocol_name @@ -279,7 +262,6 @@ impl RunningTask { let mut bad_validators = Vec::new(); let mut succeeded = false; let mut count: u32 = 0; - let mut span = self.span.child("run-fetch-chunk-task").with_relay_parent(self.relay_parent); let mut network_error_freq = gum::Freq::new(); let mut canceled_freq = gum::Freq::new(); // Try validators in reverse order: @@ -289,11 +271,7 @@ impl RunningTask { self.metrics.on_retry(); } count += 1; - let _chunk_fetch_span = span - .child("fetch-chunk-request") - .with_validator_index(self.request.index) - .with_chunk_index(self.chunk_index) - .with_stage(jaeger::Stage::AvailabilityDistribution); + // Send request: let resp = match self .do_request(&validator, &mut network_error_freq, &mut canceled_freq) @@ -313,13 +291,7 @@ impl RunningTask { continue }, }; - // We drop the span here, so that the span is not active while we recombine the chunk. - drop(_chunk_fetch_span); - let _chunk_recombine_span = span - .child("recombine-chunk") - .with_validator_index(self.request.index) - .with_chunk_index(self.chunk_index) - .with_stage(jaeger::Stage::AvailabilityDistribution); + let chunk = match resp { Some(chunk) => chunk, None => { @@ -337,14 +309,6 @@ impl RunningTask { continue }, }; - // We drop the span so that the span is not active whilst we validate and store the - // chunk. - drop(_chunk_recombine_span); - let _chunk_validate_and_store_span = span - .child("validate-and-store-chunk") - .with_validator_index(self.request.index) - .with_chunk_index(self.chunk_index) - .with_stage(jaeger::Stage::AvailabilityDistribution); // Data genuine? if !self.validate_chunk(&validator, &chunk, self.chunk_index) { @@ -357,7 +321,6 @@ impl RunningTask { succeeded = true; break } - span.add_int_tag("tries", count as _); if succeeded { self.metrics.on_fetch(SUCCEEDED); self.conclude(bad_validators).await; diff --git a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs index 2cd4bf29a56..9d4ac5bc4b1 100644 --- a/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs +++ b/polkadot/node/network/availability-distribution/src/requester/fetch_task/tests.rs @@ -365,7 +365,6 @@ fn get_test_running_task( relay_parent: Hash::repeat_byte(71), sender: tx, metrics: Metrics::new_dummy(), - span: jaeger::Span::Disabled, req_v1_protocol_name: req_protocol_names.get_name(Protocol::ChunkFetchingV1), req_v2_protocol_name: req_protocol_names.get_name(Protocol::ChunkFetchingV2), chunk_index, diff --git a/polkadot/node/network/availability-distribution/src/requester/mod.rs b/polkadot/node/network/availability-distribution/src/requester/mod.rs index 0175161af70..23382503272 100644 --- a/polkadot/node/network/availability-distribution/src/requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/requester/mod.rs @@ -31,7 +31,6 @@ use futures::{ use polkadot_node_network_protocol::request_response::{v1, v2, IsRequest, ReqProtocolNames}; use polkadot_node_subsystem::{ - jaeger, messages::{ChainApiMessage, RuntimeApiMessage}, overseer, ActivatedLeaf, ActiveLeavesUpdate, }; @@ -39,9 +38,7 @@ use polkadot_node_subsystem_util::{ availability_chunks::availability_chunk_index, runtime::{get_occupied_cores, RuntimeInfo}, }; -use polkadot_primitives::{ - BlockNumber, CandidateHash, CoreIndex, Hash, OccupiedCore, SessionIndex, -}; +use polkadot_primitives::{CandidateHash, CoreIndex, Hash, OccupiedCore, SessionIndex}; use super::{FatalError, Metrics, Result, LOG_TARGET}; @@ -114,21 +111,13 @@ impl Requester { ctx: &mut Context, runtime: &mut RuntimeInfo, update: ActiveLeavesUpdate, - spans: &HashMap, ) -> Result<()> { gum::trace!(target: LOG_TARGET, ?update, "Update fetching heads"); let ActiveLeavesUpdate { activated, deactivated } = update; if let Some(leaf) = activated { - let span = spans - .get(&leaf.hash) - .map(|(_, span)| span.child("update-fetching-heads")) - .unwrap_or_else(|| jaeger::Span::new(&leaf.hash, "update-fetching-heads")) - .with_string_tag("leaf", format!("{:?}", leaf.hash)) - .with_stage(jaeger::Stage::AvailabilityDistribution); - // Order important! We need to handle activated, prior to deactivated, otherwise we // might cancel still needed jobs. - self.start_requesting_chunks(ctx, runtime, leaf, &span).await?; + self.start_requesting_chunks(ctx, runtime, leaf).await?; } self.stop_requesting_chunks(deactivated.into_iter()); @@ -144,13 +133,7 @@ impl Requester { ctx: &mut Context, runtime: &mut RuntimeInfo, new_head: ActivatedLeaf, - span: &jaeger::Span, ) -> Result<()> { - let mut span = span - .child("request-chunks-new-head") - .with_string_tag("leaf", format!("{:?}", new_head.hash)) - .with_stage(jaeger::Stage::AvailabilityDistribution); - let sender = &mut ctx.sender().clone(); let ActivatedLeaf { hash: leaf, .. } = new_head; let (leaf_session_index, ancestors_in_session) = get_block_ancestors_in_same_session( @@ -160,15 +143,9 @@ impl Requester { Self::LEAF_ANCESTRY_LEN_WITHIN_SESSION, ) .await?; - span.add_uint_tag("ancestors-in-session", ancestors_in_session.len() as u64); // Also spawn or bump tasks for candidates in ancestry in the same session. for hash in std::iter::once(leaf).chain(ancestors_in_session) { - let span = span - .child("request-chunks-ancestor") - .with_string_tag("leaf", format!("{:?}", hash.clone())) - .with_stage(jaeger::Stage::AvailabilityDistribution); - let cores = get_occupied_cores(sender, hash).await?; gum::trace!( target: LOG_TARGET, @@ -182,7 +159,7 @@ impl Requester { // The next time the subsystem receives leaf update, some of spawned task will be bumped // to be live in fresh relay parent, while some might get dropped due to the current // leaf being deactivated. - self.add_cores(ctx, runtime, leaf, leaf_session_index, cores, span).await?; + self.add_cores(ctx, runtime, leaf, leaf_session_index, cores).await?; } Ok(()) @@ -211,22 +188,12 @@ impl Requester { leaf: Hash, leaf_session_index: SessionIndex, cores: impl IntoIterator, - span: jaeger::Span, ) -> Result<()> { for (core_index, core) in cores { - let mut span = span - .child("check-fetch-candidate") - .with_trace_id(core.candidate_hash) - .with_string_tag("leaf", format!("{:?}", leaf)) - .with_candidate(core.candidate_hash) - .with_stage(jaeger::Stage::AvailabilityDistribution); - if let Some(e) = self.fetches.get_mut(&core.candidate_hash) { // Just book keeping - we are already requesting that chunk: - span.add_string_tag("already-requested-chunk", "true"); e.add_leaf(leaf); } else { - span.add_string_tag("already-requested-chunk", "false"); let tx = self.tx.clone(); let metrics = self.metrics.clone(); @@ -272,7 +239,6 @@ impl Requester { metrics, session_info, chunk_index, - span, self.req_protocol_names.get_name(v1::ChunkFetchingRequest::PROTOCOL), self.req_protocol_names.get_name(v2::ChunkFetchingRequest::PROTOCOL), ); diff --git a/polkadot/node/network/availability-distribution/src/requester/tests.rs b/polkadot/node/network/availability-distribution/src/requester/tests.rs index decb3156004..021f6da7e2e 100644 --- a/polkadot/node/network/availability-distribution/src/requester/tests.rs +++ b/polkadot/node/network/availability-distribution/src/requester/tests.rs @@ -15,9 +15,9 @@ // along with Polkadot. If not, see . use futures::FutureExt; -use std::{collections::HashMap, future::Future}; +use std::future::Future; -use polkadot_node_network_protocol::{jaeger, request_response::ReqProtocolNames}; +use polkadot_node_network_protocol::request_response::ReqProtocolNames; use polkadot_node_primitives::{BlockData, ErasureChunk, PoV}; use polkadot_node_subsystem_util::runtime::RuntimeInfo; use polkadot_primitives::{ @@ -208,7 +208,6 @@ fn check_ancestry_lookup_in_same_session() { test_harness(test_state.clone(), |mut ctx| async move { let chain = &test_state.relay_chain; - let spans: HashMap = HashMap::new(); let block_number = 1; let update = ActiveLeavesUpdate { activated: Some(new_leaf(chain[block_number], block_number as u32)), @@ -216,7 +215,7 @@ fn check_ancestry_lookup_in_same_session() { }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; @@ -231,7 +230,7 @@ fn check_ancestry_lookup_in_same_session() { }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; @@ -252,7 +251,7 @@ fn check_ancestry_lookup_in_same_session() { deactivated: vec![chain[1], chain[2]].into(), }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; @@ -281,7 +280,6 @@ fn check_ancestry_lookup_in_different_sessions() { test_harness(test_state.clone(), |mut ctx| async move { let chain = &test_state.relay_chain; - let spans: HashMap = HashMap::new(); let block_number = 3; let update = ActiveLeavesUpdate { activated: Some(new_leaf(chain[block_number], block_number as u32)), @@ -289,7 +287,7 @@ fn check_ancestry_lookup_in_different_sessions() { }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; @@ -302,7 +300,7 @@ fn check_ancestry_lookup_in_different_sessions() { }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; @@ -315,7 +313,7 @@ fn check_ancestry_lookup_in_different_sessions() { }; requester - .update_fetching_heads(&mut ctx, &mut runtime, update, &spans) + .update_fetching_heads(&mut ctx, &mut runtime, update) .await .expect("Leaf processing failed"); let fetch_tasks = &requester.fetches; diff --git a/polkadot/node/network/availability-distribution/src/responder.rs b/polkadot/node/network/availability-distribution/src/responder.rs index fb08c471250..6512fcb7f65 100644 --- a/polkadot/node/network/availability-distribution/src/responder.rs +++ b/polkadot/node/network/availability-distribution/src/responder.rs @@ -27,7 +27,7 @@ use polkadot_node_network_protocol::{ UnifiedReputationChange as Rep, }; use polkadot_node_primitives::{AvailableData, ErasureChunk}; -use polkadot_node_subsystem::{jaeger, messages::AvailabilityStoreMessage, SubsystemSender}; +use polkadot_node_subsystem::{messages::AvailabilityStoreMessage, SubsystemSender}; use polkadot_primitives::{CandidateHash, ValidatorIndex}; use crate::{ @@ -193,8 +193,6 @@ pub async fn answer_pov_request( where Sender: SubsystemSender, { - let _span = jaeger::Span::new(req.payload.candidate_hash, "answer-pov-request"); - let av_data = query_available_data(sender, req.payload.candidate_hash).await?; let result = av_data.is_some(); @@ -228,12 +226,6 @@ where // V1 and V2 requests have the same payload, so decoding into either one will work. It's the // responses that differ, hence the `MakeResp` generic. let payload: v1::ChunkFetchingRequest = req.payload.into(); - let span = jaeger::Span::new(payload.candidate_hash, "answer-chunk-request"); - - let _child_span = span - .child("answer-chunk-request") - .with_trace_id(payload.candidate_hash) - .with_validator_index(payload.index); let chunk = query_chunk(sender, payload.candidate_hash, payload.index).await?; diff --git a/polkadot/node/network/availability-recovery/src/lib.rs b/polkadot/node/network/availability-recovery/src/lib.rs index 167125f987a..114faa2859c 100644 --- a/polkadot/node/network/availability-recovery/src/lib.rs +++ b/polkadot/node/network/availability-recovery/src/lib.rs @@ -57,7 +57,6 @@ use polkadot_node_network_protocol::{ use polkadot_node_primitives::AvailableData; use polkadot_node_subsystem::{ errors::RecoveryError, - jaeger, messages::{AvailabilityRecoveryMessage, AvailabilityStoreMessage}, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError, @@ -387,9 +386,6 @@ async fn handle_recover( ) -> Result<()> { let candidate_hash = receipt.hash(); - let span = jaeger::Span::new(candidate_hash, "availability-recovery") - .with_stage(jaeger::Stage::AvailabilityRecovery); - if let Some(result) = state.availability_lru.get(&candidate_hash).cloned().map(|v| v.into_result()) { @@ -403,13 +399,11 @@ async fn handle_recover( return Ok(()) } - let _span = span.child("not-cached"); let session_info_res = state .runtime_info .get_session_info_by_index(ctx.sender(), state.live_block.1, session_index) .await; - let _span = span.child("session-info-ctx-received"); match session_info_res { Ok(ExtendedSessionInfo { session_info, node_features, .. }) => { let mut backer_group = None; diff --git a/polkadot/node/network/bitfield-distribution/src/lib.rs b/polkadot/node/network/bitfield-distribution/src/lib.rs index 029401e0bd5..3003f970a64 100644 --- a/polkadot/node/network/bitfield-distribution/src/lib.rs +++ b/polkadot/node/network/bitfield-distribution/src/lib.rs @@ -36,8 +36,8 @@ use polkadot_node_network_protocol::{ UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_subsystem::{ - jaeger, messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, PerLeafSpan, - SpawnedSubsystem, SubsystemError, SubsystemResult, + messages::*, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, + SubsystemError, SubsystemResult, }; use polkadot_node_subsystem_util::{ self as util, @@ -177,22 +177,14 @@ struct PerRelayParentData { /// Track messages that were already received by a peer /// to prevent flooding. message_received_from_peer: HashMap>, - - /// The span for this leaf/relay parent. - span: PerLeafSpan, } impl PerRelayParentData { /// Create a new instance. - fn new( - signing_context: SigningContext, - validator_set: Vec, - span: PerLeafSpan, - ) -> Self { + fn new(signing_context: SigningContext, validator_set: Vec) -> Self { Self { signing_context, validator_set, - span, one_per_validator: Default::default(), message_sent_to_peer: Default::default(), message_received_from_peer: Default::default(), @@ -304,8 +296,6 @@ impl BitfieldDistribution { let relay_parent = activated.hash; gum::trace!(target: LOG_TARGET, ?relay_parent, "activated"); - let span = PerLeafSpan::new(activated.span, "bitfield-distribution"); - let _span = span.child("query-basics"); // query validator set and signing context per relay_parent once only match query_basics(&mut ctx, relay_parent).await { @@ -317,7 +307,7 @@ impl BitfieldDistribution { // us anything to do with this relay-parent anyway. let _ = state.per_relay_parent.insert( relay_parent, - PerRelayParentData::new(signing_context, validator_set, span), + PerRelayParentData::new(signing_context, validator_set), ); }, Err(err) => { @@ -430,9 +420,7 @@ async fn relay_message( rng: &mut (impl CryptoRng + Rng), ) { let relay_parent = message.relay_parent; - let span = job_data.span.child("relay-msg"); - let _span = span.child("provisionable"); // notify the overseer about a new and valid signed bitfield ctx.send_message(ProvisionerMessage::ProvisionableData( relay_parent, @@ -440,11 +428,9 @@ async fn relay_message( )) .await; - drop(_span); let total_peers = peers.len(); let mut random_routing: RandomRouting = Default::default(); - let _span = span.child("interested-peers"); // pass on the bitfield distribution to all interested peers let interested_peers = peers .iter() @@ -487,8 +473,6 @@ async fn relay_message( .insert(validator.clone()); }); - drop(_span); - if interested_peers.is_empty() { gum::trace!( target: LOG_TARGET, @@ -496,8 +480,6 @@ async fn relay_message( "no peers are interested in gossip for relay parent", ); } else { - let _span = span.child("gossip"); - let v1_interested_peers = filter_by_peer_version(&interested_peers, ValidationVersion::V1.into()); let v2_interested_peers = @@ -594,14 +576,6 @@ async fn process_incoming_peer_message( let validator_index = bitfield.unchecked_validator_index(); - let mut _span = job_data - .span - .child("msg-received") - .with_peer_id(&origin) - .with_relay_parent(relay_parent) - .with_claimed_validator_index(validator_index) - .with_stage(jaeger::Stage::BitfieldDistribution); - let validator_set = &job_data.validator_set; if validator_set.is_empty() { gum::trace!(target: LOG_TARGET, ?relay_parent, ?origin, "Validator set is empty",); @@ -914,7 +888,6 @@ async fn send_tracked_gossip_message( return }; - let _span = job_data.span.child("gossip"); gum::trace!( target: LOG_TARGET, ?dest, diff --git a/polkadot/node/network/bitfield-distribution/src/tests.rs b/polkadot/node/network/bitfield-distribution/src/tests.rs index 4ed4bf6b38c..66a3c3f7090 100644 --- a/polkadot/node/network/bitfield-distribution/src/tests.rs +++ b/polkadot/node/network/bitfield-distribution/src/tests.rs @@ -25,11 +25,7 @@ use polkadot_node_network_protocol::{ peer_set::ValidationVersion, view, ObservedRole, }; -use polkadot_node_subsystem::{ - jaeger, - jaeger::{PerLeafSpan, Span}, - messages::ReportPeerMessage, -}; +use polkadot_node_subsystem::messages::ReportPeerMessage; use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{AvailabilityBitfield, Signed, ValidatorIndex}; @@ -86,7 +82,6 @@ fn prewarmed_state( }, message_received_from_peer: hashmap!{}, message_sent_to_peer: hashmap!{}, - span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), }, }, peer_data: peers @@ -124,7 +119,6 @@ fn state_with_view( one_per_validator: hashmap! {}, message_received_from_peer: hashmap! {}, message_sent_to_peer: hashmap! {}, - span: PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), }, ) }) @@ -1024,11 +1018,7 @@ fn need_message_works() { let validator_set = Vec::from_iter(validators.iter().map(|k| ValidatorId::from(k.public()))); let signing_context = SigningContext { session_index: 1, parent_hash: Hash::repeat_byte(0x00) }; - let mut state = PerRelayParentData::new( - signing_context, - validator_set.clone(), - PerLeafSpan::new(Arc::new(Span::Disabled), "foo"), - ); + let mut state = PerRelayParentData::new(signing_context, validator_set.clone()); let peer_a = PeerId::random(); let peer_b = PeerId::random(); diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 9a2357f02d8..bb99536f783 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -986,7 +986,7 @@ fn update_our_view( }; let our_view = OurView::new( - live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| (a.hash, a.span)), + live_heads.iter().take(MAX_VIEW_HEADS).cloned().map(|a| a.hash), finalized_number, ); diff --git a/polkadot/node/network/bridge/src/rx/tests.rs b/polkadot/node/network/bridge/src/rx/tests.rs index a96817eb254..e3f2715ef2b 100644 --- a/polkadot/node/network/bridge/src/rx/tests.rs +++ b/polkadot/node/network/bridge/src/rx/tests.rs @@ -16,7 +16,6 @@ use super::*; use futures::{channel::oneshot, executor}; -use overseer::jaeger; use polkadot_node_network_protocol::{self as net_protocol, OurView}; use polkadot_node_subsystem::messages::NetworkBridgeEvent; @@ -1381,12 +1380,7 @@ fn our_view_updates_decreasing_order_and_limited_to_max() { } let our_views = (1..=MAX_VIEW_HEADS).rev().map(|start| { - OurView::new( - (start..=MAX_VIEW_HEADS) - .rev() - .map(|i| (Hash::repeat_byte(i as u8), Arc::new(jaeger::Span::Disabled))), - 0, - ) + OurView::new((start..=MAX_VIEW_HEADS).rev().map(|i| Hash::repeat_byte(i as u8)), 0) }); for our_view in our_views { diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 97bc66d6058..af9beb535f4 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -38,12 +38,11 @@ use polkadot_node_network_protocol::{ }; use polkadot_node_primitives::{CollationSecondedSignal, PoV, Statement}; use polkadot_node_subsystem::{ - jaeger, messages::{ CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, RuntimeApiMessage, }, - overseer, FromOrchestra, OverseerSignal, PerLeafSpan, + overseer, FromOrchestra, OverseerSignal, }; use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, @@ -284,9 +283,6 @@ struct State { /// our view, including both leaves and implicit ancestry. per_relay_parent: HashMap, - /// Span per relay parent. - span_per_relay_parent: HashMap, - /// The result senders per collation. collation_result_senders: HashMap>, @@ -345,7 +341,6 @@ impl State { implicit_view: None, active_leaves: Default::default(), per_relay_parent: Default::default(), - span_per_relay_parent: Default::default(), collation_result_senders: Default::default(), peer_ids: Default::default(), validator_groups_buf: ValidatorGroupsBuffer::with_capacity(VALIDATORS_BUFFER_CAPACITY), @@ -854,12 +849,6 @@ async fn process_msg( result_sender, core_index, } => { - let _span1 = state - .span_per_relay_parent - .get(&candidate_receipt.descriptor.relay_parent) - .map(|s| s.child("distributing-collation")); - let _span2 = jaeger::Span::new(&pov, "distributing-collation"); - match state.collating_on { Some(id) if candidate_receipt.descriptor.para_id != id => { // If the ParaId of a collation requested to be distributed does not match @@ -1088,11 +1077,6 @@ async fn handle_incoming_request( let peer_id = req.peer_id(); let para_id = req.para_id(); - let _span = state - .span_per_relay_parent - .get(&relay_parent) - .map(|s| s.child("request-collation")); - match state.collating_on { Some(our_para_id) if our_para_id == para_id => { let per_relay_parent = match state.per_relay_parent.get_mut(&relay_parent) { @@ -1147,8 +1131,6 @@ async fn handle_incoming_request( state.metrics.on_collation_sent_requested(); - let _span = _span.as_ref().map(|s| s.child("sending")); - let waiting = state.waiting_collation_fetches.entry(relay_parent).or_default(); let candidate_hash = receipt.hash(); @@ -1359,11 +1341,6 @@ async fn handle_our_view_change( for leaf in added { let mode = prospective_parachains_mode(ctx.sender(), *leaf).await?; - if let Some(span) = view.span_per_head().get(leaf).cloned() { - let per_leaf_span = PerLeafSpan::new(span, "collator-side"); - state.span_per_relay_parent.insert(*leaf, per_leaf_span); - } - state.active_leaves.insert(*leaf, mode); state.per_relay_parent.insert(*leaf, PerRelayParent::new(mode)); @@ -1464,7 +1441,6 @@ async fn handle_our_view_change( ), } } - state.span_per_relay_parent.remove(removed); state.waiting_collation_fetches.remove(removed); } } diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs index 2f4c768b89e..23954f8d781 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs @@ -18,7 +18,6 @@ use super::*; use std::{ collections::{BTreeMap, HashSet, VecDeque}, - sync::Arc, time::Duration, }; @@ -42,7 +41,6 @@ use polkadot_node_network_protocol::{ use polkadot_node_primitives::BlockData; use polkadot_node_subsystem::{ errors::RuntimeApiError, - jaeger, messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, ActiveLeavesUpdate, }; diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index d3eae9dbba6..348feb9dd1d 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -36,8 +36,7 @@ async fn update_view( ) { let new_view: HashMap = HashMap::from_iter(new_view); - let our_view = - OurView::new(new_view.keys().map(|hash| (*hash, Arc::new(jaeger::Span::Disabled))), 0); + let our_view = OurView::new(new_view.keys().map(|hash| *hash), 0); overseer_send( virtual_overseer, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index 96ffe9f13db..58d9ebc5772 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -36,7 +36,6 @@ use polkadot_node_network_protocol::{ PeerId, }; use polkadot_node_primitives::PoV; -use polkadot_node_subsystem::jaeger; use polkadot_node_subsystem_util::{ metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode, }; @@ -319,8 +318,6 @@ pub(super) struct CollationFetchRequest { pub from_collator: BoxFuture<'static, OutgoingResult>, /// Handle used for checking if this request was cancelled. pub cancellation_token: CancellationToken, - /// A jaeger span corresponding to the lifetime of the request. - pub span: Option, /// A metric histogram for the lifetime of the request pub _lifetime_timer: Option, } @@ -339,7 +336,6 @@ impl Future for CollationFetchRequest { }; if cancelled { - self.span.as_mut().map(|s| s.add_string_tag("success", "false")); return Poll::Ready(( CollationEvent { collator_protocol_version: self.collator_protocol_version, @@ -361,16 +357,6 @@ impl Future for CollationFetchRequest { ) }); - match &res { - Poll::Ready((_, Ok(_))) => { - self.span.as_mut().map(|s| s.add_string_tag("success", "true")); - }, - Poll::Ready((_, Err(_))) => { - self.span.as_mut().map(|s| s.add_string_tag("success", "false")); - }, - _ => {}, - }; - res } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index cbf00a9e119..deb6ce03f43 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -39,13 +39,12 @@ use polkadot_node_network_protocol::{ }; use polkadot_node_primitives::{SignedFullStatement, Statement}; use polkadot_node_subsystem::{ - jaeger, messages::{ CanSecondRequest, CandidateBackingMessage, CollatorProtocolMessage, IfDisconnected, NetworkBridgeEvent, NetworkBridgeTxMessage, ParentHeadData, ProspectiveParachainsMessage, ProspectiveValidationDataRequest, }, - overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, PerLeafSpan, + overseer, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal, }; use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, @@ -420,9 +419,6 @@ struct State { /// Metrics. metrics: Metrics, - /// Span per relay parent. - span_per_relay_parent: HashMap, - /// When a timer in this `FuturesUnordered` triggers, we should dequeue the next request /// attempt in the corresponding `collations_per_relay_parent`. /// @@ -723,10 +719,6 @@ async fn request_collation( collator_protocol_version: peer_protocol_version, from_collator: response_recv, cancellation_token: cancellation_token.clone(), - span: state - .span_per_relay_parent - .get(&relay_parent) - .map(|s| s.child("collation-request").with_para_id(para_id)), _lifetime_timer: state.metrics.time_collation_request_duration(), }; @@ -1066,11 +1058,6 @@ async fn handle_advertisement( where Sender: CollatorProtocolSenderTrait, { - let _span = state - .span_per_relay_parent - .get(&relay_parent) - .map(|s| s.child("advertise-collation")); - let peer_data = state.peer_data.get_mut(&peer_id).ok_or(AdvertisementError::UnknownPeer)?; if peer_data.version == CollationVersion::V1 && !state.active_leaves.contains_key(&relay_parent) @@ -1264,11 +1251,6 @@ where for leaf in added { let mode = prospective_parachains_mode(sender, *leaf).await?; - if let Some(span) = view.span_per_head().get(leaf).cloned() { - let per_leaf_span = PerLeafSpan::new(span, "validator-side"); - state.span_per_relay_parent.insert(*leaf, per_leaf_span); - } - let mut per_relay_parent = PerRelayParent::new(mode); assign_incoming( sender, @@ -1338,7 +1320,6 @@ where keep }); state.fetched_candidates.retain(|k, _| k.relay_parent != removed); - state.span_per_relay_parent.remove(&removed); } } @@ -1983,10 +1964,6 @@ async fn handle_collation_fetch_response( Ok(resp) => Ok(resp), }; - let _span = state - .span_per_relay_parent - .get(&pending_collation.relay_parent) - .map(|s| s.child("received-collation")); let _timer = state.metrics.time_handle_collation_request_result(); let mut metrics_result = Err(()); @@ -2067,7 +2044,6 @@ async fn handle_collation_fetch_response( candidate_hash = ?candidate_receipt.hash(), "Received collation", ); - let _span = jaeger::Span::new(&pov, "received-collation"); metrics_result = Ok(()); Ok(PendingCollationFetch { @@ -2093,7 +2069,6 @@ async fn handle_collation_fetch_response( candidate_hash = ?receipt.hash(), "Received collation (v3)", ); - let _span = jaeger::Span::new(&pov, "received-collation"); metrics_result = Ok(()); Ok(PendingCollationFetch { diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index 472731b506a..dff98e22e3d 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -103,8 +103,7 @@ pub(super) async fn update_view( ) -> Option { let new_view: HashMap = HashMap::from_iter(new_view); - let our_view = - OurView::new(new_view.keys().map(|hash| (*hash, Arc::new(jaeger::Span::Disabled))), 0); + let our_view = OurView::new(new_view.keys().map(|hash| *hash), 0); overseer_send( virtual_overseer, diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index c9ae23d756c..3d51d3c0a56 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -15,7 +15,6 @@ async-trait = { workspace = true } hex = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index ca0f8a4e484..f4f1b715b92 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -21,10 +21,9 @@ use codec::{Decode, Encode}; use polkadot_primitives::{BlockNumber, Hash}; -use std::{collections::HashMap, fmt}; +use std::fmt; #[doc(hidden)] -pub use polkadot_node_jaeger as jaeger; pub use sc_network::IfDisconnected; pub use sc_network_types::PeerId; #[doc(hidden)] @@ -91,31 +90,16 @@ impl Into for ObservedRole { } /// Specialized wrapper around [`View`]. -/// -/// Besides the access to the view itself, it also gives access to the [`jaeger::Span`] per -/// leave/head. #[derive(Debug, Clone, Default)] pub struct OurView { view: View, - span_per_head: HashMap>, } impl OurView { /// Creates a new instance. - pub fn new( - heads: impl IntoIterator)>, - finalized_number: BlockNumber, - ) -> Self { - let state_per_head = heads.into_iter().collect::>(); - let view = View::new(state_per_head.keys().cloned(), finalized_number); - Self { view, span_per_head: state_per_head } - } - - /// Returns the span per head map. - /// - /// For each head there exists one span in this map. - pub fn span_per_head(&self) -> &HashMap> { - &self.span_per_head + pub fn new(heads: impl IntoIterator, finalized_number: BlockNumber) -> Self { + let view = View::new(heads, finalized_number); + Self { view } } } @@ -133,8 +117,7 @@ impl std::ops::Deref for OurView { } } -/// Construct a new [`OurView`] with the given chain heads, finalized number 0 and disabled -/// [`jaeger::Span`]'s. +/// Construct a new [`OurView`] with the given chain heads, finalized number 0 /// /// NOTE: Use for tests only. /// @@ -149,7 +132,7 @@ impl std::ops::Deref for OurView { macro_rules! our_view { ( $( $hash:expr ),* $(,)? ) => { $crate::OurView::new( - vec![ $( $hash.clone() ),* ].into_iter().map(|h| (h, $crate::Arc::new($crate::jaeger::Span::Disabled))), + vec![ $( $hash.clone() ),* ].into_iter().map(|h| h), 0, ) }; diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs index 264333435a0..8270b980919 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs @@ -33,9 +33,8 @@ use polkadot_node_subsystem_util::{ }; use polkadot_node_subsystem::{ - jaeger, messages::{CandidateBackingMessage, NetworkBridgeEvent, NetworkBridgeTxMessage}, - overseer, ActivatedLeaf, PerLeafSpan, StatementDistributionSenderTrait, + overseer, ActivatedLeaf, StatementDistributionSenderTrait, }; use polkadot_primitives::{ AuthorityDiscoveryId, CandidateHash, CommittedCandidateReceipt, CompactStatement, Hash, @@ -632,15 +631,12 @@ pub(crate) struct ActiveHeadData { session_index: sp_staking::SessionIndex, /// How many `Seconded` statements we've seen per validator. seconded_counts: HashMap, - /// A Jaeger span for this head, so we can attach data to it. - span: PerLeafSpan, } impl ActiveHeadData { fn new( validators: IndexedVec, session_index: sp_staking::SessionIndex, - span: PerLeafSpan, ) -> Self { ActiveHeadData { candidates: Default::default(), @@ -650,7 +646,6 @@ impl ActiveHeadData { validators, session_index, seconded_counts: Default::default(), - span, } } @@ -901,12 +896,6 @@ async fn circulate_statement_and_dependents( None => return, }; - let _span = active_head - .span - .child("circulate-statement") - .with_candidate(statement.payload().candidate_hash()) - .with_stage(jaeger::Stage::StatementDistribution); - let topology = topology_store .get_topology_or_fallback(active_head.session_index) .local_grid_neighbors(); @@ -933,12 +922,10 @@ async fn circulate_statement_and_dependents( } }; - let _span = _span.child("send-to-peers"); // Now send dependent statements to all peers needing them, if any. if let Some((candidate_hash, peers_needing_dependents)) = outputs { for peer in peers_needing_dependents { if let Some(peer_data) = peers.get_mut(&peer) { - let _span_loop = _span.child("to-peer").with_peer_id(&peer); // defensive: the peer data should always be some because the iterator // of peers is derived from the set of peers. send_statements_about( @@ -1513,11 +1500,6 @@ async fn handle_incoming_message<'a, Context>( let fingerprint = message.get_fingerprint(); let candidate_hash = *fingerprint.0.candidate_hash(); - let handle_incoming_span = active_head - .span - .child("handle-incoming") - .with_candidate(candidate_hash) - .with_peer_id(&peer); let max_message_count = active_head.validators.len() * 2; @@ -1699,8 +1681,6 @@ async fn handle_incoming_message<'a, Context>( NotedStatement::Fresh(statement) => { modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT_FIRST).await; - let mut _span = handle_incoming_span.child("notify-backing"); - // When we receive a new message from a peer, we forward it to the // candidate backing subsystem. ctx.send_message(CandidateBackingMessage::Statement(relay_parent, statement_with_pvd)) @@ -2079,7 +2059,6 @@ pub(crate) async fn handle_activated_leaf( activated: ActivatedLeaf, ) -> Result<()> { let relay_parent = activated.hash; - let span = PerLeafSpan::new(activated.span, "statement-distribution-legacy"); gum::trace!( target: LOG_TARGET, hash = ?relay_parent, @@ -2095,11 +2074,10 @@ pub(crate) async fn handle_activated_leaf( .await?; let session_info = &info.session_info; - state.active_heads.entry(relay_parent).or_insert(ActiveHeadData::new( - session_info.validators.clone(), - session_index, - span, - )); + state + .active_heads + .entry(relay_parent) + .or_insert(ActiveHeadData::new(session_info.validators.clone(), session_index)); Ok(()) } diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs index 8a8a8f3d624..c0346adfe10 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/requester.rs @@ -28,7 +28,6 @@ use polkadot_node_network_protocol::{ }, PeerId, UnifiedReputationChange, }; -use polkadot_node_subsystem::{Span, Stage}; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{CandidateHash, CommittedCandidateReceipt, Hash}; @@ -82,10 +81,6 @@ pub async fn fetch( mut sender: mpsc::Sender, metrics: Metrics, ) { - let span = Span::new(candidate_hash, "fetch-large-statement") - .with_relay_parent(relay_parent) - .with_stage(Stage::StatementDistribution); - gum::debug!( target: LOG_TARGET, ?candidate_hash, @@ -102,11 +97,7 @@ pub async fn fetch( // We retry endlessly (with sleep periods), and rely on the subsystem to kill us eventually. loop { - let span = span.child("try-available-peers"); - while let Some(peer) = new_peers.pop() { - let _span = span.child("try-peer").with_peer_id(&peer); - let (outgoing, pending_response) = OutgoingRequest::new(Recipient::Peer(peer), req.clone()); if let Err(err) = sender @@ -182,7 +173,7 @@ pub async fn fetch( new_peers = std::mem::take(&mut tried_peers); // All our peers failed us - try getting new ones before trying again: - match try_get_new_peers(relay_parent, candidate_hash, &mut sender, &span).await { + match try_get_new_peers(relay_parent, candidate_hash, &mut sender).await { Ok(Some(mut peers)) => { gum::trace!(target: LOG_TARGET, ?peers, "Received new peers."); // New arrivals will be tried first: @@ -205,10 +196,7 @@ async fn try_get_new_peers( relay_parent: Hash, candidate_hash: CandidateHash, sender: &mut mpsc::Sender, - span: &Span, ) -> Result>, ()> { - let _span = span.child("wait-for-peers"); - let (tx, rx) = oneshot::channel(); if let Err(err) = sender diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs index 8e6fcbaebbf..5e00fb96d74 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs @@ -121,7 +121,6 @@ fn active_head_accepts_only_2_seconded_per_validator() { let mut head_data = ActiveHeadData::new( IndexedVec::::from(validators), session_index, - PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), ); let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); @@ -467,7 +466,6 @@ fn peer_view_update_sends_messages() { let mut data = ActiveHeadData::new( IndexedVec::::from(validators), session_index, - PerLeafSpan::new(Arc::new(jaeger::Span::Disabled), "test"), ); let statement = SignedFullStatement::sign( diff --git a/polkadot/node/overseer/src/dummy.rs b/polkadot/node/overseer/src/dummy.rs index 6f9cd9d0040..d618c0c7ca9 100644 --- a/polkadot/node/overseer/src/dummy.rs +++ b/polkadot/node/overseer/src/dummy.rs @@ -193,7 +193,6 @@ where .chain_selection(subsystem.clone()) .prospective_parachains(subsystem.clone()) .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) .spawner(SpawnGlue(spawner)) .metrics(metrics) diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index e30ed69f9e3..87ef63d8a5d 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -88,8 +88,8 @@ use polkadot_node_subsystem_types::messages::{ pub use polkadot_node_subsystem_types::{ errors::{SubsystemError, SubsystemResult}, - jaeger, ActivatedLeaf, ActiveLeavesUpdate, ChainApiBackend, OverseerSignal, - RuntimeApiSubsystemClient, UnpinHandle, + ActivatedLeaf, ActiveLeavesUpdate, ChainApiBackend, OverseerSignal, RuntimeApiSubsystemClient, + UnpinHandle, }; pub mod metrics; @@ -650,9 +650,6 @@ pub struct Overseer { /// External listeners waiting for a hash to be in the active-leave set. pub activation_external_listeners: HashMap>>>, - /// Stores the [`jaeger::Span`] per active leaf. - pub span_per_active_leaf: HashMap>, - /// The set of the "active leaves". pub active_leaves: HashMap, @@ -817,11 +814,10 @@ where }; let mut update = match self.on_head_activated(&block.hash, Some(block.parent_hash)).await { - Some(span) => ActiveLeavesUpdate::start_work(ActivatedLeaf { + Some(_) => ActiveLeavesUpdate::start_work(ActivatedLeaf { hash: block.hash, number: block.number, unpin_handle: block.unpin_handle, - span, }), None => ActiveLeavesUpdate::default(), }; @@ -874,11 +870,7 @@ where /// Handles a header activation. If the header's state doesn't support the parachains API, /// this returns `None`. - async fn on_head_activated( - &mut self, - hash: &Hash, - parent_hash: Option, - ) -> Option> { + async fn on_head_activated(&mut self, hash: &Hash, _parent_hash: Option) -> Option<()> { if !self.supports_parachains.head_supports_parachains(hash).await { return None } @@ -896,22 +888,12 @@ where } } - let mut span = jaeger::Span::new(*hash, "leaf-activated"); - - if let Some(parent_span) = parent_hash.and_then(|h| self.span_per_active_leaf.get(&h)) { - span.add_follows_from(parent_span); - } - - let span = Arc::new(span); - self.span_per_active_leaf.insert(*hash, span.clone()); - - Some(span) + Some(()) } fn on_head_deactivated(&mut self, hash: &Hash) { self.metrics.on_head_deactivated(); self.activation_external_listeners.remove(hash); - self.span_per_active_leaf.remove(hash); } fn clean_up_external_listeners(&mut self) { diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index d029f3be53e..9515dd23113 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -59,7 +59,6 @@ use { sc_client_api::BlockBackend, sc_consensus_grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider}, sc_transaction_pool_api::OffchainTransactionPoolFactory, - sp_core::traits::SpawnNamed, }; use polkadot_node_subsystem_util::database::Database; @@ -76,9 +75,6 @@ pub use { sp_consensus_babe::BabeApi, }; -#[cfg(feature = "full-node")] -use polkadot_node_subsystem::jaeger; - use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration}; use prometheus_endpoint::Registry; @@ -222,9 +218,6 @@ pub enum Error { #[error(transparent)] Telemetry(#[from] sc_telemetry::Error), - #[error(transparent)] - Jaeger(#[from] polkadot_node_subsystem::jaeger::JaegerError), - #[cfg(feature = "full-node")] #[error(transparent)] Availability(#[from] AvailabilityError), @@ -365,25 +358,6 @@ pub fn open_database(db_source: &DatabaseSource) -> Result, Er Ok(parachains_db) } -/// Initialize the `Jeager` collector. The destination must listen -/// on the given address and port for `UDP` packets. -#[cfg(any(test, feature = "full-node"))] -fn jaeger_launch_collector_with_agent( - spawner: impl SpawnNamed, - config: &Configuration, - agent: Option, -) -> Result<(), Error> { - if let Some(agent) = agent { - let cfg = jaeger::JaegerConfig::builder() - .agent(agent) - .named(&config.network.node_name) - .build(); - - jaeger::Jaeger::new(cfg).launch(spawner)?; - } - Ok(()) -} - #[cfg(feature = "full-node")] type FullSelectChain = relay_chain_selection::SelectRelayChain; #[cfg(feature = "full-node")] @@ -411,7 +385,6 @@ struct Basics { #[cfg(feature = "full-node")] fn new_partial_basics( config: &mut Configuration, - jaeger_agent: Option, telemetry_worker_handle: Option, ) -> Result { let telemetry = config @@ -463,8 +436,6 @@ fn new_partial_basics( telemetry }); - jaeger_launch_collector_with_agent(task_manager.spawn_handle(), &*config, jaeger_agent)?; - Ok(Basics { task_manager, client, backend, keystore_container, telemetry }) } @@ -637,7 +608,6 @@ pub struct NewFullParams { /// Whether to enable the block authoring backoff on production networks /// where it isn't enabled by default. pub force_authoring_backoff: bool, - pub jaeger_agent: Option, pub telemetry_worker_handle: Option, /// The version of the node. TESTING ONLY: `None` can be passed to skip the node/worker version /// check, both on startup and in the workers. @@ -742,7 +712,6 @@ pub fn new_full< is_parachain_node, enable_beefy, force_authoring_backoff, - jaeger_agent, telemetry_worker_handle, node_version, secure_validator_mode, @@ -798,7 +767,7 @@ pub fn new_full< let disable_grandpa = config.disable_grandpa; let name = config.network.node_name.clone(); - let basics = new_partial_basics(&mut config, jaeger_agent, telemetry_worker_handle)?; + let basics = new_partial_basics(&mut config, telemetry_worker_handle)?; let prometheus_registry = config.prometheus_registry().cloned(); @@ -1437,11 +1406,10 @@ pub fn new_full< #[cfg(feature = "full-node")] macro_rules! chain_ops { - ($config:expr, $jaeger_agent:expr, $telemetry_worker_handle:expr) => {{ + ($config:expr, $telemetry_worker_handle:expr) => {{ let telemetry_worker_handle = $telemetry_worker_handle; - let jaeger_agent = $jaeger_agent; let mut config = $config; - let basics = new_partial_basics(config, jaeger_agent, telemetry_worker_handle)?; + let basics = new_partial_basics(config, telemetry_worker_handle)?; use ::sc_consensus::LongestChain; // use the longest chain selection, since there is no overseer available @@ -1457,19 +1425,18 @@ macro_rules! chain_ops { #[cfg(feature = "full-node")] pub fn new_chain_ops( config: &mut Configuration, - jaeger_agent: Option, ) -> Result<(Arc, Arc, sc_consensus::BasicQueue, TaskManager), Error> { config.keystore = sc_service::config::KeystoreConfig::InMemory; if config.chain_spec.is_rococo() || config.chain_spec.is_versi() { - chain_ops!(config, jaeger_agent, None) + chain_ops!(config, None) } else if config.chain_spec.is_kusama() { - chain_ops!(config, jaeger_agent, None) + chain_ops!(config, None) } else if config.chain_spec.is_westend() { - return chain_ops!(config, jaeger_agent, None) + return chain_ops!(config, None) } else { - chain_ops!(config, jaeger_agent, None) + chain_ops!(config, None) } } diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index a98b6bcb308..279b6ff8070 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -358,7 +358,6 @@ where .chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db)) .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) .supports_parachains(runtime_client) .metrics(metrics) @@ -580,7 +579,6 @@ where .chain_selection(ChainSelectionSubsystem::new(chain_selection_config, parachains_db)) .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) .supports_parachains(runtime_client) .metrics(metrics) @@ -728,7 +726,6 @@ where .chain_selection(DummySubsystem) .prospective_parachains(DummySubsystem) .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) .supports_parachains(runtime_client) .metrics(Metrics::register(registry)?) diff --git a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs index f28adff315f..a99f013195f 100644 --- a/polkadot/node/subsystem-bench/src/lib/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/availability/mod.rs @@ -49,10 +49,7 @@ use polkadot_node_subsystem::{ messages::{AllMessages, AvailabilityRecoveryMessage}, Overseer, OverseerConnector, SpawnGlue, }; -use polkadot_node_subsystem_types::{ - messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, - Span, -}; +use polkadot_node_subsystem_types::messages::{AvailabilityStoreMessage, NetworkBridgeEvent}; use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; use polkadot_primitives::{Block, CoreIndex, GroupIndex, Hash}; use sc_network::request_responses::{IncomingRequest as RawIncomingRequest, ProtocolConfig}; @@ -421,7 +418,7 @@ pub async fn benchmark_availability_write( // Inform bitfield distribution about our view of current test block let message = polkadot_node_subsystem_types::messages::BitfieldDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::OurViewChange(OurView::new(vec![(relay_block_hash, Arc::new(Span::Disabled))], 0)) + NetworkBridgeEvent::OurViewChange(OurView::new(vec![relay_block_hash], 0)) ); env.send_message(AllMessages::BitfieldDistribution(message)).await; diff --git a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs index 2ca47d9fc08..00c19fe62cc 100644 --- a/polkadot/node/subsystem-bench/src/lib/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/mock/mod.rs @@ -71,7 +71,6 @@ macro_rules! dummy_builder { .dispute_distribution(MockDisputeDistribution {}) .prospective_parachains(MockProspectiveParachains {}) .activation_external_listeners(Default::default()) - .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) .metrics($metrics) .supports_parachains(AlwaysSupportsParachains {}) diff --git a/polkadot/node/subsystem-test-helpers/src/mock.rs b/polkadot/node/subsystem-test-helpers/src/mock.rs index 14026960ac1..f73b4b573ff 100644 --- a/polkadot/node/subsystem-test-helpers/src/mock.rs +++ b/polkadot/node/subsystem-test-helpers/src/mock.rs @@ -16,7 +16,7 @@ use std::sync::Arc; -use polkadot_node_subsystem::{jaeger, ActivatedLeaf, BlockInfo}; +use polkadot_node_subsystem::{ActivatedLeaf, BlockInfo}; use sc_client_api::UnpinHandle; use sc_keystore::LocalKeystore; use sc_utils::mpsc::tracing_unbounded; @@ -52,12 +52,7 @@ pub fn dummy_unpin_handle(block: Hash) -> UnpinHandle { /// Create a new leaf with the given hash and number. pub fn new_leaf(hash: Hash, number: BlockNumber) -> ActivatedLeaf { - ActivatedLeaf { - hash, - number, - unpin_handle: dummy_unpin_handle(hash), - span: Arc::new(jaeger::Span::Disabled), - } + ActivatedLeaf { hash, number, unpin_handle: dummy_unpin_handle(hash) } } /// Create a new leaf with the given hash and number. diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index c8fc324699e..b5686ec96be 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -17,7 +17,6 @@ polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-statement-table = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } orchestra = { features = ["futures_channel"], workspace = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } diff --git a/polkadot/node/subsystem-types/src/errors.rs b/polkadot/node/subsystem-types/src/errors.rs index 8e1b515c8db..8770f3a3d9a 100644 --- a/polkadot/node/subsystem-types/src/errors.rs +++ b/polkadot/node/subsystem-types/src/errors.rs @@ -16,7 +16,6 @@ //! Error types for the subsystem requests. -use crate::JaegerError; use ::orchestra::OrchestraError as OverseerError; use fatality::fatality; @@ -109,9 +108,6 @@ pub enum SubsystemError { #[error(transparent)] Prometheus(#[from] prometheus_endpoint::PrometheusError), - #[error(transparent)] - Jaeger(#[from] JaegerError), - #[error("Failed to {0}")] Context(String), diff --git a/polkadot/node/subsystem-types/src/lib.rs b/polkadot/node/subsystem-types/src/lib.rs index cd39aa03e56..cde6bba18e7 100644 --- a/polkadot/node/subsystem-types/src/lib.rs +++ b/polkadot/node/subsystem-types/src/lib.rs @@ -23,7 +23,7 @@ #![warn(missing_docs)] use smallvec::SmallVec; -use std::{fmt, sync::Arc}; +use std::fmt; pub use polkadot_primitives::{Block, BlockNumber, Hash}; @@ -42,9 +42,6 @@ pub mod messages; mod runtime_client; pub use runtime_client::{ChainApiBackend, DefaultSubsystemClient, RuntimeApiSubsystemClient}; -pub use jaeger::*; -pub use polkadot_node_jaeger as jaeger; - /// How many slots are stack-reserved for active leaves updates /// /// If there are fewer than this number of slots, then we've wasted some stack space. @@ -60,11 +57,6 @@ pub struct ActivatedLeaf { pub number: BlockNumber, /// A handle to unpin the block on drop. pub unpin_handle: UnpinHandle, - /// An associated [`jaeger::Span`]. - /// - /// NOTE: Each span should only be kept active as long as the leaf is considered active and - /// should be dropped when the leaf is deactivated. - pub span: Arc, } /// Changes in the set of active leaves: the parachain heads which we care to work on. diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index 3bd3892ba60..d12daa57205 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -27,7 +27,6 @@ schnellru = { workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index 8edfea9e26b..ce4bceec733 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -12,4 +12,3 @@ workspace = true [dependencies] polkadot-overseer = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-jaeger = { workspace = true, default-features = true } diff --git a/polkadot/node/subsystem/src/lib.rs b/polkadot/node/subsystem/src/lib.rs index 8b407c75a0c..bde5a623c47 100644 --- a/polkadot/node/subsystem/src/lib.rs +++ b/polkadot/node/subsystem/src/lib.rs @@ -21,9 +21,6 @@ #![deny(missing_docs)] #![deny(unused_crate_dependencies)] -pub use jaeger::*; -pub use polkadot_node_jaeger as jaeger; - pub use polkadot_overseer::{self as overseer, *}; pub use polkadot_node_subsystem_types::{ diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index f879aa93df9..aa7295dddc5 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -88,7 +88,6 @@ pub fn new_full( is_parachain_node, enable_beefy: true, force_authoring_backoff: false, - jaeger_agent: None, telemetry_worker_handle: None, node_version: None, secure_validator_mode: false, @@ -111,7 +110,6 @@ pub fn new_full( is_parachain_node, enable_beefy: true, force_authoring_backoff: false, - jaeger_agent: None, telemetry_worker_handle: None, node_version: None, secure_validator_mode: false, diff --git a/polkadot/parachain/test-parachains/adder/collator/src/main.rs b/polkadot/parachain/test-parachains/adder/collator/src/main.rs index 4660b4d38f7..416e58b0a8a 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/main.rs @@ -82,7 +82,6 @@ fn main() -> Result<()> { ), enable_beefy: false, force_authoring_backoff: false, - jaeger_agent: None, telemetry_worker_handle: None, // Collators don't spawn PVF workers, so we can disable version checks. diff --git a/polkadot/parachain/test-parachains/undying/collator/src/main.rs b/polkadot/parachain/test-parachains/undying/collator/src/main.rs index 3dfa714e6d1..017eefe5ee3 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/main.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/main.rs @@ -84,7 +84,6 @@ fn main() -> Result<()> { ), enable_beefy: false, force_authoring_backoff: false, - jaeger_agent: None, telemetry_worker_handle: None, // Collators don't spawn PVF workers, so we can disable version checks. diff --git a/prdoc/pr_5875.prdoc b/prdoc/pr_5875.prdoc new file mode 100644 index 00000000000..fb308c02dde --- /dev/null +++ b/prdoc/pr_5875.prdoc @@ -0,0 +1,47 @@ +title: "Remove jaeger from polkadot" + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + Jaeger was remove from the codebase because it was not used by anyone + and it did not help with the debugging. + +crates: + - name: polkadot-sdk + bump: patch + - name: polkadot-overseer + bump: major + - name: polkadot-node-subsystem + bump: patch + - name: polkadot-node-subsystem-types + bump: major + - name: polkadot-node-network-protocol + bump: major + - name: polkadot-service + bump: major + - name: polkadot-availability-distribution + bump: patch + - name: polkadot-availability-recovery + bump: patch + - name: polkadot-node-core-av-store + bump: patch + - name: polkadot-statement-distribution + bump: patch + - name: polkadot-collator-protocol + bump: patch + - name: polkadot-availability-bitfield-distribution + bump: patch + - name: polkadot-network-bridge + bump: patch + - name: polkadot-node-collation-generation + bump: patch + - name: polkadot-node-core-bitfield-signing + bump: patch + - name: polkadot-node-core-candidate-validation + bump: patch + - name: polkadot-node-core-provisioner + bump: patch + - name: cumulus-relay-chain-inprocess-interface + bump: patch + - name: polkadot-cli + bump: major diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 83cbebbc61c..3e7edc73982 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -600,7 +600,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-jaeger", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", @@ -2052,11 +2052,6 @@ path = "../polkadot/node/core/runtime-api" default-features = false optional = true -[dependencies.polkadot-node-jaeger] -path = "../polkadot/node/jaeger" -default-features = false -optional = true - [dependencies.polkadot-node-metrics] path = "../polkadot/node/metrics" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 4a653dab99b..656273029fe 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -874,10 +874,6 @@ pub use polkadot_node_core_pvf_prepare_worker; #[cfg(feature = "polkadot-node-core-runtime-api")] pub use polkadot_node_core_runtime_api; -/// Polkadot Jaeger primitives, but equally useful for Grafana/Tempo. -#[cfg(feature = "polkadot-node-jaeger")] -pub use polkadot_node_jaeger; - /// Subsystem metric helpers. #[cfg(feature = "polkadot-node-metrics")] pub use polkadot_node_metrics; -- GitLab From f8807d1e185ccd281052106599a2c55694d02fc1 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:28:04 +0300 Subject: [PATCH 045/127] templates: add genesis config presets for minimal/solochain (#5868) # Description Closes [#5790](https://github.com/paritytech/polkadot-sdk/issues/5790). Useful for starting nodes based on minimal/solochain when doing development or for testing omni node with less happy code paths. It is reusing the presets defined for the nodes chain specs. ## Integration Specifically useful for development/testing if generating chain-specs for `minimal` or `solochain` runtimes from `templates` directories. ## Review Notes Added `genesis_config_presets` modules for both minimal/solochain. I reused the presets defined in each node `chain_spec` module correspondingly. ### PRDOC Not sure who uses templates, maybe node devs and runtime devs at start of their learning journey, but happy to get some guidance on how to write the prdoc if needed. ### Thinking out loud I saw concerns around sharing functionality for such genesis config presets between the template chains. I think there might be a case for doing that, on the lines of this comment: https://github.com/paritytech/polkadot-sdk/pull/4739#issuecomment-2157341035. I would add that `parachains-common::genesis_config_heleper` contains a few methods from those mentioned, but I am unsure if using it as a dependency for templates is correct. Feels like the comment suggests there should be a `commons` crate concerning just `templates`, which I agree with to some degree, if we assume `cumulus` needs might be driven in certain directions that are not relevant to `templates` and vice versa. However I am not so certain about this, so would welcome some thoughts, since I am seeing `parachains-common` being used already in a few runtime implementations: https://crates.io/crates/parachains-common/reverse_dependencies?page=3, so might be a good candidate already for the `common` logic. --------- Signed-off-by: Iulian Barbu --- Cargo.lock | 4 + templates/minimal/node/src/chain_spec.rs | 22 +--- templates/minimal/node/src/command.rs | 2 +- templates/minimal/runtime/Cargo.toml | 2 + templates/minimal/runtime/src/lib.rs | 54 ++++++++- templates/parachain/node/src/chain_spec.rs | 4 +- templates/parachain/node/src/command.rs | 6 +- templates/parachain/runtime/Cargo.toml | 2 +- templates/solochain/node/Cargo.toml | 5 +- templates/solochain/node/src/chain_spec.rs | 98 +-------------- templates/solochain/node/src/command.rs | 4 +- templates/solochain/runtime/Cargo.toml | 4 + templates/solochain/runtime/src/apis.rs | 6 +- .../runtime/src/genesis_config_presets.rs | 112 ++++++++++++++++++ templates/solochain/runtime/src/lib.rs | 2 + 15 files changed, 197 insertions(+), 130 deletions(-) create mode 100644 templates/solochain/runtime/src/genesis_config_presets.rs diff --git a/Cargo.lock b/Cargo.lock index 568d642879b..119a8b3afed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9592,6 +9592,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk", "scale-info", + "serde_json", ] [[package]] @@ -21246,6 +21247,7 @@ dependencies = [ "sp-consensus-aura", "sp-consensus-grandpa", "sp-core 28.0.0", + "sp-genesis-builder", "sp-inherents", "sp-io 30.0.0", "sp-keyring", @@ -21277,6 +21279,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", + "serde_json", "sp-api 26.0.0", "sp-block-builder", "sp-consensus-aura", @@ -21284,6 +21287,7 @@ dependencies = [ "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", + "sp-keyring", "sp-offchain", "sp-runtime 31.0.1", "sp-session", diff --git a/templates/minimal/node/src/chain_spec.rs b/templates/minimal/node/src/chain_spec.rs index 0646460acef..17b98137b41 100644 --- a/templates/minimal/node/src/chain_spec.rs +++ b/templates/minimal/node/src/chain_spec.rs @@ -15,13 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use minimal_template_runtime::{BalancesConfig, SudoConfig, WASM_BINARY}; +use minimal_template_runtime::WASM_BINARY; use polkadot_sdk::{ sc_service::{ChainType, Properties}, - sp_keyring::AccountKeyring, *, }; -use serde_json::{json, Value}; /// This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; @@ -33,26 +31,12 @@ fn props() -> Properties { properties } -pub fn development_config() -> Result { +pub fn development_chain_spec() -> Result { Ok(ChainSpec::builder(WASM_BINARY.expect("Development wasm not available"), Default::default()) .with_name("Development") .with_id("dev") .with_chain_type(ChainType::Development) - .with_genesis_config_patch(testnet_genesis()) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .with_properties(props()) .build()) } - -/// Configure initial storage state for FRAME pallets. -fn testnet_genesis() -> Value { - use minimal_template_runtime::interface::{Balance, MinimumBalance}; - use polkadot_sdk::polkadot_sdk_frame::traits::Get; - let endowment = >::get().max(1) * 1000; - let balances = AccountKeyring::iter() - .map(|a| (a.to_account_id(), endowment)) - .collect::>(); - json!({ - "balances": BalancesConfig { balances }, - "sudo": SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, - }) -} diff --git a/templates/minimal/node/src/command.rs b/templates/minimal/node/src/command.rs index b09ea1fab23..5cb0694d982 100644 --- a/templates/minimal/node/src/command.rs +++ b/templates/minimal/node/src/command.rs @@ -49,7 +49,7 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { Ok(match id { - "dev" => Box::new(chain_spec::development_config()?), + "dev" => Box::new(chain_spec::development_chain_spec()?), path => Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), }) diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index 49ddf3987e9..74a09b9396e 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -21,6 +21,7 @@ polkadot-sdk = { workspace = true, features = [ "pallet-transaction-payment-rpc-runtime-api", "runtime", ] } +serde_json = { workspace = true, default-features = false, features = ["alloc"] } # local pallet templates pallet-minimal-template = { workspace = true } @@ -37,4 +38,5 @@ std = [ "pallet-minimal-template/std", "polkadot-sdk/std", "scale-info/std", + "serde_json/std", ] diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index cce13c48af7..7379e33b6b3 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -25,7 +25,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use polkadot_sdk::{ polkadot_sdk_frame::{ @@ -36,6 +36,54 @@ use polkadot_sdk::{ *, }; +/// Provides getters for genesis configuration presets. +pub mod genesis_config_presets { + use crate::{ + interface::{Balance, MinimumBalance}, + sp_genesis_builder::PresetId, + sp_keyring::AccountKeyring, + BalancesConfig, RuntimeGenesisConfig, SudoConfig, + }; + + use alloc::{vec, vec::Vec}; + use polkadot_sdk::{sp_core::Get, sp_genesis_builder}; + use serde_json::Value; + + /// Returns a development genesis config preset. + pub fn development_config_genesis() -> Value { + let endowment = >::get().max(1) * 1000; + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: AccountKeyring::iter() + .map(|a| (a.to_account_id(), endowment)) + .collect::>(), + }, + sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") + } + + /// Get the set of the available genesis config presets. + pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => development_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) + } + + /// List of supported presets. + pub fn preset_names() -> Vec { + vec![PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET)] + } +} + /// The runtime version. #[runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { @@ -272,11 +320,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, self::genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + self::genesis_config_presets::preset_names() } } } diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index 7cdd362dffb..af82607bc8e 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -24,7 +24,7 @@ impl Extensions { } } -pub fn development_config() -> ChainSpec { +pub fn development_chain_spec() -> ChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "UNIT".into()); @@ -46,7 +46,7 @@ pub fn development_config() -> ChainSpec { .build() } -pub fn local_testnet_config() -> ChainSpec { +pub fn local_chain_spec() -> ChainSpec { // Give your base currency a unit name and decimal places let mut properties = sc_chain_spec::Properties::new(); properties.insert("tokenSymbol".into(), "UNIT".into()); diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs index 610dbd7a686..6b9deade127 100644 --- a/templates/parachain/node/src/command.rs +++ b/templates/parachain/node/src/command.rs @@ -17,9 +17,9 @@ use crate::{ fn load_spec(id: &str) -> std::result::Result, String> { Ok(match id { - "dev" => Box::new(chain_spec::development_config()), - "template-rococo" => Box::new(chain_spec::local_testnet_config()), - "" | "local" => Box::new(chain_spec::local_testnet_config()), + "dev" => Box::new(chain_spec::development_chain_spec()), + "template-rococo" => Box::new(chain_spec::local_chain_spec()), + "" | "local" => Box::new(chain_spec::local_chain_spec()), path => Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), }) } diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index 45c77d18e81..236b7c04863 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -27,7 +27,7 @@ scale-info = { features = [ ], workspace = true } smallvec = { workspace = true, default-features = true } docify = { workspace = true } -serde_json = { workspace = true, default-features = false } +serde_json = { workspace = true, default-features = false, features = ["alloc"] } # Local pallet-parachain-template = { workspace = true } diff --git a/templates/solochain/node/Cargo.toml b/templates/solochain/node/Cargo.toml index 8f74c6b3cb5..a0285e048d1 100644 --- a/templates/solochain/node/Cargo.toml +++ b/templates/solochain/node/Cargo.toml @@ -35,6 +35,7 @@ sp-consensus-aura = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } @@ -66,9 +67,7 @@ substrate-build-script-utils = { workspace = true, default-features = true } [features] default = ["std"] -std = [ - "solochain-template-runtime/std", -] +std = ["solochain-template-runtime/std"] # Dependencies that are only required if runtime benchmarking should be build. runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", diff --git a/templates/solochain/node/src/chain_spec.rs b/templates/solochain/node/src/chain_spec.rs index 651025e68de..086bf7accf3 100644 --- a/templates/solochain/node/src/chain_spec.rs +++ b/templates/solochain/node/src/chain_spec.rs @@ -1,39 +1,10 @@ use sc_service::ChainType; -use solochain_template_runtime::{AccountId, Signature, WASM_BINARY}; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_consensus_grandpa::AuthorityId as GrandpaId; -use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; - -// The URL for the telemetry server. -// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; +use solochain_template_runtime::WASM_BINARY; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; -/// Generate a crypto pair from seed. -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -type AccountPublic = ::Signer; - -/// Generate an account ID from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Generate an Aura authority key. -pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { - (get_from_seed::(s), get_from_seed::(s)) -} - -pub fn development_config() -> Result { +pub fn development_chain_spec() -> Result { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, None, @@ -41,24 +12,11 @@ pub fn development_config() -> Result { .with_name("Development") .with_id("dev") .with_chain_type(ChainType::Development) - .with_genesis_config_patch(testnet_genesis( - // Initial PoA authorities - vec![authority_keys_from_seed("Alice")], - // Sudo account - get_account_id_from_seed::("Alice"), - // Pre-funded accounts - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - ], - true, - )) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) .build()) } -pub fn local_testnet_config() -> Result { +pub fn local_chain_spec() -> Result { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, None, @@ -66,52 +24,6 @@ pub fn local_testnet_config() -> Result { .with_name("Local Testnet") .with_id("local_testnet") .with_chain_type(ChainType::Local) - .with_genesis_config_patch(testnet_genesis( - // Initial PoA authorities - vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], - // Sudo account - get_account_id_from_seed::("Alice"), - // Pre-funded accounts - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - true, - )) + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) .build()) } - -/// Configure initial storage state for FRAME modules. -fn testnet_genesis( - initial_authorities: Vec<(AuraId, GrandpaId)>, - root_key: AccountId, - endowed_accounts: Vec, - _enable_println: bool, -) -> serde_json::Value { - serde_json::json!({ - "balances": { - // Configure endowed accounts with initial balance of 1 << 60. - "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::>(), - }, - "aura": { - "authorities": initial_authorities.iter().map(|x| (x.0.clone())).collect::>(), - }, - "grandpa": { - "authorities": initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect::>(), - }, - "sudo": { - // Assign network admin rights. - "key": Some(root_key), - }, - }) -} diff --git a/templates/solochain/node/src/command.rs b/templates/solochain/node/src/command.rs index 624ace1bf35..e2c7657c95c 100644 --- a/templates/solochain/node/src/command.rs +++ b/templates/solochain/node/src/command.rs @@ -37,8 +37,8 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { Ok(match id { - "dev" => Box::new(chain_spec::development_config()?), - "" | "local" => Box::new(chain_spec::local_testnet_config()?), + "dev" => Box::new(chain_spec::development_chain_spec()?), + "" | "local" => Box::new(chain_spec::local_chain_spec()?), path => Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), }) diff --git a/templates/solochain/runtime/Cargo.toml b/templates/solochain/runtime/Cargo.toml index 9a1f7145c2c..8a7fa74a597 100644 --- a/templates/solochain/runtime/Cargo.toml +++ b/templates/solochain/runtime/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { features = [ "derive", "serde", ], workspace = true } +serde_json = { workspace = true, default-features = false, features = ["alloc"] } # frame frame-support = { features = ["experimental"], workspace = true } @@ -45,6 +46,7 @@ sp-consensus-aura = { features = [ sp-consensus-grandpa = { features = [ "serde", ], workspace = true } +sp-keyring = { workspace = true } sp-core = { features = [ "serde", ], workspace = true } @@ -114,6 +116,8 @@ std = [ "sp-transaction-pool/std", "sp-version/std", + "serde_json/std", + "sp-keyring/std", "substrate-wasm-builder", ] diff --git a/templates/solochain/runtime/src/apis.rs b/templates/solochain/runtime/src/apis.rs index 1e3dc452857..87a09a5f7fe 100644 --- a/templates/solochain/runtime/src/apis.rs +++ b/templates/solochain/runtime/src/apis.rs @@ -24,7 +24,7 @@ // For more information, please refer to // External crates imports -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use frame_support::{ genesis_builder_helper::{build_state, get_preset}, weights::Weight, @@ -285,11 +285,11 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + get_preset::(id, crate::genesis_config_presets::get_preset) } fn preset_names() -> Vec { - vec![] + crate::genesis_config_presets::preset_names() } } } diff --git a/templates/solochain/runtime/src/genesis_config_presets.rs b/templates/solochain/runtime/src/genesis_config_presets.rs new file mode 100644 index 00000000000..693ae5c2221 --- /dev/null +++ b/templates/solochain/runtime/src/genesis_config_presets.rs @@ -0,0 +1,112 @@ +// 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. + +use crate::{AccountId, BalancesConfig, RuntimeGenesisConfig, SudoConfig}; +use alloc::{vec, vec::Vec}; +use serde_json::Value; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; +use sp_genesis_builder::{self, PresetId}; +use sp_keyring::AccountKeyring; + +// Returns the genesis config presets populated with given parameters. +fn testnet_genesis( + initial_authorities: Vec<(AuraId, GrandpaId)>, + endowed_accounts: Vec, + root: AccountId, +) -> Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1u128 << 60)) + .collect::>(), + }, + aura: pallet_aura::GenesisConfig { + authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect::>(), + }, + grandpa: pallet_grandpa::GenesisConfig { + authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect::>(), + ..Default::default() + }, + sudo: SudoConfig { key: Some(root) }, + ..Default::default() + }; + + serde_json::to_value(config).expect("Could not build genesis config.") +} + +/// Return the development genesis config. +pub fn development_config_genesis() -> Value { + testnet_genesis( + vec![( + sp_keyring::Sr25519Keyring::Alice.public().into(), + sp_keyring::Ed25519Keyring::Alice.public().into(), + )], + vec![ + AccountKeyring::Alice.to_account_id(), + AccountKeyring::Bob.to_account_id(), + AccountKeyring::AliceStash.to_account_id(), + AccountKeyring::BobStash.to_account_id(), + ], + sp_keyring::AccountKeyring::Alice.to_account_id(), + ) +} + +/// Return the local genesis config preset. +pub fn local_config_genesis() -> Value { + testnet_genesis( + vec![ + ( + sp_keyring::Sr25519Keyring::Alice.public().into(), + sp_keyring::Ed25519Keyring::Alice.public().into(), + ), + ( + sp_keyring::Sr25519Keyring::Bob.public().into(), + sp_keyring::Ed25519Keyring::Bob.public().into(), + ), + ], + AccountKeyring::iter() + .filter(|v| v != &AccountKeyring::One && v != &AccountKeyring::Two) + .map(|v| v.to_account_id()) + .collect::>(), + AccountKeyring::Alice.to_account_id(), + ) +} + +/// Provides the JSON representation of predefined genesis config for given `id`. +pub fn get_preset(id: &PresetId) -> Option> { + let patch = match id.try_into() { + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => development_config_genesis(), + Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => local_config_genesis(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) +} + +/// List of supported presets. +pub fn preset_names() -> Vec { + vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + ] +} diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index 279518c8f47..bc47f3aeac8 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -25,6 +25,8 @@ pub use pallet_timestamp::Call as TimestampCall; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; +pub mod genesis_config_presets; + /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats /// of data like extrinsics, allowing for them to continue syncing the network through upgrades -- GitLab From d968c9414a6ee4e8f95f2c011b4047c6f7779342 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Sat, 5 Oct 2024 15:28:17 +0300 Subject: [PATCH 046/127] XCM paid execution barrier supports more origin altering instructions (#5917) The AllowTopLevelPaidExecutionFrom allows ClearOrigin instructions before the expected BuyExecution instruction, it also allows messages without any origin altering instructions. This commit enhances the barrier to also support messages that use AliasOrigin, or DescendOrigin. This is sometimes desired in asset transfer XCM programs that need to run the inbound assets instructions using the origin chain root origin, but then want to drop privileges for the rest of the program. Currently these programs drop privileges by clearing the origin completely, but that also unnecessarily limits the range of actions available to the rest of the program. Using DescendOrigin or AliasOrigin allows the sending chain to instruct the receiving chain what the deprivileged real origin is. See https://github.com/polkadot-fellows/RFCs/pull/109 and https://github.com/polkadot-fellows/RFCs/pull/122 for more details on how DescendOrigin and AliasOrigin could be used instead of ClearOrigin. --------- Signed-off-by: Adrian Catangiu --- polkadot/xcm/xcm-builder/src/barriers.rs | 12 +++-- .../xcm/xcm-builder/src/tests/barriers.rs | 50 +++++++++++++++++++ prdoc/pr_5917.prdoc | 14 ++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_5917.prdoc diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index 5d95005eb66..c995361ea8a 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -57,8 +57,9 @@ const MAX_ASSETS_FOR_BUY_EXECUTION: usize = 2; /// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking /// payments into account. /// -/// Only allows for `TeleportAsset`, `WithdrawAsset`, `ClaimAsset` and `ReserveAssetDeposit` XCMs -/// because they are the only ones that place assets in the Holding Register to pay for execution. +/// Only allows for `WithdrawAsset`, `ReceiveTeleportedAsset`, `ReserveAssetDeposited` and +/// `ClaimAsset` XCMs because they are the only ones that place assets in the Holding Register to +/// pay for execution. pub struct AllowTopLevelPaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( @@ -81,9 +82,9 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFrom instructions[..end] .matcher() .match_next_inst(|inst| match inst { + WithdrawAsset(ref assets) | ReceiveTeleportedAsset(ref assets) | ReserveAssetDeposited(ref assets) | - WithdrawAsset(ref assets) | ClaimAsset { ref assets, .. } => if assets.len() <= MAX_ASSETS_FOR_BUY_EXECUTION { Ok(()) @@ -92,7 +93,10 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFrom }, _ => Err(ProcessMessageError::BadFormat), })? - .skip_inst_while(|inst| matches!(inst, ClearOrigin))? + .skip_inst_while(|inst| { + matches!(inst, ClearOrigin | AliasOrigin(..)) || + matches!(inst, DescendOrigin(child) if child != &Here) + })? .match_next_inst(|inst| match inst { BuyExecution { weight_limit: Limited(ref mut weight), .. } if weight.all_gte(max_weight) => diff --git a/polkadot/xcm/xcm-builder/src/tests/barriers.rs b/polkadot/xcm/xcm-builder/src/tests/barriers.rs index 665b5febc61..cd2b6db66ef 100644 --- a/polkadot/xcm/xcm-builder/src/tests/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/tests/barriers.rs @@ -283,6 +283,56 @@ fn allow_paid_should_work() { assert_eq!(r, Ok(())) } +#[test] +fn allow_paid_should_deprivilege_origin() { + AllowPaidFrom::set(vec![Parent.into()]); + let fees = (Parent, 1).into(); + + let mut paying_message_clears_origin = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + ClearOrigin, + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_clears_origin.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Ok(())); + + let mut paying_message_aliases_origin = paying_message_clears_origin.clone(); + paying_message_aliases_origin.0[1] = AliasOrigin(Parachain(1).into()); + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_aliases_origin.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Ok(())); + + let mut paying_message_descends_origin = paying_message_clears_origin.clone(); + paying_message_descends_origin.0[1] = DescendOrigin(Parachain(1).into()); + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_descends_origin.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Ok(())); + + let mut paying_message_fake_descends_origin = paying_message_clears_origin.clone(); + paying_message_fake_descends_origin.0[1] = DescendOrigin(Here.into()); + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_fake_descends_origin.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Err(ProcessMessageError::Overweight(Weight::from_parts(30, 30)))); +} + #[test] fn suspension_should_work() { TestSuspender::set_suspended(true); diff --git a/prdoc/pr_5917.prdoc b/prdoc/pr_5917.prdoc new file mode 100644 index 00000000000..54b2e42ed9c --- /dev/null +++ b/prdoc/pr_5917.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: "XCM paid execution barrier supports more origin altering instructions" + +doc: + - audience: Runtime Dev + description: | + Updates the `AllowTopLevelPaidExecutionFrom` barrier to also support messages that + use `DescendOrigin` or `AliasOrigin` for altering the computed origin during execution. + +crates: + - name: staging-xcm-builder + bump: patch -- GitLab From 73bf37ab2593cdb03d7eeb52225a3a2503dd7c6c Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sat, 5 Oct 2024 14:39:34 +0200 Subject: [PATCH 047/127] Bridge relayer backwards compatibility for reading storage InboundLaneData/OutboundLaneData (#5921) For permissionless lanes, we add `lane_state` to the `InboundLaneData` and `OutboundLaneData` structs. However, for a period of time (until both BHK and BHP are upgraded to the same version), we need the relayer to function with runtimes where one has been migrated with `lane_state` and the other has not. This PR addresses the incompatibility by introducing wrapper structs for decoding without `lane_state`. --- .../src/messages/source.rs | 52 +++++++++- .../src/messages/target.rs | 97 ++++++++++++++++++- 2 files changed, 141 insertions(+), 8 deletions(-) diff --git a/bridges/relays/lib-substrate-relay/src/messages/source.rs b/bridges/relays/lib-substrate-relay/src/messages/source.rs index b560867a235..3e60ed7abd0 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/source.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/source.rs @@ -35,10 +35,10 @@ use bp_messages::{ storage_keys::{operating_mode_key, outbound_lane_data_key}, target_chain::FromBridgedChainMessagesProof, ChainWithMessages as _, InboundMessageDetails, MessageNonce, MessagePayload, - MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails, + MessagesOperatingMode, OutboundMessageDetails, }; use bp_runtime::{BasicOperatingMode, HeaderIdProvider, RangeInclusiveExt}; -use codec::Encode; +use codec::{Decode, Encode}; use frame_support::weights::Weight; use messages_relay::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, @@ -63,6 +63,18 @@ use std::ops::RangeInclusive; pub type SubstrateMessagesProof = (Weight, FromBridgedChainMessagesProof, L>); type MessagesToRefine<'a> = Vec<(MessagePayload, &'a mut OutboundMessageDetails)>; +/// Outbound lane data - for backwards compatibility with `bp_messages::OutboundLaneData` which has +/// additional `lane_state` attribute. +/// +/// TODO: remove - https://github.com/paritytech/polkadot-sdk/issues/5923 +#[derive(Decode)] +struct LegacyOutboundLaneData { + #[allow(unused)] + oldest_unpruned_nonce: MessageNonce, + latest_received_nonce: MessageNonce, + latest_generated_nonce: MessageNonce, +} + /// Substrate client as Substrate messages source. pub struct SubstrateMessagesSource { source_client: SourceClnt, @@ -98,7 +110,7 @@ impl, TargetClnt> async fn outbound_lane_data( &self, id: SourceHeaderIdOf>, - ) -> Result, SubstrateError> { + ) -> Result, SubstrateError> { self.source_client .storage_value( id.hash(), @@ -743,4 +755,38 @@ mod tests { Ok(vec![2, 4, 3]), ); } + + #[test] + fn outbound_lane_data_wrapper_is_compatible() { + let bytes_without_state = + vec![1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]; + let bytes_with_state = { + // add state byte `bp_messages::LaneState::Opened` + let mut b = bytes_without_state.clone(); + b.push(0); + b + }; + + let full = bp_messages::OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 2, + latest_generated_nonce: 3, + state: bp_messages::LaneState::Opened, + }; + assert_eq!(full.encode(), bytes_with_state); + assert_ne!(full.encode(), bytes_without_state); + + // decode from `bytes_with_state` + let decoded: LegacyOutboundLaneData = Decode::decode(&mut &bytes_with_state[..]).unwrap(); + assert_eq!(full.oldest_unpruned_nonce, decoded.oldest_unpruned_nonce); + assert_eq!(full.latest_received_nonce, decoded.latest_received_nonce); + assert_eq!(full.latest_generated_nonce, decoded.latest_generated_nonce); + + // decode from `bytes_without_state` + let decoded: LegacyOutboundLaneData = + Decode::decode(&mut &bytes_without_state[..]).unwrap(); + assert_eq!(full.oldest_unpruned_nonce, decoded.oldest_unpruned_nonce); + assert_eq!(full.latest_received_nonce, decoded.latest_received_nonce); + assert_eq!(full.latest_generated_nonce, decoded.latest_generated_nonce); + } } diff --git a/bridges/relays/lib-substrate-relay/src/messages/target.rs b/bridges/relays/lib-substrate-relay/src/messages/target.rs index 0d1aac88a32..214819a1c42 100644 --- a/bridges/relays/lib-substrate-relay/src/messages/target.rs +++ b/bridges/relays/lib-substrate-relay/src/messages/target.rs @@ -36,8 +36,9 @@ use async_std::sync::Arc; use async_trait::async_trait; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, storage_keys::inbound_lane_data_key, - ChainWithMessages as _, InboundLaneData, MessageNonce, UnrewardedRelayersState, + ChainWithMessages as _, LaneState, MessageNonce, UnrewardedRelayer, UnrewardedRelayersState, }; +use codec::Decode; use messages_relay::{ message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf}, message_lane_loop::{NoncesSubmitArtifacts, TargetClient, TargetClientState}, @@ -48,12 +49,52 @@ use relay_substrate_client::{ }; use relay_utils::relay_loop::Client as RelayClient; use sp_core::Pair; -use std::{convert::TryFrom, ops::RangeInclusive}; +use std::{collections::VecDeque, convert::TryFrom, ops::RangeInclusive}; /// Message receiving proof returned by the target Substrate node. pub type SubstrateMessagesDeliveryProof = (UnrewardedRelayersState, FromBridgedChainMessagesDeliveryProof, L>); +/// Inbound lane data - for backwards compatibility with `bp_messages::InboundLaneData` which has +/// additional `lane_state` attribute. +/// +/// TODO: remove - https://github.com/paritytech/polkadot-sdk/issues/5923 +#[derive(Decode)] +struct LegacyInboundLaneData { + relayers: VecDeque>, + last_confirmed_nonce: MessageNonce, +} +impl Default for LegacyInboundLaneData { + fn default() -> Self { + let full = bp_messages::InboundLaneData::default(); + Self { relayers: full.relayers, last_confirmed_nonce: full.last_confirmed_nonce } + } +} + +impl LegacyInboundLaneData { + pub fn last_delivered_nonce(self) -> MessageNonce { + bp_messages::InboundLaneData { + relayers: self.relayers, + last_confirmed_nonce: self.last_confirmed_nonce, + // we don't care about the state here + state: LaneState::Opened, + } + .last_delivered_nonce() + } +} + +impl From> for UnrewardedRelayersState { + fn from(value: LegacyInboundLaneData) -> Self { + (&bp_messages::InboundLaneData { + relayers: value.relayers, + last_confirmed_nonce: value.last_confirmed_nonce, + // we don't care about the state here + state: LaneState::Opened, + }) + .into() + } +} + /// Substrate client as Substrate messages target. pub struct SubstrateMessagesTarget { target_client: TargetClnt, @@ -94,7 +135,7 @@ where async fn inbound_lane_data( &self, id: TargetHeaderIdOf>, - ) -> Result>>, SubstrateError> { + ) -> Result>>, SubstrateError> { self.target_client .storage_value( id.hash(), @@ -217,8 +258,8 @@ where ) -> Result<(TargetHeaderIdOf>, UnrewardedRelayersState), SubstrateError> { let inbound_lane_data = - self.inbound_lane_data(id).await?.unwrap_or(InboundLaneData::default()); - Ok((id, (&inbound_lane_data).into())) + self.inbound_lane_data(id).await?.unwrap_or(LegacyInboundLaneData::default()); + Ok((id, inbound_lane_data.into())) } async fn prove_messages_receiving( @@ -321,3 +362,49 @@ fn make_messages_delivery_call( trace_call, ) } + +#[cfg(test)] +mod tests { + use super::*; + use bp_messages::{DeliveredMessages, UnrewardedRelayer}; + use codec::Encode; + + #[test] + fn inbound_lane_data_wrapper_is_compatible() { + let bytes_without_state = + vec![4, 0, 2, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0]; + let bytes_with_state = { + // add state byte `bp_messages::LaneState::Opened` + let mut b = bytes_without_state.clone(); + b.push(0); + b + }; + + let full = bp_messages::InboundLaneData:: { + relayers: vec![UnrewardedRelayer { + relayer: Default::default(), + messages: DeliveredMessages { begin: 2, end: 5 }, + }] + .into_iter() + .collect(), + last_confirmed_nonce: 6, + state: bp_messages::LaneState::Opened, + }; + assert_eq!(full.encode(), bytes_with_state); + assert_ne!(full.encode(), bytes_without_state); + + // decode from `bytes_with_state` + let decoded: LegacyInboundLaneData = + Decode::decode(&mut &bytes_with_state[..]).unwrap(); + assert_eq!(full.relayers, decoded.relayers); + assert_eq!(full.last_confirmed_nonce, decoded.last_confirmed_nonce); + assert_eq!(full.last_delivered_nonce(), decoded.last_delivered_nonce()); + + // decode from `bytes_without_state` + let decoded: LegacyInboundLaneData = + Decode::decode(&mut &bytes_without_state[..]).unwrap(); + assert_eq!(full.relayers, decoded.relayers); + assert_eq!(full.last_confirmed_nonce, decoded.last_confirmed_nonce); + assert_eq!(full.last_delivered_nonce(), decoded.last_delivered_nonce()); + } +} -- GitLab From a8ebe9af23df6c4dddddf74eb21255231331c5df Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Sat, 5 Oct 2024 15:14:14 +0200 Subject: [PATCH 048/127] [pallet-revive] immutable data storage (#5861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces the concept of immutable storage data, used for [Solidity immutable variables](https://docs.soliditylang.org/en/latest/contracts.html#immutable). This is a minimal implementation. Immutable data is attached to a contract; to keep `ContractInfo` fixed in size, we only store the length there, and store the immutable data in a dedicated storage map instead. Which comes at the cost of requiring an additional storage read (costly) for contracts using this feature. We discussed more optimal solutions not requiring any additional storage accesses internally, but they turned out to be non-trivial to implement. Another optimization benefiting multiple calls to the same contract in a single call stack would be to cache the immutable data in `Stack`. However, this potential creates a DOS vulnerability (the attack vector is to call into as many contracts in a single stack as possible, where they all have maximum immutable data to fill the cache as efficiently as possible). So this either has to be guaranteed to be a non-issue by limits, or, more likely, to have some logic to bound the cache. Eventually, we should think about introducing the concept of warm and cold storage reads (akin to EVM). Since immutable variables are commonly used in contracts, this change is blocking our initial launch and we should only optimize it properly in follow-ups. This PR also disables the `set_code_hash` API (which isn't usable for Solidity contracts without pre-compiles anyways). With immutable storage attached to contracts, we now want to run the constructor of the new code hash to collect the immutable data during `set_code_hash`. This will be implemented in a follow up PR. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen Co-authored-by: PG Herveou --- prdoc/pr_5861.prdoc | 37 + .../fixtures/contracts/immutable_data.rs | 43 + .../frame/revive/src/benchmarking/mod.rs | 47 + substrate/frame/revive/src/exec.rs | 275 +++++- substrate/frame/revive/src/lib.rs | 19 + substrate/frame/revive/src/limits.rs | 6 + substrate/frame/revive/src/storage.rs | 37 +- substrate/frame/revive/src/storage/meter.rs | 6 + substrate/frame/revive/src/test_utils.rs | 1 + substrate/frame/revive/src/tests.rs | 50 +- substrate/frame/revive/src/wasm/runtime.rs | 40 +- substrate/frame/revive/src/weights.rs | 826 ++++++++++-------- substrate/frame/revive/uapi/src/host.rs | 24 + .../frame/revive/uapi/src/host/riscv32.rs | 12 + 14 files changed, 1031 insertions(+), 392 deletions(-) create mode 100644 prdoc/pr_5861.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/immutable_data.rs diff --git a/prdoc/pr_5861.prdoc b/prdoc/pr_5861.prdoc new file mode 100644 index 00000000000..e2187dc1bdd --- /dev/null +++ b/prdoc/pr_5861.prdoc @@ -0,0 +1,37 @@ +title: "[pallet-revive] immutable data storage" + +doc: + - audience: Runtime Dev + description: | + This PR introduces the concept of immutable storage data, used for + [Solidity immutable variables](https://docs.soliditylang.org/en/latest/contracts.html#immutable). + + This is a minimal implementation. Immutable data is attached to a contract; to + `ContractInfo` fixed in size, we only store the length there, and store the immutable + data in a dedicated storage map instead. Which comes at the cost of requiring an + storage read (costly) for contracts using this feature. + + We discussed more optimal solutions not requiring any additional storage accesses + internally, but they turned out to be non-trivial to implement. Another optimization + benefiting multiple calls to the same contract in a single call stack would be to cache + the immutable data in `Stack`. However, this potential creates a DOS vulnerability (the + attack vector is to call into as many contracts in a single stack as possible, where + they all have maximum immutable data to fill the cache as efficiently as possible). So + this either has to be guaranteed to be a non-issue by limits, or, more likely, to have + some logic to bound the cache. Eventually, we should think about introducing the concept + of warm and cold storage reads (akin to EVM). Since immutable variables are commonly + used in contracts, this change is blocking our initial launch and we should only + optimize it properly in follow-ups. + + This PR also disables the `set_code_hash` API (which isn't usable for Solidity contracts + without pre-compiles anyways). With immutable storage attached to contracts, we now want + to run the constructor of the new code hash to collect the immutable data during + `set_code_hash`. This will be implemented in a follow up PR. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/immutable_data.rs b/substrate/frame/revive/fixtures/contracts/immutable_data.rs new file mode 100644 index 00000000000..ac50e61a400 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/immutable_data.rs @@ -0,0 +1,43 @@ +// 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. + +//! Tests that the `get_immutable_data` and `set_immutable_data` APIs work. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + input!(data: &[u8; 8],); + + api::set_immutable_data(data); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(data: &[u8; 8],); + + let mut buf = [0; 8]; + api::get_immutable_data(&mut &mut buf[..]); + + assert_eq!(data, &buf); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index acca876f376..ebafb6c7054 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -628,6 +628,53 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..len]), runtime.ext().balance_of(&address)); } + #[benchmark(pov_mode = Measured)] + fn seal_get_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) { + let len = n as usize; + let immutable_data = vec![1u8; len]; + + build_runtime!(runtime, contract, memory: [(len as u32).encode(), vec![0u8; len],]); + + >::insert::<_, BoundedVec<_, _>>( + contract.address(), + immutable_data.clone().try_into().unwrap(), + ); + + let result; + #[block] + { + result = runtime.bench_get_immutable_data(memory.as_mut_slice(), 4, 0 as u32); + } + + assert_ok!(result); + assert_eq!(&memory[0..4], (len as u32).encode()); + assert_eq!(&memory[4..len + 4], &immutable_data); + } + + #[benchmark(pov_mode = Measured)] + fn seal_set_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) { + let len = n as usize; + let mut memory = vec![1u8; len]; + let mut setup = CallSetup::::default(); + let input = setup.data(); + let (mut ext, _) = setup.ext(); + ext.override_export(crate::debug::ExportedFunction::Constructor); + + let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); + + let result; + #[block] + { + result = runtime.bench_set_immutable_data(memory.as_mut_slice(), 0, n); + } + + assert_ok!(result); + assert_eq!( + &memory[..], + &>::get(setup.contract().address()).unwrap()[..] + ); + } + #[benchmark(pov_mode = Measured)] fn seal_value_transferred() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 2e48bab2925..c6d2f205ae2 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -25,7 +25,7 @@ use crate::{ storage::{self, meter::Diff, WriteOutcome}, transient_storage::TransientStorage, BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error, - Event, Pallet as Contracts, LOG_TARGET, + Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -296,6 +296,18 @@ pub trait Ext: sealing::Sealed { ::AddressMapper::to_address(self.account_id()) } + /// Returns the immutable data of the current contract. + /// + /// Returns `Err(InvalidImmutableAccess)` if called from a constructor. + fn get_immutable_data(&mut self) -> Result; + + /// Set the the immutable data of the current contract. + /// + /// Returns `Err(InvalidImmutableAccess)` if not called from a constructor. + /// + /// Note: Requires &mut self to access the contract info. + fn set_immutable_data(&mut self, data: ImmutableData) -> Result<(), DispatchError>; + /// Returns the balance of the current contract. /// /// The `value_transferred` is already added. @@ -373,7 +385,7 @@ pub trait Ext: sealing::Sealed { #[cfg(feature = "runtime-benchmarks")] fn transient_storage(&mut self) -> &mut TransientStorage; - /// Sets new code hash for existing contract. + /// Sets new code hash and immutable data for an existing contract. fn set_code_hash(&mut self, hash: H256) -> DispatchResult; /// Returns the number of times the specified contract exists on the call stack. Delegated calls @@ -1286,6 +1298,13 @@ where fn account_balance(&self, who: &T::AccountId) -> U256 { T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Polite).into() } + + /// Certain APIs, e.g. `{set,get}_immutable_data` behave differently depending + /// on the configured entry point. Thus, we allow setting the export manually. + #[cfg(all(feature = "runtime-benchmarks", feature = "riscv"))] + pub(crate) fn override_export(&mut self, export: ExportedFunction) { + self.top_frame_mut().entry_point = export; + } } impl<'a, T, E> Ext for Stack<'a, T, E> @@ -1418,6 +1437,7 @@ where info.queue_trie_for_deletion(); let account_address = T::AddressMapper::to_address(&frame.account_id); ContractInfoOf::::remove(&account_address); + ImmutableDataOf::::remove(&account_address); Self::decrement_refcount(info.code_hash); for (code_hash, deposit) in info.delegate_dependencies() { @@ -1521,6 +1541,30 @@ where self.caller_is_origin() && self.origin == Origin::Root } + fn get_immutable_data(&mut self) -> Result { + if self.top_frame().entry_point == ExportedFunction::Constructor { + return Err(Error::::InvalidImmutableAccess.into()); + } + + let address = T::AddressMapper::to_address(self.account_id()); + Ok(>::get(address).ok_or_else(|| Error::::InvalidImmutableAccess)?) + } + + fn set_immutable_data(&mut self, data: ImmutableData) -> Result<(), DispatchError> { + if self.top_frame().entry_point == ExportedFunction::Call { + return Err(Error::::InvalidImmutableAccess.into()); + } + + let account_id = self.account_id().clone(); + let len = data.len() as u32; + let amount = self.top_frame_mut().contract_info().set_immutable_data_len(len)?; + self.top_frame_mut().nested_storage.charge_deposit(account_id.clone(), amount); + + >::insert(T::AddressMapper::to_address(&account_id), &data); + + Ok(()) + } + fn balance(&self) -> U256 { self.account_balance(&self.top_frame().account_id) } @@ -1627,6 +1671,21 @@ where &mut self.transient_storage } + /// TODO: This should be changed to run the constructor of the supplied `hash`. + /// + /// Because the immutable data is attached to a contract and not a code, + /// we need to update the immutable data too. + /// + /// Otherwise we open a massive footgun: + /// If the immutables changed in the new code, the contract will brick. + /// + /// A possible implementation strategy is to add a flag to `FrameArgs::Instantiate`, + /// so that `fn run()` will roll back any changes if this flag is set. + /// + /// After running the constructor, the new immutable data is already stored in + /// `self.immutable_data` at the address of the (reverted) contract instantiation. + /// + /// The `set_code_hash` contract API stays disabled until this change is implemented. fn set_code_hash(&mut self, hash: H256) -> DispatchResult { let frame = top_frame_mut!(self); @@ -4280,4 +4339,216 @@ mod tests { assert_matches!(result, Ok(_)); }); } + + #[test] + fn immutable_data_access_checks_work() { + let dummy_ch = MockLoader::insert(Constructor, move |ctx, _| { + // Calls can not store immutable data + assert_eq!( + ctx.ext.get_immutable_data(), + Err(Error::::InvalidImmutableAccess.into()) + ); + exec_success() + }); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + let value = ::Currency::minimum_balance().into(); + + assert_eq!( + ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap()), + Err(Error::::InvalidImmutableAccess.into()) + ); + + // Constructors can not access the immutable data + ctx.ext + .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .unwrap(); + + exec_success() + } + }); + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB_CONTRACT_ID, 100); + place_contract(&BOB, instantiator_ch); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } + + #[test] + fn correct_immutable_data_in_delegate_call() { + let charlie_ch = MockLoader::insert(Call, |ctx, _| { + Ok(ExecReturnValue { + flags: ReturnFlags::empty(), + data: ctx.ext.get_immutable_data()?.to_vec(), + }) + }); + let bob_ch = MockLoader::insert(Call, move |ctx, _| { + // In a regular call, we should witness the callee immutable data + assert_eq!( + ctx.ext + .call( + Weight::zero(), + U256::zero(), + &CHARLIE_ADDR, + U256::zero(), + vec![], + true, + false, + ) + .map(|_| ctx.ext.last_frame_output().data.clone()), + Ok(vec![2]), + ); + + // In a delegate call, we should witness the caller immutable data + assert_eq!( + ctx.ext.delegate_call(charlie_ch, Vec::new()).map(|_| ctx + .ext + .last_frame_output() + .data + .clone()), + Ok(vec![1]) + ); + + exec_success() + }); + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + place_contract(&BOB, bob_ch); + place_contract(&CHARLIE, charlie_ch); + + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + // Place unique immutable data for each contract + >::insert::<_, ImmutableData>( + BOB_ADDR, + vec![1].try_into().unwrap(), + ); + >::insert::<_, ImmutableData>( + CHARLIE_ADDR, + vec![2].try_into().unwrap(), + ); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } + + #[test] + fn immutable_data_set_works_only_once() { + let dummy_ch = MockLoader::insert(Constructor, move |ctx, _| { + // Calling `set_immutable_data` the first time should work + assert_ok!(ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap())); + // Calling `set_immutable_data` the second time should error out + assert_eq!( + ctx.ext.set_immutable_data(vec![0, 1, 2, 3].try_into().unwrap()), + Err(Error::::InvalidImmutableAccess.into()) + ); + exec_success() + }); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + let value = ::Currency::minimum_balance().into(); + ctx.ext + .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .unwrap(); + + exec_success() + } + }); + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB_CONTRACT_ID, 100); + place_contract(&BOB, instantiator_ch); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } + + #[test] + fn immutable_data_set_errors_with_empty_data() { + let dummy_ch = MockLoader::insert(Constructor, move |ctx, _| { + // Calling `set_immutable_data` with empty data should error out + assert_eq!( + ctx.ext.set_immutable_data(Default::default()), + Err(Error::::InvalidImmutableAccess.into()) + ); + exec_success() + }); + let instantiator_ch = MockLoader::insert(Call, { + move |ctx, _| { + let value = ::Currency::minimum_balance().into(); + ctx.ext + .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .unwrap(); + + exec_success() + } + }); + ExtBuilder::default() + .with_code_hashes(MockLoader::code_hashes()) + .existential_deposit(15) + .build() + .execute_with(|| { + set_balance(&ALICE, 1000); + set_balance(&BOB_CONTRACT_ID, 100); + place_contract(&BOB, instantiator_ch); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 200, 0).unwrap(); + + MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ) + .unwrap() + }); + } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 91c5fa5563a..6d87fd904c3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -94,6 +94,7 @@ type CodeVec = BoundedVec>; type EventRecordOf = EventRecord<::RuntimeEvent, ::Hash>; type DebugBuffer = BoundedVec>; +type ImmutableData = BoundedVec>; /// Used as a sentinel value when reading and writing contract memory. /// @@ -550,6 +551,9 @@ pub mod pallet { ExecutionFailed, /// Failed to convert a U256 to a Balance. BalanceConversionFailed, + /// Immutable data can only be set during deploys and only be read during calls. + /// Additionally, it is only valid to set the data once and it must not be empty. + InvalidImmutableAccess, } /// A reason for the pallet contracts placing a hold on funds. @@ -573,6 +577,10 @@ pub mod pallet { #[pallet::storage] pub(crate) type ContractInfoOf = StorageMap<_, Identity, H160, ContractInfo>; + /// The immutable data associated with a given account. + #[pallet::storage] + pub(crate) type ImmutableDataOf = StorageMap<_, Identity, H160, ImmutableData>; + /// Evicted contracts that await child trie deletion. /// /// Child trie deletion is a heavy operation depending on the amount of storage items @@ -665,6 +673,16 @@ pub mod pallet { .hash() .len() as u32; + let max_immutable_key_size = T::AccountId::max_encoded_len() as u32; + let max_immutable_size: u32 = ((max_block_ref_time / + (>::weight(&RuntimeCosts::SetImmutableData( + limits::IMMUTABLE_BYTES, + )) + .ref_time())) + .saturating_mul(limits::IMMUTABLE_BYTES.saturating_add(max_immutable_key_size) as u64)) + .try_into() + .expect("Immutable data size too big"); + // We can use storage to store items using the available block ref_time with the // `set_storage` host function. let max_storage_size: u32 = ((max_block_ref_time / @@ -674,6 +692,7 @@ pub mod pallet { }) .ref_time())) .saturating_mul(max_payload_size.saturating_add(max_key_size) as u64)) + .saturating_add(max_immutable_size.into()) .try_into() .expect("Storage size too big"); diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index f712493d3bc..50cdba6d0c9 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -65,6 +65,12 @@ pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; /// The page size in which PolkaVM should allocate memory chunks. pub const PAGE_SIZE: u32 = 4 * 1024; +/// The maximum amount of immutable bytes a single contract can store. +/// +/// The current limit of 4kb allows storing up 16 U256 immutable variables. +/// Which should always be enough because Solidity allows for 16 local (stack) variables. +pub const IMMUTABLE_BYTES: u32 = 4 * 1024; + /// Limits that are only enforced on code upload. /// /// # Note diff --git a/substrate/frame/revive/src/storage.rs b/substrate/frame/revive/src/storage.rs index ef7ce2db32c..db4db3e8eac 100644 --- a/substrate/frame/revive/src/storage.rs +++ b/substrate/frame/revive/src/storage.rs @@ -26,7 +26,7 @@ use crate::{ storage::meter::Diff, weights::WeightInfo, BalanceOf, CodeInfo, Config, ContractInfoOf, DeletionQueue, DeletionQueueCounter, Error, - TrieId, SENTINEL, + StorageDeposit, TrieId, SENTINEL, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -36,6 +36,7 @@ use frame_support::{ weights::{Weight, WeightMeter}, CloneNoBound, DefaultNoBound, }; +use meter::DepositOf; use scale_info::TypeInfo; use sp_core::{ConstU32, Get, H160}; use sp_io::KillStorageResult; @@ -75,6 +76,8 @@ pub struct ContractInfo { /// to the map can not be removed from the chain state and can be safely used for delegate /// calls. delegate_dependencies: DelegateDependencyMap, + /// The size of the immutable data of this contract. + immutable_data_len: u32, } impl ContractInfo { @@ -88,7 +91,7 @@ impl ContractInfo { code_hash: sp_core::H256, ) -> Result { if >::contains_key(address) { - return Err(Error::::DuplicateContract.into()) + return Err(Error::::DuplicateContract.into()); } let trie_id = { @@ -108,6 +111,7 @@ impl ContractInfo { storage_item_deposit: Zero::zero(), storage_base_deposit: Zero::zero(), delegate_dependencies: Default::default(), + immutable_data_len: 0, }; Ok(contract) @@ -356,6 +360,35 @@ impl ContractInfo { pub fn load_code_hash(account: &AccountIdOf) -> Option { >::get(&T::AddressMapper::to_address(account)).map(|i| i.code_hash) } + + /// Returns the amount of immutable bytes of this contract. + pub fn immutable_data_len(&self) -> u32 { + self.immutable_data_len + } + + /// Set the number of immutable bytes of this contract. + /// + /// On success, returns the storage deposit to be charged. + /// + /// Returns `Err(InvalidImmutableAccess)` if: + /// - The immutable bytes of this contract are not 0. This indicates that the immutable data + /// have already been set; it is only valid to set the immutable data exactly once. + /// - The provided `immutable_data_len` value was 0; it is invalid to set empty immutable data. + pub fn set_immutable_data_len( + &mut self, + immutable_data_len: u32, + ) -> Result, DispatchError> { + if self.immutable_data_len != 0 || immutable_data_len == 0 { + return Err(Error::::InvalidImmutableAccess.into()); + } + + self.immutable_data_len = immutable_data_len; + + let amount = T::DepositPerByte::get() + .saturating_mul(immutable_data_len.into()) + .saturating_add(T::DepositPerItem::get()); + Ok(StorageDeposit::Charge(amount)) + } } /// Information about what happened to the pre-existing value when calling [`ContractInfo::write`]. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index a2ece03f9aa..712010bc825 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -674,6 +674,7 @@ mod tests { items: u32, bytes_deposit: BalanceOf, items_deposit: BalanceOf, + immutable_data_len: u32, } fn new_info(info: StorageInfo) -> ContractInfo { @@ -686,6 +687,7 @@ mod tests { storage_item_deposit: info.items_deposit, storage_base_deposit: Default::default(), delegate_dependencies: Default::default(), + immutable_data_len: info.immutable_data_len, } } @@ -773,6 +775,7 @@ mod tests { items: 5, bytes_deposit: 100, items_deposit: 10, + immutable_data_len: 0, }); let mut nested0 = meter.nested(BalanceOf::::zero()); nested0.charge(&Diff { @@ -788,6 +791,7 @@ mod tests { items: 10, bytes_deposit: 100, items_deposit: 20, + immutable_data_len: 0, }); let mut nested1 = nested0.nested(BalanceOf::::zero()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); @@ -798,6 +802,7 @@ mod tests { items: 7, bytes_deposit: 100, items_deposit: 20, + immutable_data_len: 0, }); let mut nested2 = nested0.nested(BalanceOf::::zero()); nested2.charge(&Diff { items_removed: 7, ..Default::default() }); @@ -867,6 +872,7 @@ mod tests { items: 10, bytes_deposit: 100, items_deposit: 20, + immutable_data_len: 0, }); let mut nested1 = nested0.nested(BalanceOf::::zero()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); diff --git a/substrate/frame/revive/src/test_utils.rs b/substrate/frame/revive/src/test_utils.rs index 671efebdf4b..92c21297a3e 100644 --- a/substrate/frame/revive/src/test_utils.rs +++ b/substrate/frame/revive/src/test_utils.rs @@ -54,6 +54,7 @@ pub const BOB_CONTRACT_ID: AccountId32 = ee_suffix(BOB_ADDR); pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); pub const CHARLIE_ADDR: H160 = H160([3u8; 20]); +pub const CHARLIE_CONTRACT_ID: AccountId32 = ee_suffix(CHARLIE_ADDR); pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); pub const DJANGO_ADDR: H160 = H160([4u8; 20]); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 5c5d144f24a..c7d1a8b2cf0 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -140,9 +140,18 @@ pub mod test_utils { pub fn contract_info_storage_deposit(addr: &H160) -> BalanceOf { let contract_info = self::get_contract(&addr); let info_size = contract_info.encoded_size() as u64; - DepositPerByte::get() + let info_deposit = DepositPerByte::get() .saturating_mul(info_size) - .saturating_add(DepositPerItem::get()) + .saturating_add(DepositPerItem::get()); + let immutable_size = contract_info.immutable_data_len() as u64; + if immutable_size > 0 { + let immutable_deposit = DepositPerByte::get() + .saturating_mul(immutable_size) + .saturating_add(DepositPerItem::get()); + info_deposit.saturating_add(immutable_deposit) + } else { + info_deposit + } } pub fn expected_deposit(code_len: usize) -> u64 { // For code_info, the deposit for max_encoded_len is taken. @@ -3530,8 +3539,11 @@ mod run_tests { // Set enough deposit limit for the child instantiate. This should succeed. let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 4) - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3)).encode()) + .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) + .data( + (1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)) + .encode(), + ) .build(); let returned = result.result.unwrap(); @@ -3548,6 +3560,7 @@ mod run_tests { // - callee instantiation deposit = (callee_info_len + 2) // - callee account ED // - for writing an item of 1 byte to storage = 3 Balance + // - Immutable data storage item deposit assert_eq!( ::Currency::free_balance(&BOB), 1_000_000 - (callee_info_len + 2 + ED + 3) @@ -4354,4 +4367,33 @@ mod run_tests { .build()); }); } + + #[test] + fn immutable_data_works() { + let (code, _) = compile_module("immutable_data").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let data = [0xfe; 8]; + + // Create fixture: Constructor sets the immtuable data + let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(code)) + .data(data.to_vec()) + .build_and_unwrap_contract(); + + // Storing immmutable data charges storage deposit; verify it explicitly. + assert_eq!( + test_utils::get_balance_on_hold( + &HoldReason::StorageDepositReserve.into(), + &::AddressMapper::to_account_id(&addr) + ), + test_utils::contract_info_storage_deposit(&addr) + ); + assert_eq!(test_utils::get_contract(&addr).immutable_data_len(), data.len() as u32); + + // Call the contract: Asserts the input to equal the immutable data + assert_ok!(builder::call(addr).data(data.to_vec()).build()); + }); + } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 56e3b685dcf..485abbfda22 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -390,6 +390,10 @@ pub enum RuntimeCosts { LockDelegateDependency, /// Weight of calling `unlock_delegate_dependency` UnlockDelegateDependency, + /// Weight of calling `get_immutable_dependency` + GetImmutableData(u32), + /// Weight of calling `set_immutable_dependency` + SetImmutableData(u32), } /// For functions that modify storage, benchmarks are performed with one item in the @@ -507,6 +511,8 @@ impl Token for RuntimeCosts { EcdsaToEthAddress => T::WeightInfo::seal_ecdsa_to_eth_address(), LockDelegateDependency => T::WeightInfo::lock_delegate_dependency(), UnlockDelegateDependency => T::WeightInfo::unlock_delegate_dependency(), + GetImmutableData(len) => T::WeightInfo::seal_get_immutable_data(len), + SetImmutableData(len) => T::WeightInfo::seal_set_immutable_data(len), } } } @@ -1513,6 +1519,36 @@ pub mod env { )?) } + /// Stores the immutable data into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. + #[api_version(0)] + fn get_immutable_data( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let charged = self.charge_gas(RuntimeCosts::GetImmutableData(limits::IMMUTABLE_BYTES))?; + let data = self.ext.get_immutable_data()?; + self.adjust_gas(charged, RuntimeCosts::GetImmutableData(data.len() as u32)); + self.write_sandbox_output(memory, out_ptr, out_len_ptr, &data, false, already_charged)?; + Ok(()) + } + + /// Attaches the supplied immutable data to the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::set_immutable_data`]. + #[api_version(0)] + fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> { + if len > limits::IMMUTABLE_BYTES { + return Err(Error::::OutOfBounds.into()); + } + self.charge_gas(RuntimeCosts::SetImmutableData(len))?; + let buf = memory.read(ptr, len)?; + let data = buf.try_into().expect("bailed out earlier; qed"); + self.ext.set_immutable_data(data)?; + Ok(()) + } + /// Stores the *free* balance of the current account into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. #[api_version(0)] @@ -1930,7 +1966,9 @@ pub mod env { /// Replace the contract code at the specified address with new code. /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. - #[api_version(0)] + /// + /// Disabled until the internal implementation takes care of collecting + /// the immutable data of the new code hash. #[mutating] fn set_code_hash( &mut self, diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 6586cd1fc9c..5e549de3a9f 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-10-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-10-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-jniz7bxx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` @@ -69,6 +69,8 @@ pub trait WeightInfo { fn seal_weight_left() -> Weight; fn seal_balance() -> Weight; fn seal_balance_of() -> Weight; + fn seal_get_immutable_data(n: u32, ) -> Weight; + fn seal_set_immutable_data(n: u32, ) -> Weight; fn seal_value_transferred() -> Weight; fn seal_minimum_balance() -> Weight; fn seal_block_number() -> Weight; @@ -124,8 +126,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_783_000 picoseconds. - Weight::from_parts(2_883_000, 1594) + // Minimum execution time: 2_700_000 picoseconds. + Weight::from_parts(2_882_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -135,10 +137,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `392 + k * (69 ±0)` // Estimated: `382 + k * (70 ±0)` - // Minimum execution time: 13_394_000 picoseconds. - Weight::from_parts(305_292, 382) - // Standard Error: 1_217 - .saturating_add(Weight::from_parts(1_233_492, 0).saturating_mul(k.into())) + // Minimum execution time: 13_819_000 picoseconds. + Weight::from_parts(14_021_000, 382) + // Standard Error: 843 + .saturating_add(Weight::from_parts(1_222_715, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -146,7 +148,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -158,10 +160,10 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `834` - // Estimated: `4299` - // Minimum execution time: 78_977_000 picoseconds. - Weight::from_parts(81_687_641, 4299) + // Measured: `838` + // Estimated: `4303` + // Minimum execution time: 79_748_000 picoseconds. + Weight::from_parts(82_727_773, 4303) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -170,7 +172,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Balances::Holds` (r:2 w:2) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) @@ -183,12 +185,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `303` // Estimated: `6232` - // Minimum execution time: 179_690_000 picoseconds. - Weight::from_parts(149_042_544, 6232) + // Minimum execution time: 179_270_000 picoseconds. + Weight::from_parts(149_933_578, 6232) // Standard Error: 11 - .saturating_add(Weight::from_parts(33, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_644, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_675, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -197,7 +199,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) @@ -209,15 +211,15 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `657` // Estimated: `4111` - // Minimum execution time: 156_389_000 picoseconds. - Weight::from_parts(130_603_882, 4111) + // Minimum execution time: 156_112_000 picoseconds. + Weight::from_parts(132_715_336, 4111) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_594, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_599, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -228,10 +230,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `834` - // Estimated: `4299` - // Minimum execution time: 80_108_000 picoseconds. - Weight::from_parts(81_555_000, 4299) + // Measured: `838` + // Estimated: `4303` + // Minimum execution time: 82_111_000 picoseconds. + Weight::from_parts(83_659_000, 4303) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -246,8 +248,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 49_297_000 picoseconds. - Weight::from_parts(50_873_587, 3574) + // Minimum execution time: 49_154_000 picoseconds. + Weight::from_parts(50_856_127, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -261,21 +263,21 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 42_556_000 picoseconds. - Weight::from_parts(43_708_000, 3750) + // Minimum execution time: 41_347_000 picoseconds. + Weight::from_parts(42_272_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `491` - // Estimated: `6431` - // Minimum execution time: 24_623_000 picoseconds. - Weight::from_parts(26_390_000, 6431) + // Measured: `495` + // Estimated: `6435` + // Minimum execution time: 25_234_000 picoseconds. + Weight::from_parts(26_339_000, 6435) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -284,79 +286,79 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_590_000 picoseconds. - Weight::from_parts(8_005_868, 0) - // Standard Error: 396 - .saturating_add(Weight::from_parts(203_612, 0).saturating_mul(r.into())) + // Minimum execution time: 7_509_000 picoseconds. + Weight::from_parts(9_047_452, 0) + // Standard Error: 165 + .saturating_add(Weight::from_parts(197_103, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 285_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(300_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: // Measured: `272` // Estimated: `3737` - // Minimum execution time: 6_711_000 picoseconds. - Weight::from_parts(7_103_000, 3737) + // Minimum execution time: 6_537_000 picoseconds. + Weight::from_parts(7_024_000, 3737) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `365` - // Estimated: `3830` - // Minimum execution time: 7_572_000 picoseconds. - Weight::from_parts(7_888_000, 3830) + // Measured: `369` + // Estimated: `3834` + // Minimum execution time: 7_571_000 picoseconds. + Weight::from_parts(8_060_000, 3834) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(264_000, 0) + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(281_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 324_000 picoseconds. - Weight::from_parts(356_000, 0) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. + // Minimum execution time: 240_000 picoseconds. Weight::from_parts(286_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 255_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 198_000 picoseconds. + Weight::from_parts(262_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 610_000 picoseconds. - Weight::from_parts(701_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(665_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_207_000 picoseconds. - Weight::from_parts(5_403_000, 0) + // Minimum execution time: 5_393_000 picoseconds. + Weight::from_parts(5_688_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -364,37 +366,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_829_000 picoseconds. - Weight::from_parts(3_977_000, 3517) + // Minimum execution time: 3_907_000 picoseconds. + Weight::from_parts(4_154_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) + /// The range of component `n` is `[1, 4096]`. + fn seal_get_immutable_data(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `205 + n * (1 ±0)` + // Estimated: `3670 + n * (1 ±0)` + // Minimum execution time: 5_700_000 picoseconds. + Weight::from_parts(6_362_270, 3670) + // Standard Error: 4 + .saturating_add(Weight::from_parts(749, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) + /// The range of component `n` is `[1, 4096]`. + fn seal_set_immutable_data(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_041_000 picoseconds. + Weight::from_parts(2_216_694, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(620, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 245_000 picoseconds. - Weight::from_parts(266_000, 0) + // Minimum execution time: 229_000 picoseconds. + Weight::from_parts(261_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 256_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(270_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(278_000, 0) + // Minimum execution time: 215_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(272_000, 0) + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(271_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -402,8 +431,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 5_467_000 picoseconds. - Weight::from_parts(5_607_000, 1552) + // Minimum execution time: 5_438_000 picoseconds. + Weight::from_parts(5_695_000, 1552) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `n` is `[0, 262140]`. @@ -411,8 +440,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 438_000 picoseconds. - Weight::from_parts(532_907, 0) + // Minimum execution time: 405_000 picoseconds. + Weight::from_parts(539_737, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } @@ -421,8 +450,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(629_625, 0) + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(539_301, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) } @@ -432,18 +461,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::DeletionQueue` (r:0 w:1) /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `272 + n * (88 ±0)` // Estimated: `3738 + n * (2563 ±0)` - // Minimum execution time: 14_997_000 picoseconds. - Weight::from_parts(17_752_993, 3738) - // Standard Error: 9_865 - .saturating_add(Weight::from_parts(4_159_693, 0).saturating_mul(n.into())) + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(18_759_349, 3738) + // Standard Error: 10_298 + .saturating_add(Weight::from_parts(4_194_635, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } @@ -453,20 +484,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_277_000 picoseconds. - Weight::from_parts(4_023_910, 0) - // Standard Error: 2_210 - .saturating_add(Weight::from_parts(202_823, 0).saturating_mul(t.into())) - // Standard Error: 19 - .saturating_add(Weight::from_parts(1_141, 0).saturating_mul(n.into())) + // Minimum execution time: 4_341_000 picoseconds. + Weight::from_parts(4_292_048, 0) + // Standard Error: 2_476 + .saturating_add(Weight::from_parts(193_480, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(959, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 309_000 picoseconds. - Weight::from_parts(834_030, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(758_020, 0) // Standard Error: 1 .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) } @@ -476,8 +507,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_705_000 picoseconds. - Weight::from_parts(7_923_000, 744) + // Minimum execution time: 7_944_000 picoseconds. + Weight::from_parts(8_358_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -486,8 +517,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_510_000 picoseconds. - Weight::from_parts(45_840_000, 10754) + // Minimum execution time: 44_950_000 picoseconds. + Weight::from_parts(45_575_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -496,8 +527,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_842_000 picoseconds. - Weight::from_parts(9_363_000, 744) + // Minimum execution time: 9_095_000 picoseconds. + Weight::from_parts(9_484_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -507,8 +538,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 46_172_000 picoseconds. - Weight::from_parts(47_586_000, 10754) + // Minimum execution time: 46_814_000 picoseconds. + Weight::from_parts(47_710_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -520,12 +551,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_158_000 picoseconds. - Weight::from_parts(9_708_320, 247) - // Standard Error: 36 - .saturating_add(Weight::from_parts(499, 0).saturating_mul(n.into())) - // Standard Error: 36 - .saturating_add(Weight::from_parts(672, 0).saturating_mul(o.into())) + // Minimum execution time: 9_496_000 picoseconds. + Weight::from_parts(9_875_533, 247) + // Standard Error: 34 + .saturating_add(Weight::from_parts(712, 0).saturating_mul(n.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(777, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -537,10 +568,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_885_000 picoseconds. - Weight::from_parts(9_597_656, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(649, 0).saturating_mul(n.into())) + // Minimum execution time: 9_185_000 picoseconds. + Weight::from_parts(9_912_715, 247) + // Standard Error: 59 + .saturating_add(Weight::from_parts(567, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -552,10 +583,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_473_000 picoseconds. - Weight::from_parts(9_246_006, 247) - // Standard Error: 47 - .saturating_add(Weight::from_parts(1_468, 0).saturating_mul(n.into())) + // Minimum execution time: 8_586_000 picoseconds. + Weight::from_parts(9_536_041, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -566,10 +597,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_996_000 picoseconds. - Weight::from_parts(8_784_165, 247) + // Minimum execution time: 8_162_000 picoseconds. + Weight::from_parts(8_813_128, 247) // Standard Error: 43 - .saturating_add(Weight::from_parts(591, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(880, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -580,10 +611,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_246_000 picoseconds. - Weight::from_parts(10_239_803, 247) - // Standard Error: 57 - .saturating_add(Weight::from_parts(1_305, 0).saturating_mul(n.into())) + // Minimum execution time: 9_717_000 picoseconds. + Weight::from_parts(10_635_621, 247) + // Standard Error: 58 + .saturating_add(Weight::from_parts(1_430, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -592,36 +623,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_422_000 picoseconds. - Weight::from_parts(1_531_000, 0) + // Minimum execution time: 1_503_000 picoseconds. + Weight::from_parts(1_592_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_858_000 picoseconds. - Weight::from_parts(1_944_000, 0) + // Minimum execution time: 1_903_000 picoseconds. + Weight::from_parts(1_996_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_443_000 picoseconds. - Weight::from_parts(1_506_000, 0) + // Minimum execution time: 1_432_000 picoseconds. + Weight::from_parts(1_534_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_592_000 picoseconds. - Weight::from_parts(1_651_000, 0) + // Minimum execution time: 1_606_000 picoseconds. + Weight::from_parts(1_669_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_060_000, 0) + Weight::from_parts(1_089_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -629,62 +660,62 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_151_000 picoseconds. - Weight::from_parts(2_294_801, 0) + // Minimum execution time: 2_317_000 picoseconds. + Weight::from_parts(2_449_933, 0) // Standard Error: 11 - .saturating_add(Weight::from_parts(375, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) // Standard Error: 11 - .saturating_add(Weight::from_parts(415, 0).saturating_mul(o.into())) + .saturating_add(Weight::from_parts(387, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_880_000 picoseconds. - Weight::from_parts(2_259_773, 0) + // Minimum execution time: 1_980_000 picoseconds. + Weight::from_parts(2_352_243, 0) // Standard Error: 17 - .saturating_add(Weight::from_parts(356, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(372, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_755_000 picoseconds. - Weight::from_parts(1_968_235, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + // Minimum execution time: 1_857_000 picoseconds. + Weight::from_parts(2_082_050, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_618_000 picoseconds. - Weight::from_parts(1_811_972, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(174, 0).saturating_mul(n.into())) + // Minimum execution time: 1_693_000 picoseconds. + Weight::from_parts(1_892_600, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(193, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_482_000 picoseconds. - Weight::from_parts(2_682_484, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(n.into())) + // Minimum execution time: 2_620_000 picoseconds. + Weight::from_parts(2_818_388, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 9_899_000 picoseconds. - Weight::from_parts(10_342_000, 0) + // Minimum execution time: 10_207_000 picoseconds. + Weight::from_parts(10_627_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -693,12 +724,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `626 + t * (140 ±0)` - // Estimated: `4091 + t * (140 ±0)` - // Minimum execution time: 33_645_000 picoseconds. - Weight::from_parts(34_407_662, 4091) - // Standard Error: 36_930 - .saturating_add(Weight::from_parts(2_062_425, 0).saturating_mul(t.into())) + // Measured: `630 + t * (140 ±0)` + // Estimated: `4095 + t * (140 ±0)` + // Minimum execution time: 34_452_000 picoseconds. + Weight::from_parts(34_837_900, 4095) + // Standard Error: 30_385 + .saturating_add(Weight::from_parts(1_981_565, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) @@ -713,8 +744,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `457` // Estimated: `3922` - // Minimum execution time: 26_924_000 picoseconds. - Weight::from_parts(27_753_000, 3922) + // Minimum execution time: 27_287_000 picoseconds. + Weight::from_parts(28_450_000, 3922) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -722,7 +753,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. @@ -730,10 +761,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `703` // Estimated: `4160` - // Minimum execution time: 117_979_000 picoseconds. - Weight::from_parts(105_415_117, 4160) + // Minimum execution time: 118_660_000 picoseconds. + Weight::from_parts(104_638_796, 4160) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_293, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_296, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -742,64 +773,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 620_000 picoseconds. - Weight::from_parts(3_414_286, 0) + // Minimum execution time: 683_000 picoseconds. + Weight::from_parts(3_836_563, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_463, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_043_000 picoseconds. - Weight::from_parts(3_402_639, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(3_667, 0).saturating_mul(n.into())) + // Minimum execution time: 1_082_000 picoseconds. + Weight::from_parts(5_027_764, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(3_644, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 642_000 picoseconds. - Weight::from_parts(3_359_294, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(3_585_640, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_590, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_586, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 606_000 picoseconds. - Weight::from_parts(3_789_868, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_575, 0).saturating_mul(n.into())) + // Minimum execution time: 638_000 picoseconds. + Weight::from_parts(3_763_242, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_582, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_195_000 picoseconds. - Weight::from_parts(31_420_941, 0) + // Minimum execution time: 42_962_000 picoseconds. + Weight::from_parts(27_938_396, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_165, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_269, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_933_000 picoseconds. - Weight::from_parts(48_054_000, 0) + // Minimum execution time: 47_133_000 picoseconds. + Weight::from_parts(48_458_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_531_000 picoseconds. - Weight::from_parts(12_690_000, 0) + // Minimum execution time: 13_249_000 picoseconds. + Weight::from_parts(13_518_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -807,8 +838,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `266` // Estimated: `3731` - // Minimum execution time: 14_694_000 picoseconds. - Weight::from_parts(15_032_000, 3731) + // Minimum execution time: 14_696_000 picoseconds. + Weight::from_parts(15_106_000, 3731) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -818,8 +849,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `304` // Estimated: `3769` - // Minimum execution time: 10_205_000 picoseconds. - Weight::from_parts(10_707_000, 3769) + // Minimum execution time: 10_292_000 picoseconds. + Weight::from_parts(10_670_000, 3769) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -829,8 +860,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `304` // Estimated: `3561` - // Minimum execution time: 9_025_000 picoseconds. - Weight::from_parts(9_517_000, 3561) + // Minimum execution time: 9_056_000 picoseconds. + Weight::from_parts(9_350_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -839,10 +870,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_451_000 picoseconds. - Weight::from_parts(10_620_260, 0) - // Standard Error: 77 - .saturating_add(Weight::from_parts(84_885, 0).saturating_mul(r.into())) + // Minimum execution time: 9_145_000 picoseconds. + Weight::from_parts(10_744_073, 0) + // Standard Error: 72 + .saturating_add(Weight::from_parts(84_813, 0).saturating_mul(r.into())) } } @@ -854,8 +885,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_783_000 picoseconds. - Weight::from_parts(2_883_000, 1594) + // Minimum execution time: 2_700_000 picoseconds. + Weight::from_parts(2_882_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -865,10 +896,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `392 + k * (69 ±0)` // Estimated: `382 + k * (70 ±0)` - // Minimum execution time: 13_394_000 picoseconds. - Weight::from_parts(305_292, 382) - // Standard Error: 1_217 - .saturating_add(Weight::from_parts(1_233_492, 0).saturating_mul(k.into())) + // Minimum execution time: 13_819_000 picoseconds. + Weight::from_parts(14_021_000, 382) + // Standard Error: 843 + .saturating_add(Weight::from_parts(1_222_715, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -876,7 +907,7 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -888,10 +919,10 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `834` - // Estimated: `4299` - // Minimum execution time: 78_977_000 picoseconds. - Weight::from_parts(81_687_641, 4299) + // Measured: `838` + // Estimated: `4303` + // Minimum execution time: 79_748_000 picoseconds. + Weight::from_parts(82_727_773, 4303) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -900,7 +931,7 @@ impl WeightInfo for () { /// Storage: `Balances::Holds` (r:2 w:2) /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(337), added: 2812, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) @@ -913,12 +944,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `303` // Estimated: `6232` - // Minimum execution time: 179_690_000 picoseconds. - Weight::from_parts(149_042_544, 6232) + // Minimum execution time: 179_270_000 picoseconds. + Weight::from_parts(149_933_578, 6232) // Standard Error: 11 - .saturating_add(Weight::from_parts(33, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_644, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_675, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -927,7 +958,7 @@ impl WeightInfo for () { /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) @@ -939,15 +970,15 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `657` // Estimated: `4111` - // Minimum execution time: 156_389_000 picoseconds. - Weight::from_parts(130_603_882, 4111) + // Minimum execution time: 156_112_000 picoseconds. + Weight::from_parts(132_715_336, 4111) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_594, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_599, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -958,10 +989,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `834` - // Estimated: `4299` - // Minimum execution time: 80_108_000 picoseconds. - Weight::from_parts(81_555_000, 4299) + // Measured: `838` + // Estimated: `4303` + // Minimum execution time: 82_111_000 picoseconds. + Weight::from_parts(83_659_000, 4303) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -976,8 +1007,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 49_297_000 picoseconds. - Weight::from_parts(50_873_587, 3574) + // Minimum execution time: 49_154_000 picoseconds. + Weight::from_parts(50_856_127, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -991,21 +1022,21 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 42_556_000 picoseconds. - Weight::from_parts(43_708_000, 3750) + // Minimum execution time: 41_347_000 picoseconds. + Weight::from_parts(42_272_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:2 w:2) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `491` - // Estimated: `6431` - // Minimum execution time: 24_623_000 picoseconds. - Weight::from_parts(26_390_000, 6431) + // Measured: `495` + // Estimated: `6435` + // Minimum execution time: 25_234_000 picoseconds. + Weight::from_parts(26_339_000, 6435) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1014,79 +1045,79 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_590_000 picoseconds. - Weight::from_parts(8_005_868, 0) - // Standard Error: 396 - .saturating_add(Weight::from_parts(203_612, 0).saturating_mul(r.into())) + // Minimum execution time: 7_509_000 picoseconds. + Weight::from_parts(9_047_452, 0) + // Standard Error: 165 + .saturating_add(Weight::from_parts(197_103, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 285_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(300_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_is_contract() -> Weight { // Proof Size summary in bytes: // Measured: `272` // Estimated: `3737` - // Minimum execution time: 6_711_000 picoseconds. - Weight::from_parts(7_103_000, 3737) + // Minimum execution time: 6_537_000 picoseconds. + Weight::from_parts(7_024_000, 3737) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { // Proof Size summary in bytes: - // Measured: `365` - // Estimated: `3830` - // Minimum execution time: 7_572_000 picoseconds. - Weight::from_parts(7_888_000, 3830) + // Measured: `369` + // Estimated: `3834` + // Minimum execution time: 7_571_000 picoseconds. + Weight::from_parts(8_060_000, 3834) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(264_000, 0) + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(281_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 324_000 picoseconds. - Weight::from_parts(356_000, 0) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. + // Minimum execution time: 240_000 picoseconds. Weight::from_parts(286_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 255_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 198_000 picoseconds. + Weight::from_parts(262_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 610_000 picoseconds. - Weight::from_parts(701_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(665_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_207_000 picoseconds. - Weight::from_parts(5_403_000, 0) + // Minimum execution time: 5_393_000 picoseconds. + Weight::from_parts(5_688_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -1094,37 +1125,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_829_000 picoseconds. - Weight::from_parts(3_977_000, 3517) + // Minimum execution time: 3_907_000 picoseconds. + Weight::from_parts(4_154_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) + /// The range of component `n` is `[1, 4096]`. + fn seal_get_immutable_data(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `205 + n * (1 ±0)` + // Estimated: `3670 + n * (1 ±0)` + // Minimum execution time: 5_700_000 picoseconds. + Weight::from_parts(6_362_270, 3670) + // Standard Error: 4 + .saturating_add(Weight::from_parts(749, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) + /// The range of component `n` is `[1, 4096]`. + fn seal_set_immutable_data(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_041_000 picoseconds. + Weight::from_parts(2_216_694, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(620, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 245_000 picoseconds. - Weight::from_parts(266_000, 0) + // Minimum execution time: 229_000 picoseconds. + Weight::from_parts(261_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 256_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(270_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(278_000, 0) + // Minimum execution time: 215_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(272_000, 0) + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(271_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -1132,8 +1190,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 5_467_000 picoseconds. - Weight::from_parts(5_607_000, 1552) + // Minimum execution time: 5_438_000 picoseconds. + Weight::from_parts(5_695_000, 1552) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `n` is `[0, 262140]`. @@ -1141,8 +1199,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 438_000 picoseconds. - Weight::from_parts(532_907, 0) + // Minimum execution time: 405_000 picoseconds. + Weight::from_parts(539_737, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } @@ -1151,8 +1209,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(629_625, 0) + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(539_301, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) } @@ -1162,18 +1220,20 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::DeletionQueue` (r:0 w:1) /// Proof: `Revive::DeletionQueue` (`max_values`: None, `max_size`: Some(142), added: 2617, mode: `Measured`) + /// Storage: `Revive::ImmutableDataOf` (r:0 w:1) + /// Proof: `Revive::ImmutableDataOf` (`max_values`: None, `max_size`: Some(4118), added: 6593, mode: `Measured`) /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `272 + n * (88 ±0)` // Estimated: `3738 + n * (2563 ±0)` - // Minimum execution time: 14_997_000 picoseconds. - Weight::from_parts(17_752_993, 3738) - // Standard Error: 9_865 - .saturating_add(Weight::from_parts(4_159_693, 0).saturating_mul(n.into())) + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(18_759_349, 3738) + // Standard Error: 10_298 + .saturating_add(Weight::from_parts(4_194_635, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } @@ -1183,20 +1243,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_277_000 picoseconds. - Weight::from_parts(4_023_910, 0) - // Standard Error: 2_210 - .saturating_add(Weight::from_parts(202_823, 0).saturating_mul(t.into())) - // Standard Error: 19 - .saturating_add(Weight::from_parts(1_141, 0).saturating_mul(n.into())) + // Minimum execution time: 4_341_000 picoseconds. + Weight::from_parts(4_292_048, 0) + // Standard Error: 2_476 + .saturating_add(Weight::from_parts(193_480, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(959, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 309_000 picoseconds. - Weight::from_parts(834_030, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(758_020, 0) // Standard Error: 1 .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) } @@ -1206,8 +1266,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_705_000 picoseconds. - Weight::from_parts(7_923_000, 744) + // Minimum execution time: 7_944_000 picoseconds. + Weight::from_parts(8_358_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1216,8 +1276,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_510_000 picoseconds. - Weight::from_parts(45_840_000, 10754) + // Minimum execution time: 44_950_000 picoseconds. + Weight::from_parts(45_575_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1226,8 +1286,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_842_000 picoseconds. - Weight::from_parts(9_363_000, 744) + // Minimum execution time: 9_095_000 picoseconds. + Weight::from_parts(9_484_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1237,8 +1297,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 46_172_000 picoseconds. - Weight::from_parts(47_586_000, 10754) + // Minimum execution time: 46_814_000 picoseconds. + Weight::from_parts(47_710_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1250,12 +1310,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_158_000 picoseconds. - Weight::from_parts(9_708_320, 247) - // Standard Error: 36 - .saturating_add(Weight::from_parts(499, 0).saturating_mul(n.into())) - // Standard Error: 36 - .saturating_add(Weight::from_parts(672, 0).saturating_mul(o.into())) + // Minimum execution time: 9_496_000 picoseconds. + Weight::from_parts(9_875_533, 247) + // Standard Error: 34 + .saturating_add(Weight::from_parts(712, 0).saturating_mul(n.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(777, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1267,10 +1327,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_885_000 picoseconds. - Weight::from_parts(9_597_656, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(649, 0).saturating_mul(n.into())) + // Minimum execution time: 9_185_000 picoseconds. + Weight::from_parts(9_912_715, 247) + // Standard Error: 59 + .saturating_add(Weight::from_parts(567, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1282,10 +1342,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_473_000 picoseconds. - Weight::from_parts(9_246_006, 247) - // Standard Error: 47 - .saturating_add(Weight::from_parts(1_468, 0).saturating_mul(n.into())) + // Minimum execution time: 8_586_000 picoseconds. + Weight::from_parts(9_536_041, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1296,10 +1356,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_996_000 picoseconds. - Weight::from_parts(8_784_165, 247) + // Minimum execution time: 8_162_000 picoseconds. + Weight::from_parts(8_813_128, 247) // Standard Error: 43 - .saturating_add(Weight::from_parts(591, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(880, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1310,10 +1370,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_246_000 picoseconds. - Weight::from_parts(10_239_803, 247) - // Standard Error: 57 - .saturating_add(Weight::from_parts(1_305, 0).saturating_mul(n.into())) + // Minimum execution time: 9_717_000 picoseconds. + Weight::from_parts(10_635_621, 247) + // Standard Error: 58 + .saturating_add(Weight::from_parts(1_430, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1322,36 +1382,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_422_000 picoseconds. - Weight::from_parts(1_531_000, 0) + // Minimum execution time: 1_503_000 picoseconds. + Weight::from_parts(1_592_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_858_000 picoseconds. - Weight::from_parts(1_944_000, 0) + // Minimum execution time: 1_903_000 picoseconds. + Weight::from_parts(1_996_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_443_000 picoseconds. - Weight::from_parts(1_506_000, 0) + // Minimum execution time: 1_432_000 picoseconds. + Weight::from_parts(1_534_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_592_000 picoseconds. - Weight::from_parts(1_651_000, 0) + // Minimum execution time: 1_606_000 picoseconds. + Weight::from_parts(1_669_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_060_000, 0) + Weight::from_parts(1_089_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1359,62 +1419,62 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_151_000 picoseconds. - Weight::from_parts(2_294_801, 0) + // Minimum execution time: 2_317_000 picoseconds. + Weight::from_parts(2_449_933, 0) // Standard Error: 11 - .saturating_add(Weight::from_parts(375, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) // Standard Error: 11 - .saturating_add(Weight::from_parts(415, 0).saturating_mul(o.into())) + .saturating_add(Weight::from_parts(387, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_880_000 picoseconds. - Weight::from_parts(2_259_773, 0) + // Minimum execution time: 1_980_000 picoseconds. + Weight::from_parts(2_352_243, 0) // Standard Error: 17 - .saturating_add(Weight::from_parts(356, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(372, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_755_000 picoseconds. - Weight::from_parts(1_968_235, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + // Minimum execution time: 1_857_000 picoseconds. + Weight::from_parts(2_082_050, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_618_000 picoseconds. - Weight::from_parts(1_811_972, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(174, 0).saturating_mul(n.into())) + // Minimum execution time: 1_693_000 picoseconds. + Weight::from_parts(1_892_600, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(193, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_482_000 picoseconds. - Weight::from_parts(2_682_484, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(n.into())) + // Minimum execution time: 2_620_000 picoseconds. + Weight::from_parts(2_818_388, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(39, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 9_899_000 picoseconds. - Weight::from_parts(10_342_000, 0) + // Minimum execution time: 10_207_000 picoseconds. + Weight::from_parts(10_627_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) @@ -1423,12 +1483,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `626 + t * (140 ±0)` - // Estimated: `4091 + t * (140 ±0)` - // Minimum execution time: 33_645_000 picoseconds. - Weight::from_parts(34_407_662, 4091) - // Standard Error: 36_930 - .saturating_add(Weight::from_parts(2_062_425, 0).saturating_mul(t.into())) + // Measured: `630 + t * (140 ±0)` + // Estimated: `4095 + t * (140 ±0)` + // Minimum execution time: 34_452_000 picoseconds. + Weight::from_parts(34_837_900, 4095) + // Standard Error: 30_385 + .saturating_add(Weight::from_parts(1_981_565, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) @@ -1443,8 +1503,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `457` // Estimated: `3922` - // Minimum execution time: 26_924_000 picoseconds. - Weight::from_parts(27_753_000, 3922) + // Minimum execution time: 27_287_000 picoseconds. + Weight::from_parts(28_450_000, 3922) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1452,7 +1512,7 @@ impl WeightInfo for () { /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// Storage: `Revive::ContractInfoOf` (r:1 w:1) - /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1775), added: 4250, mode: `Measured`) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `i` is `[0, 262144]`. @@ -1460,10 +1520,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `703` // Estimated: `4160` - // Minimum execution time: 117_979_000 picoseconds. - Weight::from_parts(105_415_117, 4160) + // Minimum execution time: 118_660_000 picoseconds. + Weight::from_parts(104_638_796, 4160) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_293, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_296, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1472,64 +1532,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 620_000 picoseconds. - Weight::from_parts(3_414_286, 0) + // Minimum execution time: 683_000 picoseconds. + Weight::from_parts(3_836_563, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_463, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_043_000 picoseconds. - Weight::from_parts(3_402_639, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(3_667, 0).saturating_mul(n.into())) + // Minimum execution time: 1_082_000 picoseconds. + Weight::from_parts(5_027_764, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(3_644, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 642_000 picoseconds. - Weight::from_parts(3_359_294, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(3_585_640, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(1_590, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_586, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 606_000 picoseconds. - Weight::from_parts(3_789_868, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_575, 0).saturating_mul(n.into())) + // Minimum execution time: 638_000 picoseconds. + Weight::from_parts(3_763_242, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_582, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_195_000 picoseconds. - Weight::from_parts(31_420_941, 0) + // Minimum execution time: 42_962_000 picoseconds. + Weight::from_parts(27_938_396, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_165, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_269, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_933_000 picoseconds. - Weight::from_parts(48_054_000, 0) + // Minimum execution time: 47_133_000 picoseconds. + Weight::from_parts(48_458_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_531_000 picoseconds. - Weight::from_parts(12_690_000, 0) + // Minimum execution time: 13_249_000 picoseconds. + Weight::from_parts(13_518_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1537,8 +1597,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `266` // Estimated: `3731` - // Minimum execution time: 14_694_000 picoseconds. - Weight::from_parts(15_032_000, 3731) + // Minimum execution time: 14_696_000 picoseconds. + Weight::from_parts(15_106_000, 3731) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1548,8 +1608,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `304` // Estimated: `3769` - // Minimum execution time: 10_205_000 picoseconds. - Weight::from_parts(10_707_000, 3769) + // Minimum execution time: 10_292_000 picoseconds. + Weight::from_parts(10_670_000, 3769) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1559,8 +1619,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `304` // Estimated: `3561` - // Minimum execution time: 9_025_000 picoseconds. - Weight::from_parts(9_517_000, 3561) + // Minimum execution time: 9_056_000 picoseconds. + Weight::from_parts(9_350_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1569,9 +1629,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_451_000 picoseconds. - Weight::from_parts(10_620_260, 0) - // Standard Error: 77 - .saturating_add(Weight::from_parts(84_885, 0).saturating_mul(r.into())) + // Minimum execution time: 9_145_000 picoseconds. + Weight::from_parts(10_744_073, 0) + // Standard Error: 72 + .saturating_add(Weight::from_parts(84_813, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 816fdec3aaa..2106b8fb49b 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -56,6 +56,30 @@ pub trait HostFn: private::Sealed { /// otherwise. fn lock_delegate_dependency(code_hash: &[u8; 32]); + /// Get the contract immutable data. + /// + /// Traps if: + /// - Called from within the deploy export. + /// - Called by contracts that didn't set immutable data by calling `set_immutable_data` during + /// their constructor execution. + /// + /// # Parameters + /// - `output`: A reference to the output buffer to write the immutable bytes. + fn get_immutable_data(output: &mut &mut [u8]); + + /// Set the contract immutable data. + /// + /// It is only valid to set non-empty immutable data in the constructor once. + /// + /// Traps if: + /// - Called from within the call export. + /// - Called more than once. + /// - The provided data was empty. + /// + /// # Parameters + /// - `data`: A reference to the data to be stored as immutable bytes. + fn set_immutable_data(data: &[u8]); + /// Stores the **reducible** balance of the current account into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv32.rs index d5ea94c1a91..866b0ee8dd1 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv32.rs @@ -81,6 +81,8 @@ mod sys { pub fn address(out_ptr: *mut u8); pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn get_immutable_data(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn set_immutable_data(ptr: *const u8, len: u32); pub fn balance(out_ptr: *mut u8); pub fn balance_of(addr_ptr: *const u8, out_ptr: *mut u8); pub fn chain_id(out_ptr: *mut u8); @@ -502,6 +504,16 @@ impl HostFn for HostFnImpl { ret_val.into_bool() } + fn get_immutable_data(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::get_immutable_data(output.as_mut_ptr(), &mut output_len) }; + extract_from_slice(output, output_len as usize); + } + + fn set_immutable_data(data: &[u8]) { + unsafe { sys::set_immutable_data(data.as_ptr(), data.len() as u32) } + } + fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; } -- GitLab From cb8f4665bce0f6d856c05154369e9d6b0dc76ced Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:51:39 +0200 Subject: [PATCH 049/127] update runners for cmd and docs (#5938) Updated runners for CMD and Docs --- .github/workflows/cmd.yml | 4 ++-- .github/workflows/docs.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index e416b120269..0c9c48a123b 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -231,9 +231,9 @@ jobs: fi if [[ $BODY == "/cmd bench"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-benchmark" >> $GITHUB_OUTPUT + echo "RUNNER=parity-weights" >> $GITHUB_OUTPUT elif [[ $BODY == "/cmd update-ui"* ]]; then - echo "RUNNER=arc-runners-polkadot-sdk-beefy" >> $GITHUB_OUTPUT + echo "RUNNER=parity-large" >> $GITHUB_OUTPUT else echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT fi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 44ad9613997..8fe3e80e628 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: env: SKIP_WASM_BUILD: 1 test-doc: - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} -- GitLab From a4abcbdd89952f188ee6418da152d6bfe26e55b4 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:14:40 -0300 Subject: [PATCH 050/127] bump zombienet version `v1.3.113` (#5935) Bump zombienet version. Including fixes for `ci` failures like https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7511363 https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/7511379 --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index b7f2397d20b..b02d857f3f2 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.112" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.113" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" -- GitLab From 6a29a61e1bc627d290d98365db42e71c62ec1f9b Mon Sep 17 00:00:00 2001 From: ordian Date: Sun, 6 Oct 2024 13:40:48 +0200 Subject: [PATCH 051/127] fix `participation_with_onchain_disabling_confirmed` test (#5901) Fixes #5900. --- polkadot/node/core/dispute-coordinator/src/tests.rs | 2 +- prdoc/pr_5901.prdoc | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5901.prdoc diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index b41cdb94b4d..48762a1d80b 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -2797,7 +2797,7 @@ fn participation_with_onchain_disabling_confirmed() { }) .await; - handle_disabled_validators_queries(&mut virtual_overseer, vec![]).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)); diff --git a/prdoc/pr_5901.prdoc b/prdoc/pr_5901.prdoc new file mode 100644 index 00000000000..4d3bce7f45a --- /dev/null +++ b/prdoc/pr_5901.prdoc @@ -0,0 +1,3 @@ +crates: + - name: polkadot-node-core-dispute-coordinator + bump: none -- GitLab From c733c525c19c6de8c74d2e8cf869a83660467fa0 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:54:07 +0300 Subject: [PATCH 052/127] beefy/metrics: Add number of live peers available for requests (#5859) This PR adds a new beefy metric to monitor the number of live beefy peers. Part of investigation of litep2p request failures: https://github.com/paritytech/polkadot-sdk/issues/4985 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- prdoc/pr_5859.prdoc | 11 +++++++++++ .../request_response/outgoing_requests_engine.rs | 4 +++- substrate/client/consensus/beefy/src/metrics.rs | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5859.prdoc diff --git a/prdoc/pr_5859.prdoc b/prdoc/pr_5859.prdoc new file mode 100644 index 00000000000..edb3008238b --- /dev/null +++ b/prdoc/pr_5859.prdoc @@ -0,0 +1,11 @@ +title: Add number of live peers available for requests + +doc: + - audience: [Node Operator, Node Dev] + description: | + This PR adds a new metric for the number of live peers available for beefy requests. + The metric is exposed under the name `substrate_beefy_on_demand_live_peers`. + +crates: + - name: sc-consensus-beefy + bump: minor diff --git a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs index 95ecf35557a..5408d95acf2 100644 --- a/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs +++ b/substrate/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -38,7 +38,7 @@ use crate::{ request_response::{Error, JustificationRequest, BEEFY_SYNC_LOG_TARGET}, }, justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, - metric_inc, + metric_inc, metric_set, metrics::{register_metrics, OnDemandOutgoingRequestsMetrics}, KnownPeers, }; @@ -242,6 +242,8 @@ impl OnDemandJustificationsEngine { diff --git a/substrate/client/consensus/beefy/src/metrics.rs b/substrate/client/consensus/beefy/src/metrics.rs index 30180fe43ec..15f2f9f9033 100644 --- a/substrate/client/consensus/beefy/src/metrics.rs +++ b/substrate/client/consensus/beefy/src/metrics.rs @@ -236,6 +236,8 @@ pub struct OnDemandOutgoingRequestsMetrics { pub beefy_on_demand_justification_invalid_proof: Counter, /// Number of on-demand justification good proof pub beefy_on_demand_justification_good_proof: Counter, + /// Number of live beefy peers available for requests. + pub beefy_on_demand_live_peers: Gauge, } impl PrometheusRegister for OnDemandOutgoingRequestsMetrics { @@ -277,6 +279,13 @@ impl PrometheusRegister for OnDemandOutgoingRequestsMetrics { )?, registry, )?, + beefy_on_demand_live_peers: register( + Gauge::new( + "substrate_beefy_on_demand_live_peers", + "Number of live beefy peers available for requests.", + )?, + registry, + )?, }) } } -- GitLab From ec73ea95d67d4f315d4117c9207455bec548659a Mon Sep 17 00:00:00 2001 From: Jonathan Udd Date: Sun, 6 Oct 2024 14:23:45 +0200 Subject: [PATCH 053/127] Added people-polkadot Dwellir bootnodes (#5893) # Description Adding Dwellir bootnodes in the `people-polkadot.json` spec file. --- cumulus/parachains/chain-specs/people-polkadot.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/chain-specs/people-polkadot.json b/cumulus/parachains/chain-specs/people-polkadot.json index 6e30829eab4..083c0fbf44a 100644 --- a/cumulus/parachains/chain-specs/people-polkadot.json +++ b/cumulus/parachains/chain-specs/people-polkadot.json @@ -6,7 +6,9 @@ "/dns/polkadot-people-connect-0.polkadot.io/tcp/30334/p2p/12D3KooWP7BoJ7nAF9QnsreN8Eft1yHNUhvhxFiQyKFEUePi9mu3", "/dns/polkadot-people-connect-1.polkadot.io/tcp/30334/p2p/12D3KooWSSfWY3fTGJvGkuNUNBSNVCdLLNJnwkZSNQt7GCRYXu4o", "/dns/polkadot-people-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWP7BoJ7nAF9QnsreN8Eft1yHNUhvhxFiQyKFEUePi9mu3", - "/dns/polkadot-people-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWSSfWY3fTGJvGkuNUNBSNVCdLLNJnwkZSNQt7GCRYXu4o" + "/dns/polkadot-people-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWSSfWY3fTGJvGkuNUNBSNVCdLLNJnwkZSNQt7GCRYXu4o", + "/dns/people-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN", + "/dns/people-polkadot-boot-ng.dwellir.com/tcp/30346/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN" ], "telemetryEndpoints": null, "protocolId": null, -- GitLab From 4bda956d2c635c3926578741a19fbcc3de69cbb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 22:59:15 +0200 Subject: [PATCH 054/127] Bump docker/build-push-action from 6.7.0 to 6.8.0 in the ci_dependencies group (#5863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ci_dependencies group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.7.0 to 6.8.0
Release notes

Sourced from docker/build-push-action's releases.

v6.8.0

Full Changelog: https://github.com/docker/build-push-action/compare/v6.7.0...v6.8.0

Commits
  • 32945a3 Merge pull request #1230 from docker/dependabot/npm_and_yarn/docker/actions-t...
  • e0fe9cf chore: update generated content
  • 8f1ff6b chore(deps): Bump @​docker/actions-toolkit from 0.37.1 to 0.38.0
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/build-push-action&package-manager=github_actions&previous-version=6.7.0&new-version=6.8.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> Co-authored-by: Bastian Köcher --- .github/workflows/release-50_publish-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index ecaf3295d40..dacfcc5995b 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -348,7 +348,7 @@ jobs: - name: Build and push id: docker_build - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 + uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 with: push: true file: docker/dockerfiles/polkadot/polkadot_injected_debian.Dockerfile -- GitLab From 4b356c4b8bf1fe49f1a61fc42cb02cf115f05318 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:42:48 +0300 Subject: [PATCH 055/127] Elastic scaling: runtime v2 descriptor support (#5423) Closes https://github.com/paritytech/polkadot-sdk/issues/5045 and https://github.com/paritytech/polkadot-sdk/issues/5046 On top of https://github.com/paritytech/polkadot-sdk/pull/5362 TODO: - [x] storage migration for allowed relay parents tracker - [x] check session index - [x] PRdoc - [x] tests - [x] ensure UMP queue cannot be abused with this change - [x] Zombienet runtime upgrade test --------- Signed-off-by: Andrei Sandu --- polkadot/primitives/src/vstaging/mod.rs | 231 ++++-- .../src/node/utility/candidate-validation.md | 2 +- .../src/runtime/inclusion.md | 2 +- polkadot/runtime/parachains/src/builder.rs | 132 +++- .../runtime/parachains/src/inclusion/mod.rs | 40 +- .../runtime/parachains/src/inclusion/tests.rs | 55 +- .../parachains/src/paras_inherent/mod.rs | 310 +++++--- .../parachains/src/paras_inherent/tests.rs | 664 ++++++++++++++++-- .../parachains/src/runtime_api_impl/v11.rs | 18 +- polkadot/runtime/parachains/src/shared.rs | 46 +- .../parachains/src/shared/migration.rs | 196 ++++++ .../runtime/parachains/src/shared/tests.rs | 67 +- polkadot/runtime/parachains/src/ump_tests.rs | 55 +- polkadot/runtime/rococo/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 1 + prdoc/pr_5423.prdoc | 20 + 16 files changed, 1541 insertions(+), 299 deletions(-) create mode 100644 polkadot/runtime/parachains/src/shared/migration.rs create mode 100644 prdoc/pr_5423.prdoc diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 57cba85c10d..bc687f7e2fb 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -24,12 +24,15 @@ use super::{ HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields, ValidationCodeHash, }; +use alloc::{ + collections::{BTreeMap, BTreeSet, VecDeque}, + vec, + vec::Vec, +}; use bitvec::prelude::*; -use sp_application_crypto::ByteArray; - -use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use scale_info::TypeInfo; +use sp_application_crypto::ByteArray; use sp_core::RuntimeDebug; use sp_runtime::traits::Header as HeaderT; use sp_staking::SessionIndex; @@ -298,9 +301,9 @@ pub struct ClaimQueueOffset(pub u8); /// Signals that a parachain can send to the relay chain via the UMP queue. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug)] pub enum UMPSignal { - /// A message sent by a parachain to select the core the candidate is commited to. + /// A message sent by a parachain to select the core the candidate is committed to. /// Relay chain validators, in particular backers, use the `CoreSelector` and - /// `ClaimQueueOffset` to compute the index of the core the candidate has commited to. + /// `ClaimQueueOffset` to compute the index of the core the candidate has committed to. SelectCore(CoreSelector, ClaimQueueOffset), } /// Separator between `XCM` and `UMPSignal`. @@ -324,6 +327,25 @@ impl CandidateCommitments { UMPSignal::SelectCore(core_selector, cq_offset) => Some((core_selector, cq_offset)), } } + + /// Returns the core index determined by `UMPSignal::SelectCore` commitment + /// and `assigned_cores`. + /// + /// Returns `None` if there is no `UMPSignal::SelectCore` commitment or + /// assigned cores is empty. + /// + /// `assigned_cores` must be a sorted vec of all core indices assigned to a parachain. + pub fn committed_core_index(&self, assigned_cores: &[&CoreIndex]) -> Option { + if assigned_cores.is_empty() { + return None + } + + self.selected_core().and_then(|(core_selector, _cq_offset)| { + let core_index = + **assigned_cores.get(core_selector.0 as usize % assigned_cores.len())?; + Some(core_index) + }) + } } /// CandidateReceipt construction errors. @@ -337,7 +359,8 @@ pub enum CandidateReceiptError { InvalidSelectedCore, /// The parachain is not assigned to any core at specified claim queue offset. NoAssignment, - /// No core was selected. + /// No core was selected. The `SelectCore` commitment is mandatory for + /// v2 receipts if parachains has multiple cores assigned. NoCoreSelected, /// Unknown version. UnknownVersion(InternalVersion), @@ -432,33 +455,57 @@ impl CandidateDescriptorV2 { } impl CommittedCandidateReceiptV2 { - /// Checks if descriptor core index is equal to the commited core index. - /// Input `assigned_cores` must contain the sorted cores assigned to the para at - /// the committed claim queue offset. - pub fn check(&self, assigned_cores: &[CoreIndex]) -> Result<(), CandidateReceiptError> { - // Don't check v1 descriptors. - if self.descriptor.version() == CandidateDescriptorVersion::V1 { - return Ok(()) - } - - if self.descriptor.version() == CandidateDescriptorVersion::Unknown { - return Err(CandidateReceiptError::UnknownVersion(self.descriptor.version)) + /// Checks if descriptor core index is equal to the committed core index. + /// Input `cores_per_para` is a claim queue snapshot stored as a mapping + /// between `ParaId` and the cores assigned per depth. + pub fn check_core_index( + &self, + cores_per_para: &TransposedClaimQueue, + ) -> Result<(), CandidateReceiptError> { + match self.descriptor.version() { + // Don't check v1 descriptors. + CandidateDescriptorVersion::V1 => return Ok(()), + CandidateDescriptorVersion::V2 => {}, + CandidateDescriptorVersion::Unknown => + return Err(CandidateReceiptError::UnknownVersion(self.descriptor.version)), } - if assigned_cores.is_empty() { + if cores_per_para.is_empty() { return Err(CandidateReceiptError::NoAssignment) } - let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32); - - let (core_selector, _cq_offset) = - self.commitments.selected_core().ok_or(CandidateReceiptError::NoCoreSelected)?; + let (offset, core_selected) = + if let Some((_core_selector, cq_offset)) = self.commitments.selected_core() { + (cq_offset.0, true) + } else { + // If no core has been selected then we use offset 0 (top of claim queue) + (0, false) + }; + + // The cores assigned to the parachain at above computed offset. + let assigned_cores = cores_per_para + .get(&self.descriptor.para_id()) + .ok_or(CandidateReceiptError::NoAssignment)? + .get(&offset) + .ok_or(CandidateReceiptError::NoAssignment)? + .into_iter() + .collect::>(); + + let core_index = if core_selected { + self.commitments + .committed_core_index(assigned_cores.as_slice()) + .ok_or(CandidateReceiptError::NoAssignment)? + } else { + // `SelectCore` commitment is mandatory for elastic scaling parachains. + if assigned_cores.len() > 1 { + return Err(CandidateReceiptError::NoCoreSelected) + } - let core_index = assigned_cores - .get(core_selector.0 as usize % assigned_cores.len()) - .ok_or(CandidateReceiptError::InvalidCoreIndex)?; + **assigned_cores.get(0).ok_or(CandidateReceiptError::NoAssignment)? + }; - if *core_index != descriptor_core_index { + let descriptor_core_index = CoreIndex(self.descriptor.core_index as u32); + if core_index != descriptor_core_index { return Err(CandidateReceiptError::CoreIndexMismatch) } @@ -512,6 +559,12 @@ impl BackedCandidate { &self.candidate } + /// Get a mutable reference to the committed candidate receipt of the candidate. + /// Only for testing. + #[cfg(feature = "test")] + pub fn candidate_mut(&mut self) -> &mut CommittedCandidateReceiptV2 { + &mut self.candidate + } /// Get a reference to the descriptor of the candidate. pub fn descriptor(&self) -> &CandidateDescriptorV2 { &self.candidate.descriptor @@ -697,6 +750,29 @@ impl From> for super::v8::CoreState { } } +/// The claim queue mapped by parachain id. +pub type TransposedClaimQueue = BTreeMap>>; + +/// Returns a mapping between the para id and the core indices assigned at different +/// depths in the claim queue. +pub fn transpose_claim_queue( + claim_queue: BTreeMap>, +) -> TransposedClaimQueue { + let mut per_para_claim_queue = BTreeMap::new(); + + for (core, paras) in claim_queue { + // Iterate paras assigned to this core at each depth. + for (depth, para) in paras.into_iter().enumerate() { + let depths: &mut BTreeMap> = + per_para_claim_queue.entry(para).or_insert_with(|| Default::default()); + + depths.entry(depth as u8).or_default().insert(core); + } + } + + per_para_claim_queue +} + #[cfg(test)] mod tests { use super::*; @@ -778,7 +854,7 @@ mod tests { assert_eq!(new_ccr.descriptor.version(), CandidateDescriptorVersion::Unknown); assert_eq!( - new_ccr.check(&vec![].as_slice()), + new_ccr.check_core_index(&BTreeMap::new()), Err(CandidateReceiptError::UnknownVersion(InternalVersion(100))) ) } @@ -802,7 +878,13 @@ mod tests { .upward_messages .force_push(UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(1)).encode()); - assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(())); + let mut cq = BTreeMap::new(); + cq.insert( + CoreIndex(123), + vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(), + ); + + assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(())); } #[test] @@ -814,11 +896,12 @@ mod tests { new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR); new_ccr.commitments.upward_messages.force_push(UMP_SEPARATOR); - // The check should fail because no `SelectCore` signal was sent. - assert_eq!( - new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]), - Err(CandidateReceiptError::NoCoreSelected) - ); + let mut cq = BTreeMap::new(); + cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into()); + + // The check should not fail because no `SelectCore` signal was sent. + // The message is optional. + assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok()); // Garbage message. new_ccr.commitments.upward_messages.force_push(vec![0, 13, 200].encode()); @@ -826,9 +909,18 @@ mod tests { // No `SelectCore` can be decoded. assert_eq!(new_ccr.commitments.selected_core(), None); - // Failure is expected. + let mut cq = BTreeMap::new(); + cq.insert( + CoreIndex(0), + vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(), + ); + cq.insert( + CoreIndex(100), + vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(), + ); + assert_eq!( - new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]), + new_ccr.check_core_index(&transpose_claim_queue(cq.clone())), Err(CandidateReceiptError::NoCoreSelected) ); @@ -847,7 +939,7 @@ mod tests { .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode()); // Duplicate doesn't override first signal. - assert_eq!(new_ccr.check(&vec![CoreIndex(0), CoreIndex(100)]), Ok(())); + assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(())); } #[test] @@ -884,13 +976,57 @@ mod tests { Decode::decode(&mut encoded_ccr.as_slice()).unwrap(); assert_eq!(v2_ccr.descriptor.core_index(), Some(CoreIndex(123))); - assert_eq!(new_ccr.check(&vec![CoreIndex(123)]), Ok(())); + + let mut cq = BTreeMap::new(); + cq.insert( + CoreIndex(123), + vec![new_ccr.descriptor.para_id(), new_ccr.descriptor.para_id()].into(), + ); + + assert_eq!(new_ccr.check_core_index(&transpose_claim_queue(cq)), Ok(())); assert_eq!(new_ccr.hash(), v2_ccr.hash()); } + // Only check descriptor `core_index` field of v2 descriptors. If it is v1, that field + // will be garbage. #[test] - fn test_core_select_is_mandatory() { + fn test_v1_descriptors_with_ump_signal() { + let mut ccr = dummy_old_committed_candidate_receipt(); + ccr.descriptor.para_id = ParaId::new(1024); + // Adding collator signature should make it decode as v1. + ccr.descriptor.signature = dummy_collator_signature(); + ccr.descriptor.collator = dummy_collator_id(); + + ccr.commitments.upward_messages.force_push(UMP_SEPARATOR); + ccr.commitments + .upward_messages + .force_push(UMPSignal::SelectCore(CoreSelector(1), ClaimQueueOffset(1)).encode()); + + let encoded_ccr: Vec = ccr.encode(); + + let v1_ccr: CommittedCandidateReceiptV2 = + Decode::decode(&mut encoded_ccr.as_slice()).unwrap(); + + assert_eq!(v1_ccr.descriptor.version(), CandidateDescriptorVersion::V1); + assert!(v1_ccr.commitments.selected_core().is_some()); + + let mut cq = BTreeMap::new(); + cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into()); + cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into()); + + assert!(v1_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok()); + + assert_eq!( + v1_ccr.commitments.committed_core_index(&vec![&CoreIndex(10), &CoreIndex(5)]), + Some(CoreIndex(5)), + ); + + assert_eq!(v1_ccr.descriptor.core_index(), None); + } + + #[test] + fn test_core_select_is_optional() { // Testing edge case when collators provide zeroed signature and collator id. let mut old_ccr = dummy_old_committed_candidate_receipt(); old_ccr.descriptor.para_id = ParaId::new(1000); @@ -899,11 +1035,22 @@ mod tests { let new_ccr: CommittedCandidateReceiptV2 = Decode::decode(&mut encoded_ccr.as_slice()).unwrap(); + let mut cq = BTreeMap::new(); + cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into()); + // Since collator sig and id are zeroed, it means that the descriptor uses format - // version 2. - // We expect the check to fail in such case because there will be no `SelectCore` - // commitment. - assert_eq!(new_ccr.check(&vec![CoreIndex(0)]), Err(CandidateReceiptError::NoCoreSelected)); + // version 2. Should still pass checks without core selector. + assert!(new_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok()); + + let mut cq = BTreeMap::new(); + cq.insert(CoreIndex(0), vec![new_ccr.descriptor.para_id()].into()); + cq.insert(CoreIndex(1), vec![new_ccr.descriptor.para_id()].into()); + + // Should fail because 2 cores are assigned, + assert_eq!( + new_ccr.check_core_index(&transpose_claim_queue(cq)), + Err(CandidateReceiptError::NoCoreSelected) + ); // Adding collator signature should make it decode as v1. old_ccr.descriptor.signature = dummy_collator_signature(); diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md index 1a3ff1c6aff..aad77de0ade 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/candidate-validation.md @@ -85,7 +85,7 @@ state. Once we have all parameters, we can spin up a background task to perform the validation in a way that doesn't hold up the entire event loop. Before invoking the validation function itself, this should first do some basic checks: - * The collator signature is valid + * The collator signature is valid (only if `CandidateDescriptor` has version 1) * The PoV provided matches the `pov_hash` field of the descriptor For more details please see [PVF Host and Workers](pvf-host-and-workers.md). diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index 5031433cf5a..48909db07ba 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md @@ -109,7 +109,7 @@ All failed checks should lead to an unrecoverable error making the block invalid 1. Ensure that any code upgrade scheduled by the candidate does not happen within `config.validation_upgrade_cooldown` of `Paras::last_code_upgrade(para_id, true)`, if any, comparing against the value of `Paras::FutureCodeUpgrades` for the given para ID. - 1. Check the collator's signature on the candidate data. + 1. Check the collator's signature on the candidate data (only if `CandidateDescriptor` is version 1) 1. check the backing of the candidate using the signatures and the bitfields, comparing against the validators assigned to the groups, fetched with the `group_validators` lookup, while group indices are computed by `Scheduler` according to group rotation info. diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index 665737afa6c..1654590d109 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -32,9 +32,9 @@ use frame_system::pallet_prelude::*; use polkadot_primitives::{ node_features::FeatureIndex, vstaging::{ - BackedCandidate, CandidateDescriptorV2, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - InherentData as ParachainsInherentData, + BackedCandidate, CandidateDescriptorV2, ClaimQueueOffset, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreSelector, + InherentData as ParachainsInherentData, UMPSignal, UMP_SEPARATOR, }, AvailabilityBitfield, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CompactStatement, CoreIndex, DisputeStatement, DisputeStatementSet, @@ -52,14 +52,14 @@ fn mock_validation_code() -> ValidationCode { ValidationCode(vec![1, 2, 3]) } -// Create a dummy collator id suitable to be used in a V1 candidate descriptor. -fn junk_collator() -> CollatorId { +/// Create a dummy collator id suitable to be used in a V1 candidate descriptor. +pub fn junk_collator() -> CollatorId { CollatorId::from_slice(&mut (0..32).into_iter().collect::>().as_slice()) .expect("32 bytes; qed") } -// Creates a dummy collator signature suitable to be used in a V1 candidate descriptor. -fn junk_collator_signature() -> CollatorSignature { +/// Creates a dummy collator signature suitable to be used in a V1 candidate descriptor. +pub fn junk_collator_signature() -> CollatorSignature { CollatorSignature::from_slice(&mut (0..64).into_iter().collect::>().as_slice()) .expect("64 bytes; qed") } @@ -144,9 +144,14 @@ pub(crate) struct BenchBuilder { unavailable_cores: Vec, /// Use v2 candidate descriptor. candidate_descriptor_v2: bool, + /// Apply custom changes to generated candidates + candidate_modifier: Option>, _phantom: core::marker::PhantomData, } +pub type CandidateModifier = + fn(CommittedCandidateReceipt) -> CommittedCandidateReceipt; + /// Paras inherent `enter` benchmark scenario. #[cfg(any(feature = "runtime-benchmarks", test))] pub(crate) struct Bench { @@ -176,6 +181,7 @@ impl BenchBuilder { fill_claimqueue: true, unavailable_cores: vec![], candidate_descriptor_v2: false, + candidate_modifier: None, _phantom: core::marker::PhantomData::, } } @@ -290,6 +296,15 @@ impl BenchBuilder { self } + /// Set the candidate modifier. + pub(crate) fn set_candidate_modifier( + mut self, + modifier: Option>, + ) -> Self { + self.candidate_modifier = modifier; + self + } + /// Get the maximum number of validators per core. fn max_validators_per_core(&self) -> u32 { self.max_validators_per_core.unwrap_or(Self::fallback_max_validators_per_core()) @@ -325,18 +340,33 @@ impl BenchBuilder { HeadData(vec![0xFF; max_head_size as usize]) } - fn candidate_descriptor_mock() -> CandidateDescriptorV2 { - // Use a v1 descriptor. - CandidateDescriptor:: { - para_id: 0.into(), - relay_parent: Default::default(), - collator: junk_collator(), - persisted_validation_data_hash: Default::default(), - pov_hash: Default::default(), - erasure_root: Default::default(), - signature: junk_collator_signature(), - para_head: Default::default(), - validation_code_hash: mock_validation_code().hash(), + fn candidate_descriptor_mock(candidate_descriptor_v2: bool) -> CandidateDescriptorV2 { + if candidate_descriptor_v2 { + CandidateDescriptorV2::new( + 0.into(), + Default::default(), + CoreIndex(200), + 2, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + mock_validation_code().hash(), + ) + } else { + // Convert v1 to v2. + CandidateDescriptor:: { + para_id: 0.into(), + relay_parent: Default::default(), + collator: junk_collator(), + persisted_validation_data_hash: Default::default(), + pov_hash: Default::default(), + erasure_root: Default::default(), + signature: junk_collator_signature(), + para_head: Default::default(), + validation_code_hash: mock_validation_code().hash(), + } + .into() } .into() } @@ -348,17 +378,19 @@ impl BenchBuilder { candidate_hash: CandidateHash, availability_votes: BitVec, commitments: CandidateCommitments, + candidate_descriptor_v2: bool, ) -> inclusion::CandidatePendingAvailability> { inclusion::CandidatePendingAvailability::>::new( - core_idx, // core - candidate_hash, // hash - Self::candidate_descriptor_mock(), // candidate descriptor - commitments, // commitments - availability_votes, // availability votes - Default::default(), // backers - Zero::zero(), // relay parent - One::one(), // relay chain block this was backed in - group_idx, // backing group + core_idx, // core + candidate_hash, // hash + Self::candidate_descriptor_mock(candidate_descriptor_v2), // candidate descriptor + commitments, // commitments + availability_votes, // availability votes + Default::default(), // backers + Zero::zero(), // relay parent + One::one(), /* relay chain block this + * was backed in */ + group_idx, // backing group ) } @@ -373,6 +405,7 @@ impl BenchBuilder { group_idx: GroupIndex, availability_votes: BitVec, candidate_hash: CandidateHash, + candidate_descriptor_v2: bool, ) { let commitments = CandidateCommitments:: { upward_messages: Default::default(), @@ -388,6 +421,7 @@ impl BenchBuilder { candidate_hash, availability_votes, commitments, + candidate_descriptor_v2, ); inclusion::PendingAvailability::::mutate(para_id, |maybe_candidates| { if let Some(candidates) = maybe_candidates { @@ -547,6 +581,7 @@ impl BenchBuilder { // No validators have made this candidate available yet. bitvec::bitvec![u8, bitvec::order::Lsb0; 0; validators.len()], CandidateHash(H256::from(byte32_slice_from(current_core_idx))), + self.candidate_descriptor_v2, ); if !self.unavailable_cores.contains(¤t_core_idx) { concluding_cores.insert(current_core_idx); @@ -654,7 +689,7 @@ impl BenchBuilder { para_id, relay_parent, core_idx, - 1, + self.target_session, persisted_validation_data_hash, pov_hash, Default::default(), @@ -676,7 +711,7 @@ impl BenchBuilder { .into() }; - let candidate = CommittedCandidateReceipt:: { + let mut candidate = CommittedCandidateReceipt:: { descriptor, commitments: CandidateCommitments:: { upward_messages: Default::default(), @@ -689,6 +724,27 @@ impl BenchBuilder { }, }; + if self.candidate_descriptor_v2 { + // `UMPSignal` separator. + candidate.commitments.upward_messages.force_push(UMP_SEPARATOR); + + // `SelectCore` commitment. + // Claim queue offset must be `0` so this candidate is for the very + // next block. + candidate.commitments.upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(chain_idx as u8), + ClaimQueueOffset(0), + ) + .encode(), + ); + } + + // Maybe apply the candidate modifier + if let Some(modifier) = self.candidate_modifier { + candidate = modifier(candidate); + } + let candidate_hash = candidate.hash(); let validity_votes: Vec<_> = group_validators @@ -708,12 +764,15 @@ impl BenchBuilder { }) .collect(); - // Check if the elastic scaling bit is set, if so we need to supply the core - // index in the generated candidate. - let core_idx = configuration::ActiveConfig::::get() - .node_features - .get(FeatureIndex::ElasticScalingMVP as usize) - .map(|_the_bit| core_idx); + // Don't inject core when it is available in descriptor. + let core_idx = if candidate.descriptor.core_index().is_some() { + None + } else { + configuration::ActiveConfig::::get() + .node_features + .get(FeatureIndex::ElasticScalingMVP as usize) + .and_then(|the_bit| if *the_bit { Some(core_idx) } else { None }) + }; BackedCandidate::::new( candidate, @@ -766,6 +825,7 @@ impl BenchBuilder { group_idx, Self::validator_availability_votes_yes(validators.len()), candidate_hash, + self.candidate_descriptor_v2, ); let statements_len = diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index cc333e3b20f..36f874b8db1 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -440,6 +440,11 @@ pub(crate) enum UmpAcceptanceCheckErr { TotalSizeExceeded { total_size: u64, limit: u64 }, /// A para-chain cannot send UMP messages while it is offboarding. IsOffboarding, + /// The allowed number of `UMPSignal` messages in the queue was exceeded. + /// Currenly only one such message is allowed. + TooManyUMPSignals { count: u32 }, + /// The UMP queue contains an invalid `UMPSignal` + NoUmpSignal, } impl fmt::Debug for UmpAcceptanceCheckErr { @@ -468,6 +473,12 @@ impl fmt::Debug for UmpAcceptanceCheckErr { UmpAcceptanceCheckErr::IsOffboarding => { write!(fmt, "upward message rejected because the para is off-boarding") }, + UmpAcceptanceCheckErr::TooManyUMPSignals { count } => { + write!(fmt, "the ump queue has too many `UMPSignal` messages ({} > 1 )", count) + }, + UmpAcceptanceCheckErr::NoUmpSignal => { + write!(fmt, "Required UMP signal not found") + }, } } } @@ -935,6 +946,27 @@ impl Pallet { para: ParaId, upward_messages: &[UpwardMessage], ) -> Result<(), UmpAcceptanceCheckErr> { + // Filter any pending UMP signals and the separator. + let upward_messages = if let Some(separator_index) = + upward_messages.iter().position(|message| message.is_empty()) + { + let (upward_messages, ump_signals) = upward_messages.split_at(separator_index); + + if ump_signals.len() > 2 { + return Err(UmpAcceptanceCheckErr::TooManyUMPSignals { + count: ump_signals.len() as u32, + }) + } + + if ump_signals.len() == 1 { + return Err(UmpAcceptanceCheckErr::NoUmpSignal) + } + + upward_messages + } else { + upward_messages + }; + // Cannot send UMP messages while off-boarding. if paras::Pallet::::is_offboarding(para) { ensure!(upward_messages.is_empty(), UmpAcceptanceCheckErr::IsOffboarding); @@ -989,6 +1021,8 @@ impl Pallet { pub(crate) fn receive_upward_messages(para: ParaId, upward_messages: &[Vec]) { let bounded = upward_messages .iter() + // Stop once we hit the `UMPSignal` separator. + .take_while(|message| !message.is_empty()) .filter_map(|d| { BoundedSlice::try_from(&d[..]) .inspect_err(|_| { @@ -1257,17 +1291,17 @@ impl CandidateCheckContext { 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) = { + let (state_root, relay_parent_number) = { match allowed_relay_parents.acquire_info(relay_parent, self.prev_context) { None => return Err(Error::::DisallowedRelayParent), - Some(info) => info, + Some((info, relay_parent_number)) => (info.state_root, relay_parent_number), } }; { let persisted_validation_data = make_persisted_validation_data_with_parent::( relay_parent_number, - relay_parent_storage_root, + state_root, parent_head_data, ); diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 59114e28be1..87d21e209a4 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -26,8 +26,13 @@ use crate::{ shared::AllowedRelayParentsTracker, }; use polkadot_primitives::{ - effective_minimum_backing_votes, AvailabilityBitfield, CandidateDescriptor, - SignedAvailabilityBitfields, UncheckedSignedAvailabilityBitfields, + effective_minimum_backing_votes, + vstaging::{ + CandidateDescriptorV2, CandidateDescriptorVersion, ClaimQueueOffset, CoreSelector, + UMPSignal, UMP_SEPARATOR, + }, + AvailabilityBitfield, CandidateDescriptor, SignedAvailabilityBitfields, + UncheckedSignedAvailabilityBitfields, }; use assert_matches::assert_matches; @@ -83,7 +88,7 @@ fn default_allowed_relay_parent_tracker() -> AllowedRelayParentsTracker, pub(crate) validation_code: ValidationCode, pub(crate) hrmp_watermark: BlockNumber, + /// Creates a v2 descriptor if set. + pub(crate) core_index: Option, + /// The core selector to use. + pub(crate) core_selector: Option, } impl std::default::Default for TestCandidateBuilder { @@ -277,14 +286,28 @@ impl std::default::Default for TestCandidateBuilder { new_validation_code: None, validation_code: dummy_validation_code(), hrmp_watermark: 0u32.into(), + core_index: None, + core_selector: None, } } } impl TestCandidateBuilder { pub(crate) fn build(self) -> CommittedCandidateReceipt { - CommittedCandidateReceipt { - descriptor: CandidateDescriptor { + let descriptor = if let Some(core_index) = self.core_index { + CandidateDescriptorV2::new( + self.para_id, + self.relay_parent, + core_index, + 0, + self.persisted_validation_data_hash, + self.pov_hash, + Default::default(), + self.para_head_hash.unwrap_or_else(|| self.head_data.hash()), + self.validation_code.hash(), + ) + } else { + CandidateDescriptor { para_id: self.para_id, pov_hash: self.pov_hash, relay_parent: self.relay_parent, @@ -301,14 +324,31 @@ impl TestCandidateBuilder { ) .expect("32 bytes; qed"), } - .into(), + .into() + }; + let mut ccr = CommittedCandidateReceipt { + descriptor, commitments: CandidateCommitments { head_data: self.head_data, new_validation_code: self.new_validation_code, hrmp_watermark: self.hrmp_watermark, ..Default::default() }, + }; + + if ccr.descriptor.version() == CandidateDescriptorVersion::V2 { + ccr.commitments.upward_messages.force_push(UMP_SEPARATOR); + + ccr.commitments.upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(self.core_selector.unwrap_or_default()), + ClaimQueueOffset(0), + ) + .encode(), + ); } + + ccr } } @@ -2497,18 +2537,21 @@ fn check_allowed_relay_parents() { allowed_relay_parents.update( relay_parent_a.1, Hash::zero(), + Default::default(), relay_parent_a.0, max_ancestry_len, ); allowed_relay_parents.update( relay_parent_b.1, Hash::zero(), + Default::default(), relay_parent_b.0, max_ancestry_len, ); allowed_relay_parents.update( relay_parent_c.1, Hash::zero(), + Default::default(), relay_parent_c.0, max_ancestry_len, ); diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 70c4ba72d58..2aca0f2c728 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -45,6 +45,7 @@ use frame_support::{ pallet_prelude::*, traits::Randomness, }; + use frame_system::pallet_prelude::*; use pallet_babe::{self, ParentBlockRandomness}; use polkadot_primitives::{ @@ -332,22 +333,6 @@ impl Pallet { let now = frame_system::Pallet::::block_number(); let config = configuration::ActiveConfig::::get(); - // Before anything else, update the allowed relay-parents. - { - let parent_number = now - One::one(); - let parent_storage_root = *parent_header.state_root(); - - shared::AllowedRelayParents::::mutate(|tracker| { - tracker.update( - parent_hash, - parent_storage_root, - parent_number, - config.async_backing_params.allowed_ancestry_len, - ); - }); - } - let allowed_relay_parents = shared::AllowedRelayParents::::get(); - let candidates_weight = backed_candidates_weight::(&backed_candidates); let bitfields_weight = signed_bitfields_weight::(&bitfields); let disputes_weight = multi_dispute_statement_sets_weight::(&disputes); @@ -593,6 +578,29 @@ impl Pallet { METRICS.on_candidates_processed_total(backed_candidates.len() as u64); + // After freeing cores and filling claims, but before processing backed candidates + // we update the allowed relay-parents. + { + let parent_number = now - One::one(); + let parent_storage_root = *parent_header.state_root(); + + shared::AllowedRelayParents::::mutate(|tracker| { + tracker.update( + parent_hash, + parent_storage_root, + scheduler::ClaimQueue::::get() + .into_iter() + .map(|(core_index, paras)| { + (core_index, paras.into_iter().map(|e| e.para_id()).collect()) + }) + .collect(), + parent_number, + config.async_backing_params.allowed_ancestry_len, + ); + }); + } + let allowed_relay_parents = shared::AllowedRelayParents::::get(); + let core_index_enabled = configuration::ActiveConfig::::get() .node_features .get(FeatureIndex::ElasticScalingMVP as usize) @@ -972,6 +980,86 @@ pub(crate) fn sanitize_bitfields( bitfields } +/// Perform required checks for given candidate receipt. +/// +/// Returns `true` if candidate descriptor is version 1. +/// +/// Otherwise returns `false` if: +/// - version 2 descriptors are not allowed +/// - the core index in descriptor doesn't match the one computed from the commitments +/// - the `SelectCore` signal does not refer to a core at the top of claim queue +fn sanitize_backed_candidate_v2( + candidate: &BackedCandidate, + allowed_relay_parents: &AllowedRelayParentsTracker>, + allow_v2_receipts: bool, +) -> bool { + if candidate.descriptor().version() == CandidateDescriptorVersion::V1 { + return true + } + + // It is mandatory to filter these before calling `filter_unchained_candidates` to ensure + // any v1 descendants of v2 candidates are dropped. + if !allow_v2_receipts { + log::debug!( + target: LOG_TARGET, + "V2 candidate descriptors not allowed. Dropping candidate {:?} for paraid {:?}.", + candidate.candidate().hash(), + candidate.descriptor().para_id() + ); + return false + } + + let Some(session_index) = candidate.descriptor().session_index() else { + log::debug!( + target: LOG_TARGET, + "Invalid V2 candidate receipt {:?} for paraid {:?}, missing session index.", + candidate.candidate().hash(), + candidate.descriptor().para_id(), + ); + return false + }; + + // Check if session index is equal to current session index. + if session_index != shared::CurrentSessionIndex::::get() { + log::debug!( + target: LOG_TARGET, + "Dropping V2 candidate receipt {:?} for paraid {:?}, invalid session index {}, current session {}", + candidate.candidate().hash(), + candidate.descriptor().para_id(), + session_index, + shared::CurrentSessionIndex::::get() + ); + return false + } + + // Get the claim queue snapshot at the candidate relay parent. + let Some((rp_info, _)) = + allowed_relay_parents.acquire_info(candidate.descriptor().relay_parent(), None) + else { + log::debug!( + target: LOG_TARGET, + "Relay parent {:?} for candidate {:?} is not in the allowed relay parents.", + candidate.descriptor().relay_parent(), + candidate.candidate().hash(), + ); + return false + }; + + // Check validity of `core_index`. + if let Err(err) = candidate.candidate().check_core_index(&rp_info.claim_queue) { + log::debug!( + target: LOG_TARGET, + "Dropping candidate {:?} for paraid {:?}, {:?}", + candidate.candidate().hash(), + candidate.descriptor().para_id(), + err, + ); + + return false + } + true +} + /// Performs various filtering on the backed candidates inherent data. /// Must maintain the invariant that the returned candidate collection contains the candidates /// sorted in dependency order for each para. When doing any filtering, we must therefore drop any @@ -1001,18 +1089,10 @@ fn sanitize_backed_candidates( // Map the candidates to the right paraids, while making sure that the order between candidates // of the same para is preserved. let mut candidates_per_para: BTreeMap> = BTreeMap::new(); + for candidate in backed_candidates { - // Drop any v2 candidate receipts if nodes are not allowed to use them. - // It is mandatory to filter these before calling `filter_unchained_candidates` to ensure - // any v1 descendants of v2 candidates are dropped. - if !allow_v2_receipts && candidate.descriptor().version() == CandidateDescriptorVersion::V2 + if !sanitize_backed_candidate_v2::(&candidate, allowed_relay_parents, allow_v2_receipts) { - log::debug!( - target: LOG_TARGET, - "V2 candidate descriptors not allowed. Dropping candidate {:?} for paraid {:?}.", - candidate.candidate().hash(), - candidate.descriptor().para_id() - ); continue } @@ -1210,6 +1290,7 @@ fn filter_backed_statements_from_disabled_validators< // 1. Core index assigned to the parachain which has produced the candidate // 2. The relay chain block number of the candidate retain_candidates::(backed_candidates_with_core, |para_id, (bc, core_idx)| { + // `CoreIndex` not used, we just need a copy to write it back later. let (validator_indices, maybe_core_index) = bc.validator_indices_and_core_index(core_index_enabled); let mut validator_indices = BitVec::<_>::from(validator_indices); @@ -1368,8 +1449,8 @@ fn filter_unchained_candidates= 1 && core_index_enabled { - // We must preserve the dependency order given in the input. - let mut temp_backed_candidates = Vec::with_capacity(scheduled_cores.len()); - - for candidate in backed_candidates { - if scheduled_cores.len() == 0 { - // We've got candidates for all of this para's assigned cores. Move on to - // the next para. - log::debug!( - target: LOG_TARGET, - "Found enough candidates for paraid: {:?}.", - candidate.descriptor().para_id() - ); - break; - } - let maybe_injected_core_index: Option = - get_injected_core_index::(allowed_relay_parents, &candidate); - - if let Some(core_index) = maybe_injected_core_index { - if scheduled_cores.remove(&core_index) { - temp_backed_candidates.push((candidate, core_index)); - } else { - // if we got a candidate for a core index which is not scheduled, stop - // the work for this para. the already processed candidate chain in - // temp_backed_candidates is still fine though. - log::debug!( - target: LOG_TARGET, - "Found a backed candidate {:?} with injected core index {}, which is not scheduled for paraid {:?}.", - candidate.candidate().hash(), - core_index.0, - candidate.descriptor().para_id() - ); - - break; - } - } else { - // if we got a candidate which does not contain its core index, stop the - // work for this para. the already processed candidate chain in - // temp_backed_candidates is still fine though. - - log::debug!( - target: LOG_TARGET, - "Found a backed candidate {:?} with no injected core index, for paraid {:?} which has multiple scheduled cores.", - candidate.candidate().hash(), - candidate.descriptor().para_id() - ); - - break; - } - } - - if !temp_backed_candidates.is_empty() { - backed_candidates_with_core - .entry(para_id) - .or_insert_with(|| vec![]) - .extend(temp_backed_candidates); + if let Some(core_index) = + get_core_index::(core_index_enabled, allowed_relay_parents, &candidate) + { + if scheduled_cores.remove(&core_index) { + temp_backed_candidates.push((candidate, core_index)); + } else { + // if we got a candidate for a core index which is not scheduled, stop + // the work for this para. the already processed candidate chain in + // temp_backed_candidates is still fine though. + log::debug!( + target: LOG_TARGET, + "Found a backed candidate {:?} with core index {}, which is not scheduled for paraid {:?}.", + candidate.candidate().hash(), + core_index.0, + candidate.descriptor().para_id() + ); + + break; } } else { - log::warn!( + // No core index is fine, if para has just 1 core assigned. + if scheduled_cores.len() == 1 { + temp_backed_candidates + .push((candidate, scheduled_cores.pop_first().expect("Length is 1"))); + break; + } + + // if we got a candidate which does not contain its core index, stop the + // work for this para. the already processed candidate chain in + // temp_backed_candidates is still fine though. + + log::debug!( target: LOG_TARGET, - "Found a paraid {:?} which has multiple scheduled cores but ElasticScalingMVP feature is not enabled: {:?}", - para_id, - scheduled_cores + "Found a backed candidate {:?} without core index information, but paraid {:?} has multiple scheduled cores.", + candidate.candidate().hash(), + candidate.descriptor().para_id() ); + + break; } - } else { - log::debug!( - target: LOG_TARGET, - "Paraid: {:?} has no entry in scheduled cores but {} candidates were supplied.", - para_id, - backed_candidates.len() - ); + } + + if !temp_backed_candidates.is_empty() { + backed_candidates_with_core + .entry(para_id) + .or_insert_with(|| vec![]) + .extend(temp_backed_candidates); } } backed_candidates_with_core } +// Must be called only for candidates that have been sanitized already. +fn get_core_index( + core_index_enabled: bool, + allowed_relay_parents: &AllowedRelayParentsTracker>, + candidate: &BackedCandidate, +) -> Option { + candidate.candidate().descriptor.core_index().or_else(|| { + get_injected_core_index::(core_index_enabled, allowed_relay_parents, &candidate) + }) +} + fn get_injected_core_index( + core_index_enabled: bool, allowed_relay_parents: &AllowedRelayParentsTracker>, candidate: &BackedCandidate, ) -> 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(true); + 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 }; diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index ac42ac1611d..f5c3d507776 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -44,8 +44,11 @@ fn default_config() -> MockGenesisConfig { #[cfg(not(feature = "runtime-benchmarks"))] mod enter { use super::{inclusion::tests::TestCandidateBuilder, *}; + use polkadot_primitives::vstaging::{ClaimQueueOffset, CoreSelector, UMPSignal, UMP_SEPARATOR}; + use rstest::rstest; + use crate::{ - builder::{Bench, BenchBuilder}, + builder::{junk_collator, junk_collator_signature, Bench, BenchBuilder, CandidateModifier}, mock::{mock_assigner, new_test_ext, BlockLength, BlockWeights, RuntimeOrigin, Test}, scheduler::{ common::{Assignment, AssignmentProvider}, @@ -59,7 +62,8 @@ mod enter { use frame_support::assert_ok; use frame_system::limits; use polkadot_primitives::{ - vstaging::InternalVersion, AvailabilityBitfield, SchedulerParams, UncheckedSigned, + vstaging::{CandidateDescriptorV2, CommittedCandidateReceiptV2, InternalVersion}, + AvailabilityBitfield, CandidateDescriptor, UncheckedSigned, }; use sp_runtime::Perbill; @@ -73,6 +77,7 @@ mod enter { elastic_paras: BTreeMap, unavailable_cores: Vec, v2_descriptor: bool, + candidate_modifier: Option::Hash>>, } fn make_inherent_data( @@ -86,6 +91,7 @@ mod enter { elastic_paras, unavailable_cores, v2_descriptor, + candidate_modifier, }: TestConfig, ) -> Bench { let extra_cores = elastic_paras @@ -104,7 +110,8 @@ mod enter { .set_dispute_sessions(&dispute_sessions[..]) .set_fill_claimqueue(fill_claimqueue) .set_unavailable_cores(unavailable_cores) - .set_candidate_descriptor_v2(v2_descriptor); + .set_candidate_descriptor_v2(v2_descriptor) + .set_candidate_modifier(candidate_modifier); // Setup some assignments as needed: mock_assigner::Pallet::::set_core_count(builder.max_cores()); @@ -126,15 +133,25 @@ mod enter { } } - #[test] + #[rstest] + #[case(true)] + #[case(false)] // Validate that if we create 2 backed candidates which are assigned to 2 cores that will be // freed via becoming fully available, the backed candidates will not be filtered out in // `create_inherent` and will not cause `enter` to early. - fn include_backed_candidates() { + fn include_backed_candidates(#[case] v2_descriptor: bool) { let config = MockGenesisConfig::default(); assert!(config.configuration.config.scheduler_params.lookahead > 0); new_test_ext(config).execute_with(|| { + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + v2_descriptor, + ) + .unwrap(); + let dispute_statements = BTreeMap::new(); let mut backed_and_concluding = BTreeMap::new(); @@ -147,10 +164,11 @@ mod enter { backed_and_concluding, num_validators_per_core: 1, code_upgrade: None, - fill_claimqueue: false, + fill_claimqueue: true, elastic_paras: BTreeMap::new(), unavailable_cores: vec![], - v2_descriptor: false, + v2_descriptor, + candidate_modifier: None, }); // We expect the scenario to have cores 0 & 1 with pending availability. The backed @@ -171,9 +189,6 @@ mod enter { .put_data(PARACHAINS_INHERENT_IDENTIFIER, &expected_para_inherent_data) .unwrap(); - // The current schedule is empty prior to calling `create_inherent_enter`. - assert!(scheduler::Pallet::::claim_queue_is_empty()); - // Nothing is filtered out (including the backed candidates.) assert_eq!( Pallet::::create_inherent_inner(&inherent_data.clone()).unwrap(), @@ -212,8 +227,14 @@ mod enter { }); } - #[test] - fn include_backed_candidates_elastic_scaling() { + #[rstest] + #[case(true, false)] + #[case(true, true)] + #[case(false, true)] + fn include_backed_candidates_elastic_scaling( + #[case] v2_descriptor: bool, + #[case] injected_core: bool, + ) { // ParaId 0 has one pending candidate on core 0. // ParaId 1 has one pending candidate on core 1. // ParaId 2 has three pending candidates on cores 2, 3 and 4. @@ -226,7 +247,15 @@ mod enter { configuration::Pallet::::set_node_feature( RuntimeOrigin::root(), FeatureIndex::ElasticScalingMVP as u8, - true, + injected_core, + ) + .unwrap(); + + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + v2_descriptor, ) .unwrap(); @@ -243,10 +272,11 @@ mod enter { backed_and_concluding, num_validators_per_core: 1, code_upgrade: None, - fill_claimqueue: false, + fill_claimqueue: true, elastic_paras: [(2, 3)].into_iter().collect(), unavailable_cores: vec![], - v2_descriptor: false, + v2_descriptor, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -263,9 +293,6 @@ mod enter { .put_data(PARACHAINS_INHERENT_IDENTIFIER, &expected_para_inherent_data) .unwrap(); - // The current schedule is empty prior to calling `create_inherent_enter`. - assert!(scheduler::Pallet::::claim_queue_is_empty()); - assert!(pallet::OnChainVotes::::get().is_none()); // Nothing is filtered out (including the backed candidates.) @@ -352,6 +379,7 @@ mod enter { elastic_paras: [(2, 4)].into_iter().collect(), unavailable_cores: unavailable_cores.clone(), v2_descriptor: false, + candidate_modifier: None, }); let mut expected_para_inherent_data = scenario.data.clone(); @@ -609,6 +637,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -683,6 +712,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -755,6 +785,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -843,6 +874,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -931,6 +963,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -991,6 +1024,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1078,6 +1112,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1186,6 +1221,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1255,6 +1291,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1322,6 +1359,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1388,6 +1426,15 @@ mod enter { ccr.commitments.processed_downward_messages = idx as u32; let core_index = start_core_index + idx; + // `UMPSignal` separator. + ccr.commitments.upward_messages.force_push(UMP_SEPARATOR); + + // `SelectCore` commitment. + // Claim queue offset must be `0`` so this candidate is for the very next block. + ccr.commitments.upward_messages.force_push( + UMPSignal::SelectCore(CoreSelector(idx as u8), ClaimQueueOffset(0)).encode(), + ); + BackedCandidate::new( ccr.into(), Default::default(), @@ -1400,8 +1447,10 @@ mod enter { // Ensure that overweight parachain inherents are always rejected by the runtime. // Runtime should panic and return `InherentOverweight` error. - #[test] - fn test_backed_candidates_apply_weight_works_for_elastic_scaling() { + #[rstest] + #[case(true)] + #[case(false)] + fn test_backed_candidates_apply_weight_works_for_elastic_scaling(#[case] v2_descriptor: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { let seed = [ 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, @@ -1412,6 +1461,14 @@ mod enter { // Create an overweight inherent and oversized block let mut backed_and_concluding = BTreeMap::new(); + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + v2_descriptor, + ) + .unwrap(); + for i in 0..30 { backed_and_concluding.insert(i, i); } @@ -1425,7 +1482,8 @@ mod enter { fill_claimqueue: false, elastic_paras: BTreeMap::new(), unavailable_cores: vec![], - v2_descriptor: false, + v2_descriptor, + candidate_modifier: None, }); let mut para_inherent_data = scenario.data.clone(); @@ -1516,6 +1574,7 @@ mod enter { elastic_paras: BTreeMap::new(), unavailable_cores: vec![], v2_descriptor: false, + candidate_modifier: None, }); let expected_para_inherent_data = scenario.data.clone(); @@ -1572,10 +1631,10 @@ mod enter { num_validators_per_core: 5, code_upgrade: None, fill_claimqueue: true, - // 8 cores ! elastic_paras: [(2, 8)].into_iter().collect(), unavailable_cores: unavailable_cores.clone(), v2_descriptor: true, + candidate_modifier: None, }); let mut unfiltered_para_inherent_data = scenario.data.clone(); @@ -1614,6 +1673,432 @@ mod enter { assert_eq!(dispatch_error, Error::::CandidatesFilteredDuringExecution.into()); }); } + + #[test] + fn too_many_ump_signals() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + + let unavailable_cores = vec![]; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 5, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: [(2, 8)].into_iter().collect(), + unavailable_cores: unavailable_cores.clone(), + v2_descriptor: true, + candidate_modifier: Some(|mut candidate: CommittedCandidateReceiptV2| { + if candidate.descriptor.para_id() == 2.into() { + // Add an extra message so `verify_backed_candidates` fails. + candidate.commitments.upward_messages.force_push( + UMPSignal::SelectCore(CoreSelector(123 as u8), ClaimQueueOffset(2)) + .encode(), + ); + } + candidate + }), + }); + + let unfiltered_para_inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (5 validators per core, 10 backed candidates) + assert_eq!(unfiltered_para_inherent_data.bitfields.len(), 50); + // * 10 v2 candidate descriptors. + assert_eq!(unfiltered_para_inherent_data.backed_candidates.len(), 10); + + let mut inherent_data = InherentData::new(); + inherent_data + .put_data(PARACHAINS_INHERENT_IDENTIFIER, &unfiltered_para_inherent_data) + .unwrap(); + + let dispatch_error = Pallet::::enter( + frame_system::RawOrigin::None.into(), + unfiltered_para_inherent_data, + ) + .unwrap_err() + .error; + + // We expect `enter` to fail because the inherent data contains backed candidates with + // v2 descriptors. + assert_eq!(dispatch_error, Error::::CandidatesFilteredDuringExecution.into()); + }); + } + + #[test] + fn invalid_ump_signals() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + + let unavailable_cores = vec![]; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 5, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: [(2, 8)].into_iter().collect(), + unavailable_cores: unavailable_cores.clone(), + v2_descriptor: true, + candidate_modifier: Some(|mut candidate: CommittedCandidateReceiptV2| { + if candidate.descriptor.para_id() == 1.into() { + // Drop the core selector to make it invalid + candidate + .commitments + .upward_messages + .truncate(candidate.commitments.upward_messages.len() - 1); + } + candidate + }), + }); + + let unfiltered_para_inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (5 validators per core, 10 backed candidates) + assert_eq!(unfiltered_para_inherent_data.bitfields.len(), 50); + // * 10 v2 candidate descriptors. + assert_eq!(unfiltered_para_inherent_data.backed_candidates.len(), 10); + + let mut inherent_data = InherentData::new(); + inherent_data + .put_data(PARACHAINS_INHERENT_IDENTIFIER, &unfiltered_para_inherent_data) + .unwrap(); + + let dispatch_error = Pallet::::enter( + frame_system::RawOrigin::None.into(), + unfiltered_para_inherent_data, + ) + .unwrap_err() + .error; + + // We expect `enter` to fail because the inherent data contains backed candidates with + // v2 descriptors. + assert_eq!(dispatch_error, Error::::CandidatesFilteredDuringExecution.into()); + }); + } + #[test] + fn v2_descriptors_are_accepted() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::ElasticScalingMVP as u8, + true, + ) + .unwrap(); + + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + + let unavailable_cores = vec![]; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 1, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: [(2, 3)].into_iter().collect(), + unavailable_cores: unavailable_cores.clone(), + v2_descriptor: true, + candidate_modifier: None, + }); + + let inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (2 validators per core, 5 backed candidates) + assert_eq!(inherent_data.bitfields.len(), 5); + // * 5 v2 candidate descriptors. + assert_eq!(inherent_data.backed_candidates.len(), 5); + + Pallet::::enter(frame_system::RawOrigin::None.into(), inherent_data).unwrap(); + }); + } + + // Test when parachain runtime is upgraded to support the new commitments + // but some collators are not and provide v1 descriptors. + #[test] + fn elastic_scaling_mixed_v1_v2_descriptors() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::ElasticScalingMVP as u8, + true, + ) + .unwrap(); + + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + + let unavailable_cores = vec![]; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 1, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: [(2, 3)].into_iter().collect(), + unavailable_cores: unavailable_cores.clone(), + v2_descriptor: true, + candidate_modifier: None, + }); + + let mut inherent_data = scenario.data.clone(); + let candidate_count = inherent_data.backed_candidates.len(); + + // Make last 2 candidates v1 + for index in candidate_count - 2..candidate_count { + let encoded = inherent_data.backed_candidates[index].descriptor().encode(); + + let mut decoded: CandidateDescriptor = + Decode::decode(&mut encoded.as_slice()).unwrap(); + decoded.collator = junk_collator(); + decoded.signature = junk_collator_signature(); + + *inherent_data.backed_candidates[index].descriptor_mut() = + Decode::decode(&mut encoded.as_slice()).unwrap(); + } + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (2 validators per core, 5 backed candidates) + assert_eq!(inherent_data.bitfields.len(), 5); + // * 5 v2 candidate descriptors. + assert_eq!(inherent_data.backed_candidates.len(), 5); + + Pallet::::enter(frame_system::RawOrigin::None.into(), inherent_data).unwrap(); + }); + } + + // Mixed test with v1, v2 with/without `UMPSignal::SelectCore` + #[test] + fn mixed_v1_and_v2_optional_commitments() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::ElasticScalingMVP as u8, + true, + ) + .unwrap(); + + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + backed_and_concluding.insert(3, 1); + backed_and_concluding.insert(4, 1); + + let unavailable_cores = vec![]; + + let candidate_modifier = |mut candidate: CommittedCandidateReceiptV2| { + // first candidate has v2 descriptor with no commitments + if candidate.descriptor.para_id() == ParaId::from(0) { + candidate.commitments.upward_messages.clear(); + } + + if candidate.descriptor.para_id() > ParaId::from(2) { + let mut v1: CandidateDescriptor = candidate.descriptor.into(); + + v1.collator = junk_collator(); + v1.signature = junk_collator_signature(); + + candidate.descriptor = v1.into(); + } + candidate + }; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 1, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: Default::default(), + unavailable_cores: unavailable_cores.clone(), + v2_descriptor: true, + candidate_modifier: Some(candidate_modifier), + }); + + let inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (2 validators per core, 5 backed candidates) + assert_eq!(inherent_data.bitfields.len(), 5); + // * 5 v2 candidate descriptors. + assert_eq!(inherent_data.backed_candidates.len(), 5); + + Pallet::::enter(frame_system::RawOrigin::None.into(), inherent_data).unwrap(); + }); + } + + // A test to ensure that the `paras_inherent` filters out candidates with invalid + // session index in the descriptor. + #[test] + fn invalid_session_index() { + let config = default_config(); + assert!(config.configuration.config.scheduler_params.lookahead > 0); + new_test_ext(config).execute_with(|| { + // Set the elastic scaling MVP feature. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::ElasticScalingMVP as u8, + true, + ) + .unwrap(); + + // Enable the v2 receipts. + configuration::Pallet::::set_node_feature( + RuntimeOrigin::root(), + FeatureIndex::CandidateReceiptV2 as u8, + true, + ) + .unwrap(); + + let mut backed_and_concluding = BTreeMap::new(); + backed_and_concluding.insert(0, 1); + backed_and_concluding.insert(1, 1); + backed_and_concluding.insert(2, 1); + + let unavailable_cores = vec![]; + + let scenario = make_inherent_data(TestConfig { + dispute_statements: BTreeMap::new(), + dispute_sessions: vec![], // No disputes + backed_and_concluding, + num_validators_per_core: 1, + code_upgrade: None, + fill_claimqueue: true, + elastic_paras: [(2, 3)].into_iter().collect(), + unavailable_cores, + v2_descriptor: true, + candidate_modifier: None, + }); + + let mut inherent_data = scenario.data.clone(); + + // Check the para inherent data is as expected: + // * 1 bitfield per validator (2 validators per core, 5 backed candidates) + assert_eq!(inherent_data.bitfields.len(), 5); + // * 5 v2 candidate descriptors passed, 1 is invalid + assert_eq!(inherent_data.backed_candidates.len(), 5); + + let index = inherent_data.backed_candidates.len() - 1; + + // Put invalid session index in last candidate + let backed_candidate = inherent_data.backed_candidates[index].clone(); + + let candidate = CommittedCandidateReceiptV2 { + descriptor: CandidateDescriptorV2::new( + backed_candidate.descriptor().para_id(), + backed_candidate.descriptor().relay_parent(), + backed_candidate.descriptor().core_index().unwrap(), + 100, + backed_candidate.descriptor().persisted_validation_data_hash(), + backed_candidate.descriptor().pov_hash(), + backed_candidate.descriptor().erasure_root(), + backed_candidate.descriptor().para_head(), + backed_candidate.descriptor().validation_code_hash(), + ), + commitments: backed_candidate.candidate().commitments.clone(), + }; + + inherent_data.backed_candidates[index] = BackedCandidate::new( + candidate, + backed_candidate.validity_votes().to_vec(), + backed_candidate.validator_indices_and_core_index(false).0.into(), + None, + ); + + let mut expected_inherent_data = inherent_data.clone(); + expected_inherent_data.backed_candidates.truncate(index); + + let mut create_inherent_data = InherentData::new(); + create_inherent_data + .put_data(PARACHAINS_INHERENT_IDENTIFIER, &inherent_data) + .unwrap(); + + // 1 candidate with invalid session is filtered out + assert_eq!( + Pallet::::create_inherent_inner(&create_inherent_data).unwrap(), + expected_inherent_data + ); + + Pallet::::enter(frame_system::RawOrigin::None.into(), inherent_data).unwrap_err(); + }); + } } fn default_header() -> polkadot_primitives::Header { @@ -1913,6 +2398,7 @@ mod sanitizers { shared::Pallet::::add_allowed_relay_parent( default_header().hash(), Default::default(), + Default::default(), RELAY_PARENT_NUM, 1, ); @@ -2101,18 +2587,12 @@ mod sanitizers { // Para 6 is not scheduled. One candidate supplied. // Para 7 is scheduled on core 7 and 8, but the candidate contains the wrong core index. // Para 8 is scheduled on core 9, but the candidate contains the wrong core index. - fn get_test_data_multiple_cores_per_para(core_index_enabled: bool) -> TestData { + fn get_test_data_multiple_cores_per_para( + core_index_enabled: bool, + v2_descriptor: 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); @@ -2231,6 +2711,21 @@ mod sanitizers { ), ])); + // 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( + relay_parent, + Default::default(), + scheduler::ClaimQueue::::get() + .into_iter() + .map(|(core_index, paras)| { + (core_index, paras.into_iter().map(|e| e.para_id()).collect()) + }) + .collect(), + RELAY_PARENT_NUM, + 1, + ); + // Set the on-chain included head data and current code hash. for id in 1..=8u32 { paras::Pallet::::set_current_head(ParaId::from(id), HeadData(vec![id as u8])); @@ -2260,6 +2755,14 @@ mod sanitizers { let mut backed_candidates = vec![]; let mut expected_backed_candidates_with_core = BTreeMap::new(); + let maybe_core_index = |core_index: CoreIndex| -> Option { + if !v2_descriptor { + None + } else { + Some(core_index) + } + }; + // Para 1 { let candidate = TestCandidateBuilder { @@ -2276,6 +2779,7 @@ mod sanitizers { hrmp_watermark: RELAY_PARENT_NUM, head_data: HeadData(vec![1, 1]), validation_code: ValidationCode(vec![1]), + core_index: maybe_core_index(CoreIndex(0)), ..Default::default() } .build(); @@ -2291,7 +2795,7 @@ mod sanitizers { core_index_enabled.then_some(CoreIndex(0 as u32)), ); backed_candidates.push(backed.clone()); - if core_index_enabled { + if core_index_enabled || v2_descriptor { expected_backed_candidates_with_core .entry(ParaId::from(1)) .or_insert(vec![]) @@ -2312,6 +2816,8 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![1]), + core_index: maybe_core_index(CoreIndex(1)), + core_selector: Some(1), ..Default::default() } .build(); @@ -2326,7 +2832,7 @@ mod sanitizers { core_index_enabled.then_some(CoreIndex(1 as u32)), ); backed_candidates.push(backed.clone()); - if core_index_enabled { + if core_index_enabled || v2_descriptor { expected_backed_candidates_with_core .entry(ParaId::from(1)) .or_insert(vec![]) @@ -2349,6 +2855,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![2]), + core_index: maybe_core_index(CoreIndex(2)), ..Default::default() } .build(); @@ -2363,7 +2870,7 @@ mod sanitizers { core_index_enabled.then_some(CoreIndex(2 as u32)), ); backed_candidates.push(backed.clone()); - if core_index_enabled { + if core_index_enabled || v2_descriptor { expected_backed_candidates_with_core .entry(ParaId::from(2)) .or_insert(vec![]) @@ -2386,6 +2893,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![3]), + core_index: maybe_core_index(CoreIndex(4)), ..Default::default() } .build(); @@ -2421,6 +2929,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![4]), + core_index: maybe_core_index(CoreIndex(5)), ..Default::default() } .build(); @@ -2455,6 +2964,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![4]), + core_index: maybe_core_index(CoreIndex(5)), ..Default::default() } .build(); @@ -2488,6 +2998,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![6]), + core_index: maybe_core_index(CoreIndex(6)), ..Default::default() } .build(); @@ -2519,6 +3030,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![7]), + core_index: maybe_core_index(CoreIndex(6)), ..Default::default() } .build(); @@ -2550,6 +3062,7 @@ mod sanitizers { .hash(), hrmp_watermark: RELAY_PARENT_NUM, validation_code: ValidationCode(vec![8]), + core_index: maybe_core_index(CoreIndex(7)), ..Default::default() } .build(); @@ -2564,7 +3077,7 @@ mod sanitizers { core_index_enabled.then_some(CoreIndex(7 as u32)), ); backed_candidates.push(backed.clone()); - if !core_index_enabled { + if !core_index_enabled && !v2_descriptor { expected_backed_candidates_with_core .entry(ParaId::from(8)) .or_insert(vec![]) @@ -2625,13 +3138,6 @@ mod sanitizers { let header = default_header(); let relay_parent = header.hash(); - shared::Pallet::::add_allowed_relay_parent( - relay_parent, - Default::default(), - RELAY_PARENT_NUM, - 1, - ); - let session_index = SessionIndex::from(0_u32); let keystore = LocalKeystore::in_memory(); @@ -2743,6 +3249,19 @@ mod sanitizers { ), ])); + shared::Pallet::::add_allowed_relay_parent( + relay_parent, + Default::default(), + scheduler::ClaimQueue::::get() + .into_iter() + .map(|(core_index, paras)| { + (core_index, paras.into_iter().map(|e| e.para_id()).collect()) + }) + .collect(), + RELAY_PARENT_NUM, + 1, + ); + // Set the on-chain included head data and current code hash. for id in 1..=4u32 { paras::Pallet::::set_current_head(ParaId::from(id), HeadData(vec![id as u8])); @@ -3128,6 +3647,7 @@ mod sanitizers { shared::Pallet::::add_allowed_relay_parent( prev_relay_parent, Default::default(), + Default::default(), RELAY_PARENT_NUM - 1, 2, ); @@ -3135,6 +3655,7 @@ mod sanitizers { shared::Pallet::::add_allowed_relay_parent( relay_parent, Default::default(), + Default::default(), RELAY_PARENT_NUM, 2, ); @@ -3142,6 +3663,7 @@ mod sanitizers { shared::Pallet::::add_allowed_relay_parent( next_relay_parent, Default::default(), + Default::default(), RELAY_PARENT_NUM + 1, 2, ); @@ -3534,15 +4056,20 @@ mod sanitizers { } #[rstest] - #[case(false)] - #[case(true)] - fn test_with_multiple_cores_per_para(#[case] core_index_enabled: bool) { + #[case(false, false)] + #[case(true, false)] + #[case(false, true)] + #[case(true, true)] + fn test_with_multiple_cores_per_para( + #[case] core_index_enabled: bool, + #[case] v2_descriptor: bool, + ) { new_test_ext(default_config()).execute_with(|| { let TestData { backed_candidates, expected_backed_candidates_with_core, scheduled_paras: scheduled, - } = get_test_data_multiple_cores_per_para(core_index_enabled); + } = get_test_data_multiple_cores_per_para(core_index_enabled, v2_descriptor); assert_eq!( sanitize_backed_candidates::( @@ -3551,7 +4078,7 @@ mod sanitizers { BTreeSet::new(), scheduled, core_index_enabled, - false, + v2_descriptor, ), expected_backed_candidates_with_core, ); @@ -3738,17 +4265,22 @@ mod sanitizers { // nothing is scheduled, so no paraids match, thus all backed candidates are skipped #[rstest] - #[case(false, false)] - #[case(true, true)] - #[case(false, true)] - #[case(true, false)] + #[case(false, false, true)] + #[case(true, true, true)] + #[case(false, true, true)] + #[case(true, false, true)] + #[case(false, false, false)] + #[case(true, true, false)] + #[case(false, true, false)] + #[case(true, false, false)] fn nothing_scheduled( #[case] core_index_enabled: bool, #[case] multiple_cores_per_para: bool, + #[case] v2_descriptor: bool, ) { new_test_ext(default_config()).execute_with(|| { let TestData { backed_candidates, .. } = if multiple_cores_per_para { - get_test_data_multiple_cores_per_para(core_index_enabled) + get_test_data_multiple_cores_per_para(core_index_enabled, v2_descriptor) } else { get_test_data_one_core_per_para(core_index_enabled) }; @@ -3805,8 +4337,14 @@ mod sanitizers { } // candidates that have concluded as invalid are filtered out, as well as their descendants. - #[test] - fn concluded_invalid_are_filtered_out_multiple_cores_per_para() { + #[rstest] + #[case(false, true)] + #[case(true, false)] + #[case(true, true)] + fn concluded_invalid_are_filtered_out_multiple_cores_per_para( + #[case] core_index_enabled: bool, + #[case] v2_descriptor: bool, + ) { // Mark the first candidate of paraid 1 as invalid. Its descendant should also // be dropped. Also mark the candidate of paraid 3 as invalid. new_test_ext(default_config()).execute_with(|| { @@ -3815,7 +4353,7 @@ mod sanitizers { scheduled_paras: scheduled, mut expected_backed_candidates_with_core, .. - } = get_test_data_multiple_cores_per_para(true); + } = get_test_data_multiple_cores_per_para(core_index_enabled, v2_descriptor); let mut invalid_set = std::collections::BTreeSet::new(); @@ -3834,8 +4372,8 @@ mod sanitizers { &shared::AllowedRelayParents::::get(), invalid_set, scheduled, - true, - false, + core_index_enabled, + v2_descriptor, ); // We'll be left with candidates from paraid 2 and 4. @@ -3854,7 +4392,7 @@ mod sanitizers { scheduled_paras: scheduled, mut expected_backed_candidates_with_core, .. - } = get_test_data_multiple_cores_per_para(true); + } = get_test_data_multiple_cores_per_para(core_index_enabled, v2_descriptor); let mut invalid_set = std::collections::BTreeSet::new(); @@ -3871,8 +4409,8 @@ mod sanitizers { &shared::AllowedRelayParents::::get(), invalid_set, scheduled, - true, - false, + core_index_enabled, + v2_descriptor, ); // Only the second candidate of paraid 1 should be removed. @@ -4083,7 +4621,7 @@ mod sanitizers { // Disable Bob, only the second candidate of paraid 1 should be removed. new_test_ext(default_config()).execute_with(|| { let TestData { mut expected_backed_candidates_with_core, .. } = - get_test_data_multiple_cores_per_para(true); + get_test_data_multiple_cores_per_para(true, false); set_disabled_validators(vec![1]); @@ -4105,7 +4643,7 @@ mod sanitizers { for disabled in [vec![0], vec![0, 1]] { new_test_ext(default_config()).execute_with(|| { let TestData { mut expected_backed_candidates_with_core, .. } = - get_test_data_multiple_cores_per_para(true); + get_test_data_multiple_cores_per_para(true, false); set_disabled_validators(disabled); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index 51e33001609..a0996d5df0e 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -455,8 +455,22 @@ pub fn backing_state( // // Thus, minimum relay parent is ensured to have asynchronous backing enabled. let now = frame_system::Pallet::::block_number(); - let min_relay_parent_number = shared::AllowedRelayParents::::get() - .hypothetical_earliest_block_number(now, config.async_backing_params.allowed_ancestry_len); + + // Use the right storage depending on version to ensure #64 doesn't cause issues with this + // migration. + let min_relay_parent_number = if shared::Pallet::::on_chain_storage_version() == + StorageVersion::new(0) + { + shared::migration::v0::AllowedRelayParents::::get().hypothetical_earliest_block_number( + now, + config.async_backing_params.allowed_ancestry_len, + ) + } else { + shared::AllowedRelayParents::::get().hypothetical_earliest_block_number( + now, + config.async_backing_params.allowed_ancestry_len, + ) + }; let required_parent = paras::Heads::::get(para_id)?; let validation_code_hash = paras::CurrentCodeHash::::get(para_id)?; diff --git a/polkadot/runtime/parachains/src/shared.rs b/polkadot/runtime/parachains/src/shared.rs index 154b7cfefc3..f582bf0d90b 100644 --- a/polkadot/runtime/parachains/src/shared.rs +++ b/polkadot/runtime/parachains/src/shared.rs @@ -20,12 +20,14 @@ //! dependent on any of the other pallets. use alloc::{ - collections::{btree_map::BTreeMap, vec_deque::VecDeque}, + collections::{btree_map::BTreeMap, btree_set::BTreeSet, vec_deque::VecDeque}, vec::Vec, }; use frame_support::{pallet_prelude::*, traits::DisabledValidators}; use frame_system::pallet_prelude::BlockNumberFor; -use polkadot_primitives::{SessionIndex, ValidatorId, ValidatorIndex}; +use polkadot_primitives::{ + vstaging::transpose_claim_queue, CoreIndex, Id, SessionIndex, ValidatorId, ValidatorIndex, +}; use sp_runtime::traits::AtLeast32BitUnsigned; use rand::{seq::SliceRandom, SeedableRng}; @@ -43,16 +45,28 @@ pub(crate) const SESSION_DELAY: SessionIndex = 2; #[cfg(test)] mod tests; -/// Information about past relay-parents. +pub mod migration; + +/// Information about a relay parent. +#[derive(Encode, Decode, Default, TypeInfo, Debug)] +pub struct RelayParentInfo { + // Relay parent hash + pub relay_parent: Hash, + // The state root at this block + pub state_root: Hash, + // Claim queue snapshot, optimized for accessing the assignments by `ParaId`. + // For each para we store the cores assigned per depth. + pub claim_queue: BTreeMap>>, +} + +/// Keeps tracks of information about all viable relay parents. #[derive(Encode, Decode, Default, TypeInfo)] pub struct AllowedRelayParentsTracker { - // The past relay parents, paired with state roots, that are viable to build upon. + // Information about past relay parents that are viable to build upon. // // They are in ascending chronologic order, so the newest relay parents are at // the back of the deque. - // - // (relay_parent, state_root) - buffer: VecDeque<(Hash, Hash)>, + buffer: VecDeque>, // The number of the most recent relay-parent, if any. // If the buffer is empty, this value has no meaning and may @@ -70,13 +84,17 @@ impl &mut self, relay_parent: Hash, state_root: Hash, + claim_queue: BTreeMap>, number: BlockNumber, max_ancestry_len: u32, ) { + let claim_queue = transpose_claim_queue(claim_queue); + // + 1 for the most recent block, which is always allowed. let buffer_size_limit = max_ancestry_len as usize + 1; - self.buffer.push_back((relay_parent, state_root)); + self.buffer.push_back(RelayParentInfo { relay_parent, state_root, claim_queue }); + self.latest_number = number; while self.buffer.len() > buffer_size_limit { let _ = self.buffer.pop_front(); @@ -96,8 +114,8 @@ impl &self, relay_parent: Hash, prev: Option, - ) -> Option<(Hash, BlockNumber)> { - let pos = self.buffer.iter().position(|(rp, _)| rp == &relay_parent)?; + ) -> Option<(&RelayParentInfo, BlockNumber)> { + let pos = self.buffer.iter().position(|info| info.relay_parent == relay_parent)?; let age = (self.buffer.len() - 1) - pos; let number = self.latest_number - BlockNumber::from(age as u32); @@ -107,7 +125,7 @@ impl } } - Some((self.buffer[pos].1, number)) + Some((&self.buffer[pos], number)) } /// Returns block number of the earliest block the buffer would contain if @@ -127,8 +145,11 @@ impl pub mod pallet { use super::*; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -263,11 +284,12 @@ impl Pallet { pub(crate) fn add_allowed_relay_parent( relay_parent: T::Hash, state_root: T::Hash, + claim_queue: BTreeMap>, number: BlockNumberFor, max_ancestry_len: u32, ) { AllowedRelayParents::::mutate(|tracker| { - tracker.update(relay_parent, state_root, number, max_ancestry_len) + tracker.update(relay_parent, state_root, claim_queue, number, max_ancestry_len) }) } } diff --git a/polkadot/runtime/parachains/src/shared/migration.rs b/polkadot/runtime/parachains/src/shared/migration.rs new file mode 100644 index 00000000000..ae0412c6e26 --- /dev/null +++ b/polkadot/runtime/parachains/src/shared/migration.rs @@ -0,0 +1,196 @@ +// 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 super::*; +use codec::{Decode, Encode}; +use frame_support::{ + pallet_prelude::ValueQuery, traits::UncheckedOnRuntimeUpgrade, weights::Weight, +}; + +#[cfg(feature = "try-runtime")] +const LOG_TARGET: &str = "runtime::shared"; + +pub mod v0 { + use super::*; + use alloc::collections::vec_deque::VecDeque; + + use frame_support::storage_alias; + + /// All allowed relay-parents storage at version 0. + #[storage_alias] + pub(crate) type AllowedRelayParents = StorageValue< + Pallet, + super::v0::AllowedRelayParentsTracker<::Hash, BlockNumberFor>, + ValueQuery, + >; + + #[derive(Encode, Decode, Default, TypeInfo)] + pub struct AllowedRelayParentsTracker { + // The past relay parents, paired with state roots, that are viable to build upon. + // + // They are in ascending chronologic order, so the newest relay parents are at + // the back of the deque. + // + // (relay_parent, state_root) + pub buffer: VecDeque<(Hash, Hash)>, + + // The number of the most recent relay-parent, if any. + // If the buffer is empty, this value has no meaning and may + // be nonsensical. + pub latest_number: BlockNumber, + } + + // Required to workaround #64. + impl + AllowedRelayParentsTracker + { + /// Returns block number of the earliest block the buffer would contain if + /// `now` is pushed into it. + pub(crate) fn hypothetical_earliest_block_number( + &self, + now: BlockNumber, + max_ancestry_len: u32, + ) -> BlockNumber { + let allowed_ancestry_len = max_ancestry_len.min(self.buffer.len() as u32); + + now - allowed_ancestry_len.into() + } + } + + impl From> + for super::AllowedRelayParentsTracker + { + fn from(value: AllowedRelayParentsTracker) -> Self { + Self { + latest_number: value.latest_number, + buffer: value + .buffer + .into_iter() + .map(|(relay_parent, state_root)| super::RelayParentInfo { + relay_parent, + state_root, + claim_queue: Default::default(), + }) + .collect(), + } + } + } +} + +mod v1 { + use super::*; + + #[cfg(feature = "try-runtime")] + use frame_support::{ + ensure, + traits::{GetStorageVersion, StorageVersion}, + }; + + pub struct VersionUncheckedMigrateToV1(core::marker::PhantomData); + + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: LOG_TARGET, "Running pre_upgrade() for shared MigrateToV1"); + let bytes = u32::to_ne_bytes(v0::AllowedRelayParents::::get().buffer.len() as u32); + + Ok(bytes.to_vec()) + } + + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + + // Read old storage. + let old_rp_tracker = v0::AllowedRelayParents::::take(); + + super::AllowedRelayParents::::set(old_rp_tracker.into()); + + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + log::trace!(target: LOG_TARGET, "Running post_upgrade() for shared MigrateToV1"); + ensure!( + Pallet::::on_chain_storage_version() >= StorageVersion::new(1), + "Storage version should be >= 1 after the migration" + ); + + let relay_parent_count = u32::from_ne_bytes( + state + .try_into() + .expect("u32::from_ne_bytes(to_ne_bytes(u32)) always works; qed"), + ); + + let rp_tracker = AllowedRelayParents::::get(); + + ensure!( + relay_parent_count as usize == rp_tracker.buffer.len(), + "Number of allowed relay parents should be the same as the one before the upgrade." + ); + + Ok(()) + } + } +} + +/// Migrate shared module storage to v1. +pub type MigrateToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + v1::VersionUncheckedMigrateToV1, + Pallet, + ::DbWeight, +>; + +#[cfg(test)] +mod tests { + use super::{v1::VersionUncheckedMigrateToV1, *}; + use crate::mock::{new_test_ext, MockGenesisConfig, Test}; + use frame_support::traits::UncheckedOnRuntimeUpgrade; + use polkadot_primitives::Hash; + + #[test] + fn migrate_to_v1() { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let rp_tracker = v0::AllowedRelayParentsTracker { + latest_number: 9, + buffer: (0..10u64) + .into_iter() + .map(|idx| (Hash::from_low_u64_ne(idx), Hash::from_low_u64_ne(2 * idx))) + .collect::>(), + }; + + v0::AllowedRelayParents::::put(rp_tracker); + + as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(); + + let rp_tracker = AllowedRelayParents::::get(); + + assert_eq!(rp_tracker.buffer.len(), 10); + + for idx in 0..10u64 { + let relay_parent = Hash::from_low_u64_ne(idx); + let state_root = Hash::from_low_u64_ne(2 * idx); + let (info, block_num) = rp_tracker.acquire_info(relay_parent, None).unwrap(); + + assert!(info.claim_queue.is_empty()); + assert_eq!(info.relay_parent, relay_parent); + assert_eq!(info.state_root, state_root); + assert_eq!(block_num as u64, idx); + } + }); + } +} diff --git a/polkadot/runtime/parachains/src/shared/tests.rs b/polkadot/runtime/parachains/src/shared/tests.rs index e47d1fd9cfe..6da84e254f0 100644 --- a/polkadot/runtime/parachains/src/shared/tests.rs +++ b/polkadot/runtime/parachains/src/shared/tests.rs @@ -36,22 +36,71 @@ fn tracker_earliest_block_number() { // Push a single block into the tracker, suppose max capacity is 1. let max_ancestry_len = 0; - tracker.update(Hash::zero(), Hash::zero(), 0, max_ancestry_len); + tracker.update(Hash::zero(), Hash::zero(), Default::default(), 0, max_ancestry_len); assert_eq!(tracker.hypothetical_earliest_block_number(now, max_ancestry_len), now); // Test a greater capacity. let max_ancestry_len = 4; let now = 4; for i in 1..now { - tracker.update(Hash::zero(), Hash::zero(), i, max_ancestry_len); + tracker.update(Hash::zero(), Hash::zero(), Default::default(), i, max_ancestry_len); assert_eq!(tracker.hypothetical_earliest_block_number(i + 1, max_ancestry_len), 0); } // Capacity exceeded. - tracker.update(Hash::zero(), Hash::zero(), now, max_ancestry_len); + tracker.update(Hash::zero(), Hash::zero(), Default::default(), now, max_ancestry_len); assert_eq!(tracker.hypothetical_earliest_block_number(now + 1, max_ancestry_len), 1); } +#[test] +fn tracker_claim_queue_remap() { + let mut tracker = AllowedRelayParentsTracker::::default(); + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert(CoreIndex(0), vec![Id::from(0), Id::from(1), Id::from(2)].into()); + claim_queue.insert(CoreIndex(1), vec![Id::from(0), Id::from(0), Id::from(100)].into()); + claim_queue.insert(CoreIndex(2), vec![Id::from(1), Id::from(2), Id::from(100)].into()); + + tracker.update(Hash::zero(), Hash::zero(), claim_queue, 1u32, 3u32); + + let (info, _block_num) = tracker.acquire_info(Hash::zero(), None).unwrap(); + assert_eq!( + info.claim_queue.get(&Id::from(0)).unwrap()[&0], + vec![CoreIndex(0), CoreIndex(1)].into_iter().collect::>() + ); + assert_eq!( + info.claim_queue.get(&Id::from(1)).unwrap()[&0], + vec![CoreIndex(2)].into_iter().collect::>() + ); + assert_eq!(info.claim_queue.get(&Id::from(2)).unwrap().get(&0), None); + assert_eq!(info.claim_queue.get(&Id::from(100)).unwrap().get(&0), None); + + assert_eq!( + info.claim_queue.get(&Id::from(0)).unwrap()[&1], + vec![CoreIndex(1)].into_iter().collect::>() + ); + assert_eq!( + info.claim_queue.get(&Id::from(1)).unwrap()[&1], + vec![CoreIndex(0)].into_iter().collect::>() + ); + assert_eq!( + info.claim_queue.get(&Id::from(2)).unwrap()[&1], + vec![CoreIndex(2)].into_iter().collect::>() + ); + assert_eq!(info.claim_queue.get(&Id::from(100)).unwrap().get(&1), None); + + assert_eq!(info.claim_queue.get(&Id::from(0)).unwrap().get(&2), None); + assert_eq!(info.claim_queue.get(&Id::from(1)).unwrap().get(&2), None); + assert_eq!( + info.claim_queue.get(&Id::from(2)).unwrap()[&2], + vec![CoreIndex(0)].into_iter().collect::>() + ); + assert_eq!( + info.claim_queue.get(&Id::from(100)).unwrap()[&2], + vec![CoreIndex(1), CoreIndex(2)].into_iter().collect::>() + ); +} + #[test] fn tracker_acquire_info() { let mut tracker = AllowedRelayParentsTracker::::default(); @@ -65,20 +114,20 @@ fn tracker_acquire_info() { ]; let (relay_parent, state_root) = blocks[0]; - tracker.update(relay_parent, state_root, 0, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 0, max_ancestry_len); assert_matches!( tracker.acquire_info(relay_parent, None), - Some((s, b)) if s == state_root && b == 0 + Some((s, b)) if s.state_root == state_root && b == 0 ); let (relay_parent, state_root) = blocks[1]; - tracker.update(relay_parent, state_root, 1u32, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 1u32, max_ancestry_len); let (relay_parent, state_root) = blocks[2]; - tracker.update(relay_parent, state_root, 2u32, max_ancestry_len); + tracker.update(relay_parent, state_root, Default::default(), 2u32, max_ancestry_len); for (block_num, (rp, state_root)) in blocks.iter().enumerate().take(2) { assert_matches!( tracker.acquire_info(*rp, None), - Some((s, b)) if &s == state_root && b == block_num as u32 + Some((s, b)) if &s.state_root == state_root && b == block_num as u32 ); assert!(tracker.acquire_info(*rp, Some(2)).is_none()); @@ -87,7 +136,7 @@ fn tracker_acquire_info() { for (block_num, (rp, state_root)) in blocks.iter().enumerate().skip(1) { assert_matches!( tracker.acquire_info(*rp, Some(block_num as u32 - 1)), - Some((s, b)) if &s == state_root && b == block_num as u32 + Some((s, b)) if &s.state_root == state_root && b == block_num as u32 ); } } diff --git a/polkadot/runtime/parachains/src/ump_tests.rs b/polkadot/runtime/parachains/src/ump_tests.rs index 91571859ecf..cd7951ac9aa 100644 --- a/polkadot/runtime/parachains/src/ump_tests.rs +++ b/polkadot/runtime/parachains/src/ump_tests.rs @@ -31,7 +31,10 @@ use frame_support::{ traits::{EnqueueMessage, ExecuteOverweightError, ServiceQueues}, weights::Weight, }; -use polkadot_primitives::{well_known_keys, Id as ParaId, UpwardMessage}; +use polkadot_primitives::{ + vstaging::{ClaimQueueOffset, CoreSelector, UMPSignal, UMP_SEPARATOR}, + well_known_keys, Id as ParaId, UpwardMessage, +}; use sp_crypto_hashing::{blake2_256, twox_64}; use sp_runtime::traits::Bounded; @@ -141,12 +144,12 @@ mod check_upward_messages { configuration::ActiveConfig::::get().max_upward_message_num_per_candidate; for sent in 0..permitted + 1 { - check(P_0, vec![msg(""); sent as usize], None); + check(P_0, vec![msg("a"); sent as usize], None); } for sent in permitted + 1..permitted + 10 { check( P_0, - vec![msg(""); sent as usize], + vec![msg("a"); sent as usize], Some(UmpAcceptanceCheckErr::MoreMessagesThanPermitted { sent, permitted }), ); } @@ -161,7 +164,7 @@ mod check_upward_messages { let max_per_candidate = configuration::ActiveConfig::::get().max_upward_message_num_per_candidate; - for msg_size in 0..=max_size { + for msg_size in 1..=max_size { check(P_0, vec![vec![0; msg_size as usize]], None); } for msg_size in max_size + 1..max_size + 10 { @@ -185,18 +188,18 @@ mod check_upward_messages { let limit = configuration::ActiveConfig::::get().max_upward_queue_count as u64; for _ in 0..limit { - check(P_0, vec![msg("")], None); - queue(P_0, vec![msg("")]); + check(P_0, vec![msg("a")], None); + queue(P_0, vec![msg("a")]); } check( P_0, - vec![msg("")], + vec![msg("a")], Some(UmpAcceptanceCheckErr::CapacityExceeded { count: limit + 1, limit }), ); check( P_0, - vec![msg(""); 2], + vec![msg("a"); 2], Some(UmpAcceptanceCheckErr::CapacityExceeded { count: limit + 2, limit }), ); }); @@ -642,6 +645,42 @@ fn cannot_offboard_while_ump_dispatch_queued() { }); } +/// Test UMP signals are filtered out and don't consume `max_upward_message_num_per_candidate`. +#[test] +fn enqueue_ump_signals() { + let para = 100.into(); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + register_parachain(para); + run_to_block(5, vec![4, 5]); + + let config = configuration::ActiveConfig::::get(); + let mut messages = (0..config.max_upward_message_num_per_candidate) + .into_iter() + .map(|_| "msg".encode()) + .collect::>(); + let expected_messages = messages.iter().cloned().map(|msg| (para, msg)).collect::>(); + + // `UMPSignals` and separator do not count as XCM messages. The below check must pass. + messages.append(&mut vec![ + UMP_SEPARATOR, + UMPSignal::SelectCore(CoreSelector(0), ClaimQueueOffset(0)).encode(), + ]); + + ParaInclusion::check_upward_messages( + &configuration::ActiveConfig::::get(), + para, + &messages, + ) + .unwrap(); + + // We expect that all messages except UMP signal and separator are processed + ParaInclusion::receive_upward_messages(para, &messages); + MessageQueue::service_queues(Weight::max_value()); + assert_eq!(Processed::take(), expected_messages); + }); +} + /// A para-chain cannot send an UMP to the relay chain while it is offboarding. #[test] fn cannot_enqueue_ump_while_offboarding() { diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index e1826e4100b..b333a9f141f 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1677,6 +1677,7 @@ pub mod migrations { // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, parachains_inclusion::migration::MigrateToV1, + parachains_shared::migration::MigrateToV1, ); } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 7324485256a..a43b85504dc 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1784,6 +1784,7 @@ pub mod migrations { Runtime, MaxAgentsToMigrate, >, + parachains_shared::migration::MigrateToV1, ); } diff --git a/prdoc/pr_5423.prdoc b/prdoc/pr_5423.prdoc new file mode 100644 index 00000000000..dbd685d73dc --- /dev/null +++ b/prdoc/pr_5423.prdoc @@ -0,0 +1,20 @@ +title: Runtime support for candidate receipt v2 (RFC103) + +doc: + - audience: [Runtime Dev, Node Dev] + description: | + Implementation of [RFC103](https://github.com/polkadot-fellows/RFCs/pull/103) in the relay chain runtime. + The runtime will accept and validate the new receipts only if the `FeatureIndex::CandidateReceiptV2` + feature bit is enabled. + +crates: + - name: polkadot-primitives + bump: major + - name: polkadot-runtime-parachains + bump: patch + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: polkadot + bump: patch -- GitLab From c0ddfbae0cca0314c9a50f688cb5a3fcdd71015c Mon Sep 17 00:00:00 2001 From: Juan Ignacio Rios <54085674+JuaniRios@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:08:28 +0200 Subject: [PATCH 056/127] Generic slashing side-effects (#5623) # Description ## What? Make it possible for other pallets to implement their own logic when a slash on a balance occurs. ## Why? In the [introduction of holds](https://github.com/paritytech/substrate/pull/12951) @gavofyork said: > Since Holds are designed to be infallibly slashed, this means that any logic using a Freeze must handle the possibility of the frozen amount being reduced, potentially to zero. A permissionless function should be provided in order to allow bookkeeping to be updated in this instance. At Polimec we needed to find a way to reduce the vesting schedules of our users after a slash was made, and after talking to @kianenigma at the Web3Summit, we realized there was no easy way to implement this with the current traits, so we came up with this solution. ## How? - First we abstract the `done_slash` function of holds::Balanced to it's own trait that any pallet can implement. - Then we add a config type in pallet-balances that accepts a callback tuple of all the pallets that implement this trait. - Finally implement done_slash for pallet-balances such that it calls the config type. ## Integration The default implementation of done_slash is still an empty function, and the new config type of pallet-balances can be set to an empty tuple, so nothing changes by default. ## Review Notes - I suggest to focus on the first commit which contains the main logic changes. - I also have a working implementation of done_slash for pallet_vesting, should I add it to this PR? - If I run `cargo +nightly fmt --all` then I get changes to a lot of unrelated crates, so not sure if I should run it to avoid the fmt failure of the CI - Should I hunt down references to fungible/fungibles documentation and update it accordingly? **Polkadot address:** `15fj1UhQp8Xes7y7LSmDYTy349mXvUwrbNmLaP5tQKBxsQY1` # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [x] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: command-bot <> Co-authored-by: Francisco Aguirre --- .../assets/asset-hub-rococo/src/lib.rs | 1 + .../assets/asset-hub-westend/src/lib.rs | 1 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 1 + .../collectives-westend/src/lib.rs | 1 + .../contracts/contracts-rococo/src/lib.rs | 1 + .../coretime/coretime-rococo/src/lib.rs | 1 + .../coretime/coretime-westend/src/lib.rs | 1 + .../runtimes/people/people-rococo/src/lib.rs | 1 + .../runtimes/people/people-westend/src/lib.rs | 1 + .../runtimes/testing/penpal/src/lib.rs | 1 + .../testing/rococo-parachain/src/lib.rs | 1 + cumulus/test/runtime/src/lib.rs | 1 + polkadot/runtime/rococo/src/lib.rs | 2 + polkadot/runtime/test-runtime/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 1 + .../xcm/xcm-builder/src/tests/pay/mock.rs | 1 + prdoc/pr_5623.prdoc | 89 +++++++++++++++++++ substrate/bin/node/runtime/src/lib.rs | 1 + substrate/frame/assets-freezer/src/mock.rs | 1 + substrate/frame/balances/src/impl_fungible.rs | 8 ++ substrate/frame/balances/src/lib.rs | 9 ++ substrate/frame/broker/src/test_fungibles.rs | 14 +++ .../contracts/mock-network/src/parachain.rs | 1 + .../contracts/mock-network/src/relay_chain.rs | 1 + substrate/frame/nis/src/mock.rs | 2 + .../revive/mock-network/src/parachain.rs | 1 + .../revive/mock-network/src/relay_chain.rs | 1 + .../src/traits/tokens/fungible/hold.rs | 18 +++- .../src/traits/tokens/fungible/item_of.rs | 17 ++++ .../src/traits/tokens/fungible/union_of.rs | 29 +++++- .../src/traits/tokens/fungibles/hold.rs | 24 +++-- .../src/traits/tokens/fungibles/union_of.rs | 31 ++++++- substrate/test-utils/runtime/src/lib.rs | 1 + .../parachain/runtime/src/configs/mod.rs | 1 + .../solochain/runtime/src/configs/mod.rs | 1 + 36 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_5623.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 8163a445220..367cf8a51f2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -213,6 +213,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { 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 75aea0eb2c2..bb77f576e20 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -213,6 +213,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { 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 13c1ab6b625..e3435f35f17 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 @@ -342,6 +342,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { 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 03b69a21802..b73d8e5c67f 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 @@ -327,6 +327,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index a60d3ed66ac..7f87d4701f9 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -223,6 +223,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 50316a9a1d0..3377802e91d 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -234,6 +234,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 74569af9ec7..f16dae04f21 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -249,6 +249,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 5e37faf6181..e909b804587 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -249,6 +249,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index bfd31e2b484..aa04d01cf03 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -225,6 +225,7 @@ impl pallet_balances::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 8258ba0564f..c8131c94f15 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -224,6 +224,7 @@ impl pallet_balances::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index a98c7b4886f..05773846164 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -420,6 +420,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 4d39728df83..5abdc995c00 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -257,6 +257,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } impl pallet_transaction_payment::Config for Runtime { diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 861d55c77cd..92a6ab73a2e 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -269,6 +269,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } impl pallet_transaction_payment::Config for Runtime { diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index b333a9f141f..ab6563525ff 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -400,6 +400,7 @@ impl pallet_balances::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; type MaxFreezes = ConstU32<1>; + type DoneSlashHandler = (); } parameter_types! { @@ -1234,6 +1235,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<1>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 30ef2de3165..6ba8e6ad318 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -234,6 +234,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index a43b85504dc..fe1777bc94e 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -399,6 +399,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = VariantCountOf; + type DoneSlashHandler = (); } parameter_types! { diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 18bde3aab48..9f2146fa30e 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -77,6 +77,7 @@ impl pallet_balances::Config for Test { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } parameter_types! { diff --git a/prdoc/pr_5623.prdoc b/prdoc/pr_5623.prdoc new file mode 100644 index 00000000000..c0701e0e1b5 --- /dev/null +++ b/prdoc/pr_5623.prdoc @@ -0,0 +1,89 @@ +# 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: Generic slashing side-effects + +doc: + - audience: Runtime Dev + description: | + What? + Make it possible for other pallets to implement their own logic when a slash on a balance occurs. + + How? + First we abstract the done_slash function of holds::Balanced to it's own trait that any pallet can implement. + Then we add a config type in pallet-balances that accepts a callback tuple of all the pallets that implement this trait. + Finally implement done_slash for pallet-balances such that it calls the config type. + Integration + The default implementation of done_slash is still an empty function, and the new config type of pallet-balances can be set to an empty tuple, so nothing changes by default. + +crates: + - name: frame-support + bump: major + + - name: pallet-balances + bump: major + + - name: pallet-broker + bump: minor + + - name: rococo-runtime + bump: minor + + - name: pallet-nis + bump: minor + + - name: westend-runtime + bump: minor + + - name: pallet-assets-freezer + bump: minor + + - name: pallet-contracts-mock-network + bump: minor + + - name: pallet-revive-mock-network + bump: minor + + - name: asset-hub-rococo-runtime + bump: minor + + - name: asset-hub-westend-runtime + bump: minor + + - name: bridge-hub-rococo-runtime + bump: minor + + - name: bridge-hub-westend-runtime + bump: minor + + - name: collectives-westend-runtime + bump: minor + + - name: coretime-rococo-runtime + bump: minor + + - name: coretime-westend-runtime + bump: minor + + - name: people-rococo-runtime + bump: minor + + - name: people-westend-runtime + bump: minor + + - name: penpal-runtime + bump: minor + + - name: contracts-rococo-runtime + bump: minor + + - name: rococo-parachain-runtime + bump: minor + + - name: staging-xcm-builder + bump: minor + + - name: polkadot-sdk + bump: minor + + diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 784d74973c3..c81921f844b 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -546,6 +546,7 @@ impl pallet_balances::Config for Runtime { type WeightInfo = pallet_balances::weights::SubstrateWeight; type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = VariantCountOf; + type DoneSlashHandler = (); } parameter_types! { diff --git a/substrate/frame/assets-freezer/src/mock.rs b/substrate/frame/assets-freezer/src/mock.rs index 5e04dfe8e2b..bc903a018f7 100644 --- a/substrate/frame/assets-freezer/src/mock.rs +++ b/substrate/frame/assets-freezer/src/mock.rs @@ -87,6 +87,7 @@ impl pallet_balances::Config for Test { type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); + type DoneSlashHandler = (); } impl pallet_assets::Config for Test { diff --git a/substrate/frame/balances/src/impl_fungible.rs b/substrate/frame/balances/src/impl_fungible.rs index 0f4e51f3501..cf95a97d191 100644 --- a/substrate/frame/balances/src/impl_fungible.rs +++ b/substrate/frame/balances/src/impl_fungible.rs @@ -363,6 +363,14 @@ impl, I: 'static> fungible::Balanced for Pallet impl, I: 'static> fungible::BalancedHold for Pallet {} +impl, I: 'static> + fungible::hold::DoneSlash for Pallet +{ + fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) { + T::DoneSlashHandler::done_slash(reason, who, amount); + } +} + impl, I: 'static> AccountTouch<(), T::AccountId> for Pallet { type Balance = T::Balance; fn deposit_required(_: ()) -> Self::Balance { diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index f6858347f24..65e594a904f 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -234,6 +234,7 @@ pub mod pallet { type MaxFreezes = VariantCountOf; type WeightInfo = (); + type DoneSlashHandler = (); } } @@ -312,6 +313,14 @@ pub mod pallet { /// The maximum number of individual freeze locks that can exist on an account at any time. #[pallet::constant] type MaxFreezes: Get; + + /// Allows callbacks to other pallets so they can update their bookkeeping when a slash + /// occurs. + type DoneSlashHandler: fungible::hold::DoneSlash< + Self::RuntimeHoldReason, + Self::AccountId, + Self::Balance, + >; } /// The in-code storage version. diff --git a/substrate/frame/broker/src/test_fungibles.rs b/substrate/frame/broker/src/test_fungibles.rs index b0a06fc1a32..1015e1fac25 100644 --- a/substrate/frame/broker/src/test_fungibles.rs +++ b/substrate/frame/broker/src/test_fungibles.rs @@ -269,6 +269,20 @@ where { } +impl< + Instance: Get, + AccountId: Encode, + AssetId: tokens::AssetId + Copy, + MinimumBalance: TypedGet, + HoldReason: Encode + Decode + TypeInfo + 'static, + Balance: tokens::Balance, + > fungibles::hold::DoneSlash + for TestFungibles +where + MinimumBalance::Type: tokens::Balance, +{ +} + impl< Instance: Get, AccountId: Encode, diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 5a06cc6748b..6edbfb0e7e8 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -94,6 +94,7 @@ impl pallet_balances::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; type WeightInfo = (); + type DoneSlashHandler = (); } parameter_types! { diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index 705578cde1d..5fed061f80b 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -89,6 +89,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = ConstU32<0>; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); } impl shared::Config for Runtime { diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index f3320a306df..2b008f8ec2a 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -64,6 +64,7 @@ impl pallet_balances::Config for Test { type MaxFreezes = (); type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); } impl pallet_balances::Config for Test { @@ -84,6 +85,7 @@ impl pallet_balances::Config for Test { type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); + type DoneSlashHandler = (); } parameter_types! { diff --git a/substrate/frame/revive/mock-network/src/parachain.rs b/substrate/frame/revive/mock-network/src/parachain.rs index 0fd2248db57..26a8fdcada2 100644 --- a/substrate/frame/revive/mock-network/src/parachain.rs +++ b/substrate/frame/revive/mock-network/src/parachain.rs @@ -94,6 +94,7 @@ impl pallet_balances::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; type WeightInfo = (); + type DoneSlashHandler = (); } parameter_types! { diff --git a/substrate/frame/revive/mock-network/src/relay_chain.rs b/substrate/frame/revive/mock-network/src/relay_chain.rs index 705578cde1d..5fed061f80b 100644 --- a/substrate/frame/revive/mock-network/src/relay_chain.rs +++ b/substrate/frame/revive/mock-network/src/relay_chain.rs @@ -89,6 +89,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = ConstU32<0>; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); } impl shared::Config for Runtime { diff --git a/substrate/frame/support/src/traits/tokens/fungible/hold.rs b/substrate/frame/support/src/traits/tokens/fungible/hold.rs index 28ece25c91d..6737cfe707a 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/hold.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/hold.rs @@ -430,7 +430,11 @@ pub trait Mutate: } /// Trait for slashing a fungible asset which can be place on hold. -pub trait Balanced: super::Balanced + Unbalanced { +pub trait Balanced: + super::Balanced + + Unbalanced + + DoneSlash +{ /// Reduce the balance of some funds on hold in an account. /// /// The resulting imbalance is the first item of the tuple returned. @@ -449,6 +453,16 @@ pub trait Balanced: super::Balanced + Unbalanced { + fn done_slash(_reason: &Reason, _who: &AccountId, _amount: Balance) {} +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl DoneSlash for Tuple { + fn done_slash(reason: &Reason, who: &AccountId, amount: Balance) { + for_tuples!( #( Tuple::done_slash(reason, who, amount); )* ); + } } diff --git a/substrate/frame/support/src/traits/tokens/fungible/item_of.rs b/substrate/frame/support/src/traits/tokens/fungible/item_of.rs index c9f366911a8..309288d8278 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/item_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/item_of.rs @@ -30,6 +30,7 @@ use crate::traits::{ WithdrawConsequence, }, }; +use frame_support::traits::fungible::hold::DoneSlash; use sp_core::Get; use sp_runtime::{DispatchError, DispatchResult}; @@ -467,5 +468,21 @@ impl< } } +impl< + F: fungibles::BalancedHold, + A: Get<>::AssetId>, + AccountId, + > DoneSlash for ItemOf +{ + fn done_slash(reason: &F::Reason, who: &AccountId, amount: F::Balance) { + >::done_slash( + A::get(), + reason, + who, + amount, + ) + } +} + #[test] fn test() {} diff --git a/substrate/frame/support/src/traits/tokens/fungible/union_of.rs b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs index 3adbbdda314..5cb1d0a9e7b 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/union_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs @@ -844,8 +844,10 @@ impl< } impl< - Left: fungible::BalancedHold, - Right: fungibles::BalancedHold, + Left: fungible::BalancedHold + + fungible::hold::DoneSlash, + Right: fungibles::BalancedHold + + fungibles::hold::DoneSlash, Criterion: Convert>, AssetKind: AssetId, AccountId, @@ -871,6 +873,29 @@ impl< } } } +impl< + Reason, + Balance, + Left: fungible::hold::DoneSlash, + Right: fungibles::hold::DoneSlash + + fungibles::Inspect, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::hold::DoneSlash + for UnionOf +{ + fn done_slash(asset: AssetKind, reason: &Reason, who: &AccountId, amount: Balance) { + match Criterion::convert(asset.clone()) { + Left(()) => { + Left::done_slash(reason, who, amount); + }, + Right(a) => { + Right::done_slash(a, reason, who, amount); + }, + } + } +} impl< Left: fungible::Inspect, diff --git a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs index ef3fef7a300..026bfc872e0 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs @@ -214,7 +214,11 @@ pub trait Unbalanced: Inspect { } /// Trait for slashing a fungible asset which can be place on hold. -pub trait Balanced: super::Balanced + Unbalanced { +pub trait Balanced: + super::Balanced + + Unbalanced + + DoneSlash +{ /// Reduce the balance of some funds on hold in an account. /// /// The resulting imbalance is the first item of the tuple returned. @@ -238,13 +242,19 @@ pub trait Balanced: super::Balanced + Unbalanced { + fn done_slash(_asset: AssetId, _reason: &Reason, _who: &AccountId, _amount: Balance) {} +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl DoneSlash + for Tuple +{ + fn done_slash(asset_id: AssetId, reason: &Reason, who: &AccountId, amount: Balance) { + for_tuples!( #( Tuple::done_slash(asset_id, reason, who, amount); )* ); } } diff --git a/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs index 77047150e00..ec066dddcfa 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs @@ -825,8 +825,10 @@ impl< } impl< - Left: fungibles::BalancedHold, - Right: fungibles::BalancedHold, + Left: fungibles::BalancedHold + + fungibles::hold::DoneSlash, + Right: fungibles::BalancedHold + + fungibles::hold::DoneSlash, Criterion: Convert>, AssetKind: AssetId, AccountId, @@ -853,6 +855,31 @@ impl< } } +impl< + Reason, + Balance, + Left: fungibles::Inspect + + fungibles::hold::DoneSlash, + Right: fungibles::Inspect + + fungibles::hold::DoneSlash, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::hold::DoneSlash + for UnionOf +{ + fn done_slash(asset: AssetKind, reason: &Reason, who: &AccountId, amount: Balance) { + match Criterion::convert(asset.clone()) { + Left(a) => { + Left::done_slash(a, reason, who, amount); + }, + Right(a) => { + Right::done_slash(a, reason, who, amount); + }, + } + } +} + impl< Left: fungibles::Inspect + fungibles::Create, Right: fungibles::Inspect + fungibles::Create, diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 840081003b8..74965264c03 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -391,6 +391,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = (); type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); } impl substrate_test_pallet::Config for Runtime {} diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index 45618733712..43ab63bd0b6 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -158,6 +158,7 @@ impl pallet_balances::Config for Runtime { type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = VariantCountOf; + type DoneSlashHandler = (); } parameter_types! { diff --git a/templates/solochain/runtime/src/configs/mod.rs b/templates/solochain/runtime/src/configs/mod.rs index a5c12fbd79a..4f77ad37fe6 100644 --- a/templates/solochain/runtime/src/configs/mod.rs +++ b/templates/solochain/runtime/src/configs/mod.rs @@ -134,6 +134,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = VariantCountOf; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeHoldReason; + type DoneSlashHandler = (); } parameter_types! { -- GitLab From df8b870032794b06440bcb8855bdb4c289c7f5da Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Mon, 7 Oct 2024 11:25:59 +0300 Subject: [PATCH 057/127] parachain-system: send core selector ump signal (#5888) Runtime side of https://github.com/paritytech/polkadot-sdk/issues/5048 Send the core selector ump signal in cumulus. Guarded by a compile time feature until nodes are upgraded to a version that includes https://github.com/paritytech/polkadot-sdk/pull/5423 for gracefully handling ump signals. --------- Co-authored-by: GitHub Action --- .../consensus/aura/src/collators/lookahead.rs | 7 +-- cumulus/pallets/parachain-system/Cargo.toml | 2 + cumulus/pallets/parachain-system/src/lib.rs | 53 +++++++++++++++++-- cumulus/primitives/core/src/lib.rs | 2 +- prdoc/pr_5888.prdoc | 16 ++++++ 5 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 prdoc/pr_5888.prdoc diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 322baaa0149..8ac43fbd116 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -36,7 +36,9 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::{ClaimQueueOffset, CollectCollationInfo, PersistedValidationData}; +use cumulus_primitives_core::{ + ClaimQueueOffset, CollectCollationInfo, PersistedValidationData, DEFAULT_CLAIM_QUEUE_OFFSET, +}; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::{PoV, SubmitCollationParams}; @@ -260,8 +262,7 @@ where relay_parent, params.para_id, &mut params.relay_client, - // Use depth 0, to preserve behaviour. - ClaimQueueOffset(0), + ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET), ) .await .get(0) diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 66429625d5b..3cb0394c4b9 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -121,3 +121,5 @@ try-runtime = [ "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] + +experimental-ump-signals = [] diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 21af35fe3de..a4b505b98e5 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -193,20 +193,46 @@ pub mod ump_constants { /// Trait for selecting the next core to build the candidate for. pub trait SelectCore { - fn select_core_for_child() -> (CoreSelector, ClaimQueueOffset); + /// Core selector information for the current block. + fn selected_core() -> (CoreSelector, ClaimQueueOffset); + /// Core selector information for the next block. + fn select_next_core() -> (CoreSelector, ClaimQueueOffset); } /// The default core selection policy. pub struct DefaultCoreSelector(PhantomData); impl SelectCore for DefaultCoreSelector { - fn select_core_for_child() -> (CoreSelector, ClaimQueueOffset) { + fn selected_core() -> (CoreSelector, ClaimQueueOffset) { + let core_selector: U256 = frame_system::Pallet::::block_number().into(); + + (CoreSelector(core_selector.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)) + } + + fn select_next_core() -> (CoreSelector, ClaimQueueOffset) { let core_selector: U256 = (frame_system::Pallet::::block_number() + One::one()).into(); (CoreSelector(core_selector.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)) } } +/// Core selection policy that builds on claim queue offset 1. +pub struct LookaheadCoreSelector(PhantomData); + +impl SelectCore for LookaheadCoreSelector { + fn selected_core() -> (CoreSelector, ClaimQueueOffset) { + let core_selector: U256 = frame_system::Pallet::::block_number().into(); + + (CoreSelector(core_selector.byte(0)), ClaimQueueOffset(1)) + } + + fn select_next_core() -> (CoreSelector, ClaimQueueOffset) { + let core_selector: U256 = (frame_system::Pallet::::block_number() + One::one()).into(); + + (CoreSelector(core_selector.byte(0)), ClaimQueueOffset(1)) + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -365,6 +391,11 @@ pub mod pallet { UpwardMessages::::put(&up[..num as usize]); *up = up.split_off(num as usize); + // Send the core selector UMP signal. This is experimental until relay chain + // validators are upgraded to handle ump signals. + #[cfg(feature = "experimental-ump-signals")] + Self::send_ump_signal(); + // If the total size of the pending messages is less than the threshold, // we decrease the fee factor, since the queue is less congested. // This makes delivery of new messages cheaper. @@ -1397,9 +1428,9 @@ impl Pallet { } } - /// Returns the core selector. + /// Returns the core selector for the next block. pub fn core_selector() -> (CoreSelector, ClaimQueueOffset) { - T::SelectCore::select_core_for_child() + T::SelectCore::select_next_core() } /// Set a custom head data that should be returned as result of `validate_block`. @@ -1418,6 +1449,20 @@ impl Pallet { CustomValidationHeadData::::put(head_data); } + /// Send the ump signals + #[cfg(feature = "experimental-ump-signals")] + fn send_ump_signal() { + use cumulus_primitives_core::relay_chain::vstaging::{UMPSignal, UMP_SEPARATOR}; + + UpwardMessages::::mutate(|up| { + up.push(UMP_SEPARATOR); + + // Send the core selector signal. + let core_selector = T::SelectCore::selected_core(); + up.push(UMPSignal::SelectCore(core_selector.0, core_selector.1).encode()); + }); + } + /// Open HRMP channel for using it in benchmarks or tests. /// /// The caller assumes that the pallet will accept regular outbound message to the sibling diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index 6bad65b3ff2..dfb574ef330 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -335,7 +335,7 @@ pub mod rpsr_digest { /// The default claim queue offset to be used if it's not configured/accessible in the parachain /// runtime -pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 1; +pub const DEFAULT_CLAIM_QUEUE_OFFSET: u8 = 0; /// Information about a collation. /// diff --git a/prdoc/pr_5888.prdoc b/prdoc/pr_5888.prdoc new file mode 100644 index 00000000000..9552eada691 --- /dev/null +++ b/prdoc/pr_5888.prdoc @@ -0,0 +1,16 @@ +title: 'parachain-system: send core selector ump signal' + +doc: + - audience: Runtime Dev + description: | + Send the core selector ump signal in cumulus. Guarded by a compile time feature called `experimental-ump-signals` + until nodes are upgraded to a version that includes https://github.com/paritytech/polkadot-sdk/pull/5423 for + gracefully handling ump signals. + +crates: + - name: cumulus-client-consensus-aura + bump: minor + - name: cumulus-pallet-parachain-system + bump: major + - name: cumulus-primitives-core + bump: minor -- GitLab From e354d6680d98993481fe9b2583619c7e7d7f04ad Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 7 Oct 2024 10:30:13 +0200 Subject: [PATCH 058/127] Update docker image for bridges substrate-relay (#5947) This PR updates the substrate-relay version for the bridges' Zombienet tests. --- docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index 2198da13a4b..b1f4bffc772 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -1,7 +1,7 @@ # 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:v1.6.10 +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v1.7.0 # metadata ARG VCS_REF -- GitLab From ee0c46068199a9c0726b45a6fa1edbdf65f9f784 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:46:00 +0200 Subject: [PATCH 059/127] [ci] Use parity common runners in all workflows (#5949) cc https://github.com/paritytech/ci_cd/issues/1039 --- .github/workflows/build-publish-images.yml | 48 ++++++++++----------- .github/workflows/check-getting-started.yml | 18 ++++---- .github/workflows/tests-misc.yml | 1 - docs/contributor/weight-generation.md | 2 +- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build-publish-images.yml b/.github/workflows/build-publish-images.yml index 3a9f6076186..874b5d37469 100644 --- a/.github/workflows/build-publish-images.yml +++ b/.github/workflows/build-publish-images.yml @@ -15,7 +15,6 @@ env: COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }} jobs: - # # # @@ -24,7 +23,7 @@ jobs: if: contains(github.event.label.name, 'GHA-migration') || contains(github.event.pull_request.labels.*.name, 'GHA-migration') uses: ./.github/workflows/reusable-preflight.yml -### Build ######################## + ### Build ######################## # # @@ -240,7 +239,7 @@ jobs: with: # 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 - ref: ${{ github.head_ref || github.ref_name }} + ref: ${{ github.head_ref || github.ref_name }} - name: build run: | mkdir -p ./artifacts/substrate/ @@ -299,14 +298,14 @@ jobs: path: artifacts.tar retention-days: 1 -### Publish ######################## + ### Publish ######################## # # # build-push-image-test-parachain: needs: [preflight, build-test-parachain] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -330,7 +329,7 @@ jobs: # build-push-image-polkadot-debug: needs: [preflight, build-linux-stable] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -349,13 +348,12 @@ jobs: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/polkadot-debug" dockerfile: "docker/dockerfiles/polkadot/polkadot_injected_debug.Dockerfile" - # # # build-push-image-colander: needs: [preflight, build-test-collators] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -374,13 +372,12 @@ jobs: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/colander" dockerfile: "docker/dockerfiles/collator_injected.Dockerfile" - # # # build-push-image-malus: needs: [preflight, build-malus] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -399,13 +396,12 @@ jobs: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/malus" dockerfile: "docker/dockerfiles/malus_injected.Dockerfile" - # # # build-push-image-substrate-pr: needs: [preflight, build-linux-substrate] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -424,15 +420,20 @@ jobs: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/substrate" dockerfile: "docker/dockerfiles/substrate_injected.Dockerfile" - # # # # 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: - needs: [preflight, build-linux-stable, build-linux-stable-cumulus, prepare-bridges-zombienet-artifacts] - runs-on: arc-runners-polkadot-sdk + needs: + [ + preflight, + build-linux-stable, + build-linux-stable-cumulus, + prepare-bridges-zombienet-artifacts, + ] + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -443,24 +444,24 @@ jobs: name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | - tar -xvf artifacts.tar - rm artifacts.tar + tar -xvf artifacts.tar + rm artifacts.tar - uses: actions/download-artifact@v4.1.8 with: name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | - tar -xvf artifacts.tar - rm artifacts.tar + tar -xvf artifacts.tar + rm artifacts.tar - uses: actions/download-artifact@v4.1.8 with: name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_NAME }} - name: tar run: | - tar -xvf artifacts.tar - rm artifacts.tar + tar -xvf artifacts.tar + rm artifacts.tar - name: build and push image uses: ./.github/actions/build-push-image @@ -468,13 +469,12 @@ jobs: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/bridges-zombienet-tests" dockerfile: "docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" - # # # build-push-image-polkadot-parachain-debug: needs: [preflight, build-linux-stable-cumulus] - runs-on: arc-runners-polkadot-sdk + runs-on: ${{ needs.preflight.outputs.RUNNER_DEFAULT }} timeout-minutes: 60 steps: - name: Checkout @@ -491,4 +491,4 @@ jobs: uses: ./.github/actions/build-push-image with: image-name: "europe-docker.pkg.dev/parity-ci-2024/temp-images/polkadot-parachain-debug" - dockerfile: "docker/dockerfiles/polkadot-parachain/polkadot-parachain-debug_unsigned_injected.Dockerfile" \ No newline at end of file + dockerfile: "docker/dockerfiles/polkadot-parachain/polkadot-parachain-debug_unsigned_injected.Dockerfile" diff --git a/.github/workflows/check-getting-started.yml b/.github/workflows/check-getting-started.yml index b43db33c63b..0661fa14434 100644 --- a/.github/workflows/check-getting-started.yml +++ b/.github/workflows/check-getting-started.yml @@ -6,7 +6,7 @@ name: Check the getting-started.sh script # # There are two jobs inside. # One for systems that can run in a docker container, and one for macOS. -# +# # Each job consists of: # 1. Some necessary prerequisites for the workflow itself. # 2. A first pass of the script, which will install dependencies and clone a template. @@ -24,10 +24,10 @@ name: Check the getting-started.sh script on: pull_request: paths: - - '.github/workflows/check-getting-started.yml' - - 'scripts/getting-started.sh' + - ".github/workflows/check-getting-started.yml" + - "scripts/getting-started.sh" schedule: - - cron: '0 5 * * *' + - cron: "0 5 * * *" workflow_dispatch: concurrency: @@ -60,7 +60,7 @@ jobs: container: opensuse/tumbleweed template: solochain shell: sh - runs-on: arc-runners-polkadot-sdk-beefy + runs-on: parity-large container: ${{ matrix.container }}:latest steps: # A minimal amount of prerequisites required before we can run the actual getting-started script, @@ -116,7 +116,7 @@ jobs: expect "start with one of the templates" { send "y\r" } - + expect -re "(.)\\) ${{ matrix.template }} template" { send "$expect_out(1,string)\r" } @@ -150,7 +150,7 @@ jobs: expect "start with one of the templates" { send "y\r" } - + expect -re "(.)\\) ${{ matrix.template }} template" { send "$expect_out(1,string)\r" expect "directory already exists" {} @@ -227,7 +227,7 @@ jobs: expect "start with one of the templates" { send "y\r" } - + expect -re "(.)\\) ${{ matrix.template }} template" { send "$expect_out(1,string)\r" } @@ -267,7 +267,7 @@ jobs: expect "start with one of the templates" { send "y\r" } - + expect -re "(.)\\) ${{ matrix.template }} template" { send "$expect_out(1,string)\r" expect "directory already exists" {} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 45ed4ebdc6c..4355cd0a9f8 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -157,7 +157,6 @@ jobs: node-bench-regression-guard: timeout-minutes: 20 if: always() && !cancelled() - # runs-on: arc-runners-polkadot-sdk runs-on: ubuntu-latest needs: [preflight, cargo-check-benches] steps: diff --git a/docs/contributor/weight-generation.md b/docs/contributor/weight-generation.md index ebfdca59cae..77a570c9453 100644 --- a/docs/contributor/weight-generation.md +++ b/docs/contributor/weight-generation.md @@ -3,7 +3,7 @@ To generate weights for a runtime. Weights generation is using self-hosted runner which is provided by Parity CI, the rest commands are using standard GitHub runners on `ubuntu-latest` or `ubuntu-20.04`. -Self-hosted runner for benchmarks (arc-runners-Polkadot-sdk-benchmark) is configured to meet requirements of reference +Self-hosted runner for benchmarks (`parity-weights`) is configured to meet requirements of reference hardware for running validators https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#reference-hardware -- GitLab From d66a5a41086718af03a272fd62b779eb8b81a74f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:46:56 +0300 Subject: [PATCH 060/127] chainHead: Track reported blocks to capture notification gaps (#5856) There are cases during warp sync or re-orgs, where we receive a notification with a block parent that was not reported in the past. This PR extends the tracking state to catch those cases and report a `Stop` event to the user. This PR adds a new state to the RPC-v2 chainHead to track which blocks have been reported. In the past we relied on the pinning mechanism to provide us details if a block is pinned or not. However, the pinning state keeps the minimal information around for pinning. Therefore, unpinning a block will cause the state to disappear. Closes: https://github.com/paritytech/polkadot-sdk/issues/5761 --------- Signed-off-by: Alexandru Vasile Co-authored-by: Sebastian Kunert --- prdoc/pr_5856.prdoc | 17 ++ .../src/chain_head/chain_head_follow.rs | 151 +++++++++++++++--- .../rpc-spec-v2/src/chain_head/tests.rs | 68 ++++++++ 3 files changed, 218 insertions(+), 18 deletions(-) create mode 100644 prdoc/pr_5856.prdoc diff --git a/prdoc/pr_5856.prdoc b/prdoc/pr_5856.prdoc new file mode 100644 index 00000000000..383e95e3da8 --- /dev/null +++ b/prdoc/pr_5856.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: Extend state tracking of chainHead to capture notification gaps + +doc: + - audience: Node Dev + description: | + This PR extends the state tracking of the RPC-v2 chainHead methods. + ChainHead tracks the reported blocks to detect notification gaps. + This state tracking ensures we can detect `NewBlock` events for + which we did not report previously the parent hash. + +crates: + - name: sc-rpc-spec-v2 + bump: minor + diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs index ebb72ed3d15..f2326f01567 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs @@ -24,7 +24,7 @@ use crate::chain_head::{ BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, RuntimeEvent, RuntimeVersionEvent, }, - subscription::{SubscriptionManagement, SubscriptionManagementError}, + subscription::{InsertedSubscriptionData, SubscriptionManagement, SubscriptionManagementError}, }; use futures::{ channel::oneshot, @@ -53,8 +53,6 @@ use std::{ /// `Initialized` event. const MAX_FINALIZED_BLOCKS: usize = 16; -use super::subscription::InsertedSubscriptionData; - /// Generates the events of the `chainHead_follow` method. pub struct ChainHeadFollower, Block: BlockT, Client> { /// Substrate client. @@ -71,11 +69,76 @@ pub struct ChainHeadFollower, Block: BlockT, Client> { current_best_block: Option, /// LRU cache of pruned blocks. pruned_blocks: LruMap, + /// LRU cache of announced blocks. + announced_blocks: AnnouncedBlocks, /// Stop all subscriptions if the distance between the leaves and the current finalized /// block is larger than this value. max_lagging_distance: usize, } +struct AnnouncedBlocks { + /// Unfinalized blocks. + blocks: LruMap, + /// Finalized blocks. + finalized: MostRecentFinalizedBlocks, +} + +/// Wrapper over LRU to efficiently lookup hashes and remove elements as FIFO queue. +/// +/// For the finalized blocks we use `peek` to avoid moving the block counter to the front. +/// This effectively means that the LRU acts as a FIFO queue. Otherwise, we might +/// end up with scenarios where the "finalized block" in the end of LRU is overwritten which +/// may not necessarily be the oldest finalized block i.e, possible that "get" promotes an +/// older finalized block because it was accessed more recently. +struct MostRecentFinalizedBlocks(LruMap); + +impl MostRecentFinalizedBlocks { + /// Insert the finalized block hash into the LRU cache. + fn insert(&mut self, block: Block::Hash) { + self.0.insert(block, ()); + } + + /// Check if the block is contained in the LRU cache. + fn contains(&mut self, block: &Block::Hash) -> Option<&()> { + self.0.peek(block) + } +} + +impl AnnouncedBlocks { + /// Creates a new `AnnouncedBlocks`. + fn new() -> Self { + Self { + // The total number of pinned blocks is `MAX_PINNED_BLOCKS`, ensure we don't + // exceed the limit. + blocks: LruMap::new(ByLength::new((MAX_PINNED_BLOCKS - MAX_FINALIZED_BLOCKS) as u32)), + // We are keeping a smaller number of announced finalized blocks in memory. + // This is because the `Finalized` event might be triggered before the `NewBlock` event. + finalized: MostRecentFinalizedBlocks(LruMap::new(ByLength::new( + MAX_FINALIZED_BLOCKS as u32, + ))), + } + } + + /// Insert the block into the announced blocks. + fn insert(&mut self, block: Block::Hash, finalized: bool) { + if finalized { + // When a block is declared as finalized, it is removed from the unfinalized blocks. + // + // Given that the finalized blocks are bounded to `MAX_FINALIZED_BLOCKS`, + // this ensures we keep the minimum number of blocks in memory. + self.blocks.remove(&block); + self.finalized.insert(block); + } else { + self.blocks.insert(block, ()); + } + } + + /// Check if the block was previously announced. + fn was_announced(&mut self, block: &Block::Hash) -> bool { + self.blocks.get(block).is_some() || self.finalized.contains(block).is_some() + } +} + impl, Block: BlockT, Client> ChainHeadFollower { /// Create a new [`ChainHeadFollower`]. pub fn new( @@ -96,6 +159,7 @@ impl, Block: BlockT, Client> ChainHeadFollower, startup_point: &StartupPoint, ) -> Result>, SubscriptionManagementError> { - // The block was already pinned by the initial block events or by the finalized event. - if !self.sub_handle.pin_block(&self.sub_id, notification.hash)? { - return Ok(Default::default()) - } + let block_hash = notification.hash; // Ensure we are only reporting blocks after the starting point. if *notification.header.number() < startup_point.finalized_number { return Ok(Default::default()) } - Ok(self.generate_import_events( - notification.hash, - *notification.header.parent_hash(), - notification.is_new_best, - )) + // Ensure the block can be pinned before generating the events. + if !self.sub_handle.pin_block(&self.sub_id, block_hash)? { + // The block is already pinned, this is similar to the check above. + // + // The `SubscriptionManagement` ensures the block is tracked until (short lived): + // - 2 calls to `pin_block` are made (from `Finalized` and `NewBlock` branches). + // - the block is unpinned by the user + // + // This is rather a sanity checks for edge-cases (in theory), where + // [`MAX_FINALIZED_BLOCKS` + 1] finalized events are triggered before the `NewBlock` + // event of the first `Finalized` event. + return Ok(Default::default()) + } + + if self.announced_blocks.was_announced(&block_hash) { + // Block was already reported by the finalized branch. + return Ok(Default::default()) + } + + // Double check the parent hash. If the parent hash is not reported, we have a gap. + let parent_block_hash = *notification.header.parent_hash(); + if !self.announced_blocks.was_announced(&parent_block_hash) { + // The parent block was not reported, we have a gap. + return Err(SubscriptionManagementError::Custom("Parent block was not reported".into())) + } + + self.announced_blocks.insert(block_hash, false); + Ok(self.generate_import_events(block_hash, parent_block_hash, notification.is_new_best)) } /// Generates new block events from the given finalized hashes. @@ -448,12 +548,21 @@ where return Err(SubscriptionManagementError::BlockHeaderAbsent) }; + if !self.announced_blocks.was_announced(first_header.parent_hash()) { + return Err(SubscriptionManagementError::Custom( + "Parent block was not reported for a finalized block".into(), + )); + } + let parents = std::iter::once(first_header.parent_hash()).chain(finalized_block_hashes.iter()); for (i, (hash, parent)) in finalized_block_hashes.iter().zip(parents).enumerate() { - // Check if the block was already reported and thus, is already pinned. - if !self.sub_handle.pin_block(&self.sub_id, *hash)? { - continue + // Ensure the block is pinned before generating the events. + self.sub_handle.pin_block(&self.sub_id, *hash)?; + + // Check if the block was already reported. + if self.announced_blocks.was_announced(hash) { + continue; } // Generate `NewBlock` events for all blocks beside the last block in the list @@ -461,6 +570,7 @@ where if !is_last { // Generate only the `NewBlock` event for this block. events.extend(self.generate_import_events(*hash, *parent, false)); + self.announced_blocks.insert(*hash, true); continue; } @@ -483,7 +593,8 @@ where } // Let's generate the `NewBlock` and `NewBestBlock` events for the block. - events.extend(self.generate_import_events(*hash, *parent, true)) + events.extend(self.generate_import_events(*hash, *parent, true)); + self.announced_blocks.insert(*hash, true); } Ok(events) @@ -545,6 +656,10 @@ where let pruned_block_hashes = self.get_pruned_hashes(¬ification.stale_heads, last_finalized)?; + for finalized in &finalized_block_hashes { + self.announced_blocks.insert(*finalized, true); + } + let finalized_event = FollowEvent::Finalized(Finalized { finalized_block_hashes, pruned_block_hashes: pruned_block_hashes.clone(), 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 44a2849d915..0c2486157bd 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -3965,3 +3965,71 @@ async fn follow_report_best_block_of_a_known_block() { }); assert_eq!(event, expected); } + +#[tokio::test] +async fn follow_event_with_unknown_parent() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let client = Arc::new(builder.build()); + + let client_mock = Arc::new(ChainHeadMockClient::new(client.clone())); + + let api = ChainHead::new( + client_mock.clone(), + backend, + Arc::new(TokioTestExecutor::default()), + ChainHeadConfig { + global_max_pinned_blocks: MAX_PINNED_BLOCKS, + subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), + subscription_max_ongoing_operations: MAX_OPERATIONS, + max_follow_subscriptions_per_connection: MAX_FOLLOW_SUBSCRIPTIONS_PER_CONNECTION, + max_lagging_distance: MAX_LAGGING_DISTANCE, + }, + ) + .into_rpc(); + + let finalized_hash = client.info().finalized_hash; + let mut sub = api.subscribe_unbounded("chainHead_v1_follow", [false]).await.unwrap(); + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hashes: vec![format!("{:?}", finalized_hash)], + finalized_block_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // Block tree: + // + // finalized -> (gap: block 1) -> block 2 + // + // Block 1 is not announced yet. ChainHead should report the stop + // event when encountering an unknown parent of block 2. + + // Note: `client` is used just for constructing the blocks. + // The blocks are imported to chainHead using the `client_mock`. + let block_1 = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap() + .build() + .unwrap() + .block; + let block_1_hash = block_1.hash(); + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let block_2 = BlockBuilderBuilder::new(&*client) + .on_parent_block(block_1_hash) + .with_parent_block_number(1) + .build() + .unwrap() + .build() + .unwrap() + .block; + client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + run_with_timeout(client_mock.trigger_import_stream(block_2.header)).await; + // When importing the block 2, chainHead detects a gap in our blocks and stops. + assert_matches!(get_next_event::>(&mut sub).await, FollowEvent::Stop); +} -- GitLab From d73c56de36263b6c446910a4904368d827b753eb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Oct 2024 11:47:30 +0100 Subject: [PATCH 061/127] =?UTF-8?q?[CI]=C2=A0Fix=20backport=20bot=20(#5905?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems to also need actions permission otherwise it error when trying to backport a change to the yml files liker [here](https://github.com/paritytech/polkadot-sdk/actions/runs/11143649431/job/30969199054). --- .github/workflows/command-backport.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 5b32f954d0c..7f1d59bc0f6 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -10,6 +10,7 @@ permissions: contents: write # so it can comment pull-requests: write # so it can create pull requests issues: write + actions: write # It may have to backport changes to the CI as well. jobs: backport: -- GitLab From fe0bfb79f4c883abbc3214519d19e46617c20bd2 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Oct 2024 12:11:58 +0100 Subject: [PATCH 062/127] [CI] Update try-runtime-cli (#5890) Update try-runtime-cli to 0.8.0 for MBM testing. --------- Signed-off-by: Oliver Tale-Yazdi --- .github/workflows/check-runtime-migration.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 8d30e752b11..39a1c4d0348 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -50,54 +50,59 @@ jobs: package: westend-runtime wasm: westend_runtime.compact.compressed.wasm uri: "wss://try-runtime-westend.polkadot.io:443" - subcommand_extra_args: "--no-weight-warnings" + subcommand_extra_args: "--no-weight-warnings --blocktime 6000" command_extra_args: "" - network: rococo package: rococo-runtime wasm: rococo_runtime.compact.compressed.wasm uri: "wss://try-runtime-rococo.polkadot.io:443" - subcommand_extra_args: "--no-weight-warnings" + subcommand_extra_args: "--no-weight-warnings --blocktime 6000" command_extra_args: "" - network: asset-hub-westend package: asset-hub-westend-runtime wasm: asset_hub_westend_runtime.compact.compressed.wasm uri: "wss://westend-asset-hub-rpc.polkadot.io:443" - subcommand_extra_args: "" + subcommand_extra_args: " --blocktime 6000" command_extra_args: "" - network: "asset-hub-rococo" package: "asset-hub-rococo-runtime" wasm: "asset_hub_rococo_runtime.compact.compressed.wasm" uri: "wss://rococo-asset-hub-rpc.polkadot.io:443" - subcommand_extra_args: "" + subcommand_extra_args: " --blocktime 6000" command_extra_args: "" - network: "bridge-hub-westend" package: "bridge-hub-westend-runtime" wasm: "bridge_hub_westend_runtime.compact.compressed.wasm" uri: "wss://westend-bridge-hub-rpc.polkadot.io:443" + subcommand_extra_args: " --blocktime 6000" - network: "bridge-hub-rococo" package: "bridge-hub-rococo-runtime" wasm: "bridge_hub_rococo_runtime.compact.compressed.wasm" uri: "wss://rococo-bridge-hub-rpc.polkadot.io:443" + subcommand_extra_args: " --blocktime 6000" - network: "contracts-rococo" package: "contracts-rococo-runtime" wasm: "contracts_rococo_runtime.compact.compressed.wasm" uri: "wss://rococo-contracts-rpc.polkadot.io:443" + subcommand_extra_args: " --blocktime 6000" - network: "collectives-westend" package: "collectives-westend-runtime" wasm: "collectives_westend_runtime.compact.compressed.wasm" uri: "wss://westend-collectives-rpc.polkadot.io:443" command_extra_args: "--disable-spec-name-check" + subcommand_extra_args: " --blocktime 6000" - network: "coretime-rococo" package: "coretime-rococo-runtime" wasm: "coretime_rococo_runtime.compact.compressed.wasm" uri: "wss://rococo-coretime-rpc.polkadot.io:443" + subcommand_extra_args: " --blocktime 6000" steps: - name: Checkout uses: actions/checkout@v4 - name: Download CLI run: | - curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.7.0/try-runtime-x86_64-unknown-linux-musl -o try-runtime + curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.8.0/try-runtime-x86_64-unknown-linux-musl -o try-runtime chmod +x ./try-runtime echo "Using try-runtime-cli version:" ./try-runtime --version -- GitLab From 215252e7b55a3239a4f0b6b86aa03e9946c7ae2e Mon Sep 17 00:00:00 2001 From: "Shoyu Vanilla (Flint)" Date: Mon, 7 Oct 2024 23:46:47 +0900 Subject: [PATCH 063/127] [FRAME] fix: Do not emit `Issued { amount: 0 }` event (#5946) closes #5942 Couldn't find any emissions of `Event::Issued` without amount check other than in this PR. Currently, we have; https://github.com/paritytech/polkadot-sdk/blob/4bda956d2c635c3926578741a19fbcc3de69cbb8/substrate/frame/balances/src/impl_currency.rs#L212-L220 and https://github.com/paritytech/polkadot-sdk/blob/4bda956d2c635c3926578741a19fbcc3de69cbb8/substrate/frame/balances/src/impl_currency.rs#L293-L306 --- prdoc/pr_5946.prdoc | 15 +++++++++++++++ substrate/frame/balances/src/impl_fungible.rs | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5946.prdoc diff --git a/prdoc/pr_5946.prdoc b/prdoc/pr_5946.prdoc new file mode 100644 index 00000000000..9a858c980a1 --- /dev/null +++ b/prdoc/pr_5946.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: "[FRAME] fix: Do not emit `Issued { amount: 0 }` event" + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + Filter out `Issued` events in `pallet-balances` module when its balance amount is zero. + +crates: + - name: pallet-balances + bump: patch diff --git a/substrate/frame/balances/src/impl_fungible.rs b/substrate/frame/balances/src/impl_fungible.rs index cf95a97d191..4470c3cc9eb 100644 --- a/substrate/frame/balances/src/impl_fungible.rs +++ b/substrate/frame/balances/src/impl_fungible.rs @@ -354,7 +354,9 @@ impl, I: 'static> fungible::Balanced for Pallet Self::deposit_event(Event::::Withdraw { who: who.clone(), amount }); } fn done_issue(amount: Self::Balance) { - Self::deposit_event(Event::::Issued { amount }); + if !amount.is_zero() { + Self::deposit_event(Event::::Issued { amount }); + } } fn done_rescind(amount: Self::Balance) { Self::deposit_event(Event::::Rescinded { amount }); -- GitLab From 5778b4555d2b637ed2b9fc1a02ca1326946720f9 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 7 Oct 2024 16:56:37 +0200 Subject: [PATCH 064/127] Bridges - add version guarding for standalone relaying of parachains and messages (#5952) This PR adds the ability to start version guarding when performing standalone relaying of messages and parachains. ## Follow-up - decouple and simplify `fn start_relay_guards`: https://github.com/paritytech/polkadot-sdk/issues/5923 --------- Co-authored-by: command-bot <> --- .../relays/lib-substrate-relay/src/cli/mod.rs | 12 ------------ .../lib-substrate-relay/src/cli/relay_headers.rs | 1 + .../src/cli/relay_messages.rs | 16 ++++++++++++++++ .../src/cli/relay_parachains.rs | 7 +++++++ bridges/relays/parachains/src/parachains_loop.rs | 8 ++++++++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/bridges/relays/lib-substrate-relay/src/cli/mod.rs b/bridges/relays/lib-substrate-relay/src/cli/mod.rs index be64866fc14..d7aa38f1f2b 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/mod.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/mod.rs @@ -20,7 +20,6 @@ use rbtag::BuildInfo; use sp_runtime::traits::TryConvert; use std::str::FromStr; use structopt::StructOpt; -use strum::{EnumString, VariantNames}; pub mod bridge; pub mod chain_schema; @@ -139,17 +138,6 @@ where } } -#[doc = "Runtime version params."] -#[derive(StructOpt, Debug, PartialEq, Eq, Clone, Copy, EnumString, VariantNames)] -pub enum RuntimeVersionType { - /// Auto query version from chain - Auto, - /// Custom `spec_version` and `transaction_version` - Custom, - /// Read version from bundle dependencies directly. - Bundle, -} - #[cfg(test)] mod tests { use super::*; diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs index ea92a0c9acc..308b041c46f 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_headers.rs @@ -96,6 +96,7 @@ pub trait HeadersRelayer: RelayToRelayHeadersCliBridge { signer: target_sign, mortality: target_transactions_mortality, }; + Self::Finality::start_relay_guards(&target_client, target_client.can_start_version_guard()) .await?; diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs index 3878b081d6c..71d3adc078e 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_messages.rs @@ -121,6 +121,8 @@ where anyhow::format_err!("Invalid laneId: {:?}!", invalid_lane_id) })?; + Self::start_relay_guards(&target_client, target_client.can_start_version_guard()).await?; + crate::messages::run::(MessagesRelayParams { source_client, source_transaction_params: TransactionParams { @@ -216,4 +218,18 @@ where ) .await } + + /// Add relay guards if required. + async fn start_relay_guards( + target_client: &impl Client, + enable_version_guard: bool, + ) -> relay_substrate_client::Result<()> { + if enable_version_guard { + relay_substrate_client::guard::abort_on_spec_version_change( + target_client.clone(), + target_client.simple_runtime_version().await?.spec_version, + ); + } + Ok(()) + } } diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs index 77cd395ff72..83285b69f70 100644 --- a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs +++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs @@ -32,6 +32,7 @@ use crate::{ chain_schema::*, DefaultClient, PrometheusParams, }, + finality::SubstrateFinalitySyncPipeline, parachains::{source::ParachainsSource, target::ParachainsTarget, ParachainsPipelineAdapter}, TransactionParams, }; @@ -104,6 +105,12 @@ where data.prometheus_params.into_metrics_params()?; GlobalMetrics::new()?.register_and_spawn(&metrics_params.registry)?; + Self::RelayFinality::start_relay_guards( + target_client.target_client(), + target_client.target_client().can_start_version_guard(), + ) + .await?; + parachains_relay::parachains_loop::run( source_client, target_client, diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs index 59ca458e666..dfe6b230ced 100644 --- a/bridges/relays/parachains/src/parachains_loop.rs +++ b/bridges/relays/parachains/src/parachains_loop.rs @@ -177,6 +177,14 @@ pub async fn run( where P::SourceRelayChain: Chain, { + log::info!( + target: "bridge", + "Starting {} -> {} finality proof relay: relaying (only_free_headers: {:?}) headers", + P::SourceParachain::NAME, + P::TargetChain::NAME, + only_free_headers, + ); + let exit_signal = exit_signal.shared(); relay_utils::relay_loop(source_client, target_client) .with_metrics(metrics_params) -- GitLab From 3846691350abd2c39f557500fe1f115cd6676d16 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 7 Oct 2024 10:57:49 -0400 Subject: [PATCH 065/127] Introduce and Implement `VestedTransfer` Trait (#5630) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces a `VestedTransfer` Trait, which handles making a transfer while also applying a vesting schedule to that balance. This can be used in pallets like the Treasury pallet, where now we can easily introduce a `vested_spend` extrinsic as an alternative to giving all funds up front. We implement `()` for the `VestedTransfer` trait, which just returns an error, and allows anyone to opt out from needing to use or implement this trait. This PR also updates the logic of `do_vested_transfer` to remove the "pre-check" which was needed before we had a default transactional layer in FRAME. Finally, I also fixed up some bad formatting in the test.rs file. --------- Co-authored-by: Guillaume Thiolliere Co-authored-by: Bastian Köcher --- prdoc/pr_5630.prdoc | 15 +++++ substrate/frame/support/src/traits.rs | 3 +- .../support/src/traits/tokens/currency.rs | 4 +- .../src/traits/tokens/currency/lockable.rs | 53 +++++++++++++++ substrate/frame/vesting/src/benchmarking.rs | 29 ++++----- substrate/frame/vesting/src/lib.rs | 64 +++++++++++++------ substrate/frame/vesting/src/tests.rs | 61 +++++++++++++++--- 7 files changed, 182 insertions(+), 47 deletions(-) create mode 100644 prdoc/pr_5630.prdoc diff --git a/prdoc/pr_5630.prdoc b/prdoc/pr_5630.prdoc new file mode 100644 index 00000000000..bafb9e746d4 --- /dev/null +++ b/prdoc/pr_5630.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: Introduce and Implement the `VestedTransfer` Trait + +doc: + - audience: Runtime Dev + description: | + This PR introduces a new trait `VestedTransfer` which is implemented by `pallet_vesting`. With this, other pallets can easily introduce vested transfers into their logic. + +crates: + - name: frame-support + bump: minor + - name: pallet-vesting + bump: minor diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 6e59ff9d030..631225b9a32 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -23,7 +23,8 @@ pub mod tokens; pub use tokens::{ currency::{ ActiveIssuanceOf, Currency, InspectLockableCurrency, LockIdentifier, LockableCurrency, - NamedReservableCurrency, ReservableCurrency, TotalIssuanceOf, VestingSchedule, + NamedReservableCurrency, ReservableCurrency, TotalIssuanceOf, VestedTransfer, + VestingSchedule, }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, diff --git a/substrate/frame/support/src/traits/tokens/currency.rs b/substrate/frame/support/src/traits/tokens/currency.rs index b3db4c98001..ea2c66a32cb 100644 --- a/substrate/frame/support/src/traits/tokens/currency.rs +++ b/substrate/frame/support/src/traits/tokens/currency.rs @@ -30,7 +30,9 @@ use sp_runtime::{traits::MaybeSerializeDeserialize, DispatchError}; mod reservable; pub use reservable::{NamedReservableCurrency, ReservableCurrency}; mod lockable; -pub use lockable::{InspectLockableCurrency, LockIdentifier, LockableCurrency, VestingSchedule}; +pub use lockable::{ + InspectLockableCurrency, LockIdentifier, LockableCurrency, VestedTransfer, VestingSchedule, +}; /// Abstraction over a fungible assets system. pub trait Currency { diff --git a/substrate/frame/support/src/traits/tokens/currency/lockable.rs b/substrate/frame/support/src/traits/tokens/currency/lockable.rs index 51a48dd15ce..4ec45c908e6 100644 --- a/substrate/frame/support/src/traits/tokens/currency/lockable.rs +++ b/substrate/frame/support/src/traits/tokens/currency/lockable.rs @@ -112,3 +112,56 @@ pub trait VestingSchedule { /// NOTE: This doesn't alter the free balance of the account. fn remove_vesting_schedule(who: &AccountId, schedule_index: u32) -> DispatchResult; } + +/// A vested transfer over a currency. This allows a transferred amount to vest over time. +pub trait VestedTransfer { + /// The quantity used to denote time; usually just a `BlockNumber`. + type Moment; + + /// The currency that this schedule applies to. + type Currency: Currency; + + /// Execute a vested transfer from `source` to `target` with the given schedule: + /// - `locked`: The amount to be transferred and for the vesting schedule to apply to. + /// - `per_block`: The amount to be unlocked each block. (linear vesting) + /// - `starting_block`: The block where the vesting should start. This block can be in the past + /// or future, and should adjust when the tokens become available to the user. + /// + /// Example: Assume we are on block 100. If `locked` amount is 100, and `per_block` is 1: + /// - If `starting_block` is 0, then the whole 100 tokens will be available right away as the + /// vesting schedule started in the past and has fully completed. + /// - If `starting_block` is 50, then 50 tokens are made available right away, and 50 more + /// tokens will unlock one token at a time until block 150. + /// - If `starting_block` is 100, then each block, 1 token will be unlocked until the whole + /// balance is unlocked at block 200. + /// - If `starting_block` is 200, then the 100 token balance will be completely locked until + /// block 200, and then start to unlock one token at a time until block 300. + fn vested_transfer( + source: &AccountId, + target: &AccountId, + locked: >::Balance, + per_block: >::Balance, + starting_block: Self::Moment, + ) -> DispatchResult; +} + +// An no-op implementation of `VestedTransfer` for pallets that require this trait, but users may +// not want to implement this functionality +pub struct NoVestedTransfers { + phantom: core::marker::PhantomData, +} + +impl> VestedTransfer for NoVestedTransfers { + type Moment = (); + type Currency = C; + + fn vested_transfer( + _source: &AccountId, + _target: &AccountId, + _locked: >::Balance, + _per_block: >::Balance, + _starting_block: Self::Moment, + ) -> DispatchResult { + Err(sp_runtime::DispatchError::Unavailable.into()) + } +} diff --git a/substrate/frame/vesting/src/benchmarking.rs b/substrate/frame/vesting/src/benchmarking.rs index 68214c4f47c..736dd6eac1a 100644 --- a/substrate/frame/vesting/src/benchmarking.rs +++ b/substrate/frame/vesting/src/benchmarking.rs @@ -42,7 +42,7 @@ fn add_locks(who: &T::AccountId, n: u8) { } fn add_vesting_schedules( - target: AccountIdLookupOf, + target: &T::AccountId, n: u32, ) -> Result, &'static str> { let min_transfer = T::MinVestedTransfer::get(); @@ -52,7 +52,6 @@ fn add_vesting_schedules( let starting_block = 1u32; let source: T::AccountId = account("source", 0, SEED); - let source_lookup = T::Lookup::unlookup(source.clone()); T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); T::BlockNumberProvider::set_block_number(BlockNumberFor::::zero()); @@ -62,11 +61,7 @@ fn add_vesting_schedules( total_locked += locked; let schedule = VestingInfo::new(locked, per_block, starting_block.into()); - assert_ok!(Vesting::::do_vested_transfer( - source_lookup.clone(), - target.clone(), - schedule - )); + assert_ok!(Vesting::::do_vested_transfer(&source, target, schedule)); // Top up to guarantee we can always transfer another schedule. T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); @@ -81,11 +76,10 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); - let expected_balance = add_vesting_schedules::(caller_lookup, s)?; + let expected_balance = add_vesting_schedules::(&caller, s)?; // At block zero, everything is vested. assert_eq!(System::::block_number(), BlockNumberFor::::zero()); @@ -109,11 +103,10 @@ benchmarks! { let s in 1 .. T::MAX_VESTING_SCHEDULES; let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); - add_vesting_schedules::(caller_lookup, s)?; + add_vesting_schedules::(&caller, s)?; // At block 21, everything is unlocked. T::BlockNumberProvider::set_block_number(21u32.into()); @@ -141,7 +134,7 @@ benchmarks! { T::Currency::make_free_balance_be(&other, T::Currency::minimum_balance()); add_locks::(&other, l as u8); - let expected_balance = add_vesting_schedules::(other_lookup.clone(), s)?; + let expected_balance = add_vesting_schedules::(&other, s)?; // At block zero, everything is vested. assert_eq!(System::::block_number(), BlockNumberFor::::zero()); @@ -171,7 +164,7 @@ benchmarks! { T::Currency::make_free_balance_be(&other, T::Currency::minimum_balance()); add_locks::(&other, l as u8); - add_vesting_schedules::(other_lookup.clone(), s)?; + add_vesting_schedules::(&other, s)?; // At block 21 everything is unlocked. T::BlockNumberProvider::set_block_number(21u32.into()); @@ -206,7 +199,7 @@ benchmarks! { add_locks::(&target, l as u8); // Add one vesting schedules. let orig_balance = T::Currency::free_balance(&target); - let mut expected_balance = add_vesting_schedules::(target_lookup.clone(), s)?; + let mut expected_balance = add_vesting_schedules::(&target, s)?; let transfer_amount = T::MinVestedTransfer::get(); let per_block = transfer_amount.checked_div(&20u32.into()).unwrap(); @@ -246,7 +239,7 @@ benchmarks! { add_locks::(&target, l as u8); // Add one less than max vesting schedules let orig_balance = T::Currency::free_balance(&target); - let mut expected_balance = add_vesting_schedules::(target_lookup.clone(), s)?; + let mut expected_balance = add_vesting_schedules::(&target, s)?; let transfer_amount = T::MinVestedTransfer::get(); let per_block = transfer_amount.checked_div(&20u32.into()).unwrap(); @@ -281,7 +274,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); // Add max vesting schedules. - let expected_balance = add_vesting_schedules::(caller_lookup, s)?; + let expected_balance = add_vesting_schedules::(&caller, s)?; // Schedules are not vesting at block 0. assert_eq!(System::::block_number(), BlockNumberFor::::zero()); @@ -332,7 +325,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); // Add max vesting schedules. - let total_transferred = add_vesting_schedules::(caller_lookup, s)?; + let total_transferred = add_vesting_schedules::(&caller, s)?; // Go to about half way through all the schedules duration. (They all start at 1, and have a duration of 20 or 21). T::BlockNumberProvider::set_block_number(11u32.into()); @@ -397,7 +390,7 @@ force_remove_vesting_schedule { // Give target existing locks. add_locks::(&target, l as u8); - let _ = add_vesting_schedules::(target_lookup.clone(), s)?; + add_vesting_schedules::(&target, s)?; // The last vesting schedule. let schedule_index = s - 1; diff --git a/substrate/frame/vesting/src/lib.rs b/substrate/frame/vesting/src/lib.rs index bfc10efeed7..15f8d397f81 100644 --- a/substrate/frame/vesting/src/lib.rs +++ b/substrate/frame/vesting/src/lib.rs @@ -66,8 +66,8 @@ use frame_support::{ ensure, storage::bounded_vec::BoundedVec, traits::{ - Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestingSchedule, - WithdrawReasons, + Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestedTransfer, + VestingSchedule, WithdrawReasons, }, weights::Weight, }; @@ -351,8 +351,8 @@ pub mod pallet { schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { let transactor = ensure_signed(origin)?; - let transactor = ::unlookup(transactor); - Self::do_vested_transfer(transactor, target, schedule) + let target = T::Lookup::lookup(target)?; + Self::do_vested_transfer(&transactor, &target, schedule) } /// Force a vested transfer. @@ -380,7 +380,9 @@ pub mod pallet { schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { ensure_root(origin)?; - Self::do_vested_transfer(source, target, schedule) + let target = T::Lookup::lookup(target)?; + let source = T::Lookup::lookup(source)?; + Self::do_vested_transfer(&source, &target, schedule) } /// Merge two vesting schedules together, creating a new vesting schedule that unlocks over @@ -525,8 +527,8 @@ impl Pallet { // Execute a vested transfer from `source` to `target` with the given `schedule`. fn do_vested_transfer( - source: AccountIdLookupOf, - target: AccountIdLookupOf, + source: &T::AccountId, + target: &T::AccountId, schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { // Validate user inputs. @@ -534,27 +536,22 @@ impl Pallet { if !schedule.is_valid() { return Err(Error::::InvalidScheduleParams.into()) }; - let target = T::Lookup::lookup(target)?; - let source = T::Lookup::lookup(source)?; // Check we can add to this account prior to any storage writes. Self::can_add_vesting_schedule( - &target, + target, schedule.locked(), schedule.per_block(), schedule.starting_block(), )?; - T::Currency::transfer( - &source, - &target, - schedule.locked(), - ExistenceRequirement::AllowDeath, - )?; + T::Currency::transfer(source, target, schedule.locked(), ExistenceRequirement::AllowDeath)?; // We can't let this fail because the currency transfer has already happened. + // Must be successful as it has been checked before. + // Better to return error on failure anyway. let res = Self::add_vesting_schedule( - &target, + target, schedule.locked(), schedule.per_block(), schedule.starting_block(), @@ -751,8 +748,8 @@ where Ok(()) } - // Ensure we can call `add_vesting_schedule` without error. This should always - // be called prior to `add_vesting_schedule`. + /// Ensure we can call `add_vesting_schedule` without error. This should always + /// be called prior to `add_vesting_schedule`. fn can_add_vesting_schedule( who: &T::AccountId, locked: BalanceOf, @@ -784,3 +781,32 @@ where Ok(()) } } + +/// An implementation that allows the Vesting Pallet to handle a vested transfer +/// on behalf of another Pallet. +impl VestedTransfer for Pallet +where + BalanceOf: MaybeSerializeDeserialize + Debug, +{ + type Currency = T::Currency; + type Moment = BlockNumberFor; + + fn vested_transfer( + source: &T::AccountId, + target: &T::AccountId, + locked: BalanceOf, + per_block: BalanceOf, + starting_block: BlockNumberFor, + ) -> DispatchResult { + use frame_support::storage::{with_transaction, TransactionOutcome}; + let schedule = VestingInfo::new(locked, per_block, starting_block); + with_transaction(|| -> TransactionOutcome { + let result = Self::do_vested_transfer(source, target, schedule); + + match &result { + Ok(()) => TransactionOutcome::Commit(result), + _ => TransactionOutcome::Rollback(result), + } + }) + } +} diff --git a/substrate/frame/vesting/src/tests.rs b/substrate/frame/vesting/src/tests.rs index 57cb59f27a4..0dd7133d930 100644 --- a/substrate/frame/vesting/src/tests.rs +++ b/substrate/frame/vesting/src/tests.rs @@ -280,13 +280,14 @@ fn extra_balance_should_transfer() { // Account 1 has only 5 units vested at block 1 (plus 150 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer_allow_death(Some(1).into(), 3, 155)); // Account 1 can send extra units gained + // Account 1 can send extra units gained + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 3, 155)); // Account 2 has no units vested at block 1, but gained 100 assert_eq!(Vesting::vesting_balance(&2), Some(200)); assert_ok!(Vesting::vest(Some(2).into())); - assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100)); // Account 2 can send extra - // units gained + // Account 2 can send extra units gained + assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100)); }); } @@ -295,14 +296,16 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { ExtBuilder::default().existential_deposit(256).build().execute_with(|| { let user12_free_balance = Balances::free_balance(&12); - assert_eq!(user12_free_balance, 2560); // Account 12 has free balance - // Account 12 has liquid funds + // Account 12 has free balance + assert_eq!(user12_free_balance, 2560); + // Account 12 has liquid funds assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - 256 * 5)); // Account 12 has delayed vesting let user12_vesting_schedule = VestingInfo::new( 256 * 5, - 64, // Vesting over 20 blocks + // Vesting over 20 blocks + 64, 10, ); assert_eq!(VestingStorage::::get(&12).unwrap(), vec![user12_vesting_schedule]); @@ -630,8 +633,10 @@ fn merge_ongoing_schedules() { let sched1 = VestingInfo::new( ED * 10, - ED, // Vest over 10 blocks. - sched0.starting_block() + 5, // Start at block 15. + // Vest over 10 blocks. + ED, + // Start at block 15. + sched0.starting_block() + 5, ); assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1)); assert_eq!(VestingStorage::::get(&2).unwrap(), vec![sched0, sched1]); @@ -1191,3 +1196,43 @@ fn remove_vesting_schedule() { ); }); } + +#[test] +fn vested_transfer_impl_works() { + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + assert_eq!(Balances::free_balance(&3), 256 * 30); + assert_eq!(Balances::free_balance(&4), 256 * 40); + // Account 4 should not have any vesting yet. + assert_eq!(VestingStorage::::get(&4), None); + + // Basic working scenario + assert_ok!(>::vested_transfer( + &3, + &4, + ED * 5, + ED * 5 / 20, + 10 + )); + // Now account 4 should have vesting. + let new_vesting_schedule = VestingInfo::new( + ED * 5, + (ED * 5) / 20, // Vesting over 20 blocks + 10, + ); + assert_eq!(VestingStorage::::get(&4).unwrap(), vec![new_vesting_schedule]); + // Account 4 has 5 * 256 locked. + assert_eq!(Vesting::vesting_balance(&4), Some(256 * 5)); + + // If the transfer fails (because they don't have enough balance), no storage is changed. + assert_noop!( + >::vested_transfer(&3, &4, ED * 9999, ED * 5 / 20, 10), + TokenError::FundsUnavailable + ); + + // If applying the vesting schedule fails (per block is 0), no storage is changed. + assert_noop!( + >::vested_transfer(&3, &4, ED * 5, 0, 10), + Error::::InvalidScheduleParams + ); + }); +} -- GitLab From 5f55185eac6b4366484cf1bd7d374b2e652b076a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 7 Oct 2024 17:47:34 +0200 Subject: [PATCH 066/127] revive: Bump PolkaVM and add static code validation (#5939) This PR adds **static** validation that prevents upload of code that: 1) Contains basic blocks larger than the specified limit (currently `200`) 2) Contains invalid instructions 3) Uses the `sbrk` instruction Doing that statically at upload time (instead of at runtime) allows us to change the basic block limit or add instructions later without worrying about breaking old code. This is well worth the linear scan of the whole blob on deployment in my opinion. Please note that those checks are not applied when existing code is just run (hot path). Also some drive by fixes: - Remove superflous `publish = true` - Abort fixture build on warning and fix existing warnings - Re-enable optimizations in fixture builds (should be fixed now in PolkaVM) - Disable stripping for fixture builds (maybe we can get some line information on trap via `RUST_LOG`) --------- Co-authored-by: command-bot <> Co-authored-by: PG Herveou --- Cargo.lock | 77 +- prdoc/pr_5939.prdoc | 14 + substrate/frame/revive/Cargo.toml | 4 +- substrate/frame/revive/fixtures/Cargo.toml | 3 +- substrate/frame/revive/fixtures/build.rs | 5 +- .../frame/revive/fixtures/build/Cargo.toml | 2 +- .../revive/fixtures/contracts/basic_block.rs | 47 + .../contracts/call_diverging_out_len.rs | 7 +- .../fixtures/contracts/call_return_code.rs | 2 +- .../frame/revive/fixtures/contracts/sbrk.rs | 39 + substrate/frame/revive/src/lib.rs | 4 + substrate/frame/revive/src/limits.rs | 43 +- substrate/frame/revive/src/tests.rs | 46 + substrate/frame/revive/src/wasm/mod.rs | 1 + substrate/frame/revive/src/weights.rs | 842 +++++++++--------- substrate/frame/revive/uapi/Cargo.toml | 2 +- 16 files changed, 664 insertions(+), 474 deletions(-) create mode 100644 prdoc/pr_5939.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/basic_block.rs create mode 100644 substrate/frame/revive/fixtures/contracts/sbrk.rs diff --git a/Cargo.lock b/Cargo.lock index 119a8b3afed..e89b62af01b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1765,9 +1765,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", "arrayvec 0.7.4", @@ -2708,12 +2708,13 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.0.94" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -7918,9 +7919,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -12309,7 +12310,8 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "paste", - "polkavm 0.11.0", + "polkavm 0.12.0", + "polkavm-common 0.12.0", "pretty_assertions", "rlp", "scale-info", @@ -12334,7 +12336,7 @@ dependencies = [ "frame-system", "log", "parity-wasm", - "polkavm-linker 0.11.0", + "polkavm-linker 0.12.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -12394,7 +12396,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive 0.11.0", + "polkavm-derive 0.12.0", "scale-info", ] @@ -16141,15 +16143,15 @@ dependencies = [ [[package]] name = "polkavm" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1195fbc12f11645143a4f3974bf909d25c7f7efddcc6f4e57688d9a518c90bae" +checksum = "f27910c5061e4cea6be6c66684b49d0f42b6a05900c9b0da9e7f3dd2d587a8d4" dependencies = [ "libc", "log", - "polkavm-assembler 0.11.0", - "polkavm-common 0.11.0", - "polkavm-linux-raw 0.11.0", + "polkavm-assembler 0.12.0", + "polkavm-common 0.12.0", + "polkavm-linux-raw 0.12.0", ] [[package]] @@ -16163,9 +16165,9 @@ dependencies = [ [[package]] name = "polkavm-assembler" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b0399659fe7a5370c3e3464188888d29069cfa46d99631d19834a379c15826" +checksum = "82f0e374fa043f31459b30d629d7e866247ac4b6c7662ac72e4e5bf50d052b92" dependencies = [ "log", ] @@ -16187,12 +16189,13 @@ dependencies = [ [[package]] name = "polkavm-common" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "254b19b64ff9b57c06b32c0affed961cb9a32429b8d3e5cf2633cad7fbb3e270" +checksum = "f4e42e082c3d89da2346555baf4d951fe07dcb9208e42a02c272e6d5d0326f9a" dependencies = [ + "blake3", "log", - "polkavm-assembler 0.11.0", + "polkavm-assembler 0.12.0", ] [[package]] @@ -16215,11 +16218,11 @@ dependencies = [ [[package]] name = "polkavm-derive" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f565f0106fbb3598d64b8528d5cb966b6a87a8dd93fbcfe09fb6388ff2865711" +checksum = "540b798393e68a890202d5dc9f86a985b7ea83611e3406d90dc1043e7997b4d1" dependencies = [ - "polkavm-derive-impl-macro 0.11.0", + "polkavm-derive-impl-macro 0.12.0", ] [[package]] @@ -16248,11 +16251,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314445fb5688b4769354087d92be2ac94c487e63ffe74a6fb7bb312e57f20827" +checksum = "d179eddaaef62ce5960faaa2ec9e8f131c81661c8b9365c4d55b275011688534" dependencies = [ - "polkavm-common 0.11.0", + "polkavm-common 0.12.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.79", @@ -16280,11 +16283,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf952e05bc5ce7d81293bae18cb44c271c78615b201d75e983cdcc40d5c6ef1" +checksum = "bd35472599d35d90e24afe9eb39ae6ee6cb1b924f0c03b277ef8b5f174a63853" dependencies = [ - "polkavm-derive-impl 0.11.0", + "polkavm-derive-impl 0.12.0", "syn 2.0.79", ] @@ -16305,15 +16308,15 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535a2095a186ccde2cd2fa721d6370c495586d47714817565b2f6621d31164b3" +checksum = "6f917b16db9ab13819a738a321b48a2d0d20d9e32dedcff75054148676afbec4" dependencies = [ "gimli 0.28.0", "hashbrown 0.14.5", "log", "object 0.36.1", - "polkavm-common 0.11.0", + "polkavm-common 0.12.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -16326,9 +16329,9 @@ checksum = "26e85d3456948e650dff0cfc85603915847faf893ed1e66b020bb82ef4557120" [[package]] name = "polkavm-linux-raw" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011697430dfcfe800d1d7c540ef69e3bdd66e9037cc38f01fee1c2e0908011e" +checksum = "d280301d5b5a321c732173c969058f4b5726f3a0046f6802f396df2599f3753d" [[package]] name = "polling" @@ -16758,7 +16761,7 @@ checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.11.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -16779,7 +16782,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.11.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -16812,7 +16815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.79", @@ -16825,7 +16828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.79", diff --git a/prdoc/pr_5939.prdoc b/prdoc/pr_5939.prdoc new file mode 100644 index 00000000000..babb26281ec --- /dev/null +++ b/prdoc/pr_5939.prdoc @@ -0,0 +1,14 @@ +title: "[pallet-revive] Bump PolkaVM and add static code validation" + +doc: + - audience: Runtime Dev + description: | + Statically validate basic block sizes and instructions. + +crates: + - name: pallet-revive + bump: major + - name: pallet-revive-fixtures + bump: minor + - name: pallet-revive-uapi + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 177b3c3e9ee..e896d9e8fa2 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -19,7 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] environmental = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.11.0", default-features = false } +polkavm = { version = "0.12.0", default-features = false } +polkavm-common = { version = "0.12.0", default-features = false } bitflags = { workspace = true } codec = { features = [ "derive", @@ -83,6 +84,7 @@ std = [ "pallet-revive-fixtures/std", "pallet-timestamp/std", "pallet-utility/std", + "polkavm-common/std", "polkavm/std", "rlp/std", "scale-info/std", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 53db08a3911..75b23fdd44d 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -1,6 +1,5 @@ [package] name = "pallet-revive-fixtures" -publish = true version = "0.1.0" authors.workspace = true edition.workspace = true @@ -22,7 +21,7 @@ log = { workspace = true } parity-wasm = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } -polkavm-linker = { version = "0.11.0" } +polkavm-linker = { version = "0.12.0" } anyhow = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index 944ae246c1b..3178baf6bbe 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -115,6 +115,7 @@ mod build { fn invoke_build(current_dir: &Path) -> Result<()> { let encoded_rustflags = [ + "-Dwarnings", "-Crelocation-model=pie", "-Clink-arg=--emit-relocs", "-Clink-arg=--export-dynamic-symbol=__polkavm_symbol_export_hack__*", @@ -158,8 +159,8 @@ mod build { /// Post-process the compiled code. fn post_process(input_path: &Path, output_path: &Path) -> Result<()> { let mut config = polkavm_linker::Config::default(); - config.set_strip(true); - config.set_optimize(false); + config.set_strip(false); + config.set_optimize(true); let orig = fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/Cargo.toml index 5b3a21b5122..948d7438cf9 100644 --- a/substrate/frame/revive/fixtures/build/Cargo.toml +++ b/substrate/frame/revive/fixtures/build/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.11.0" } +polkavm-derive = { version = "0.12.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/fixtures/contracts/basic_block.rs b/substrate/frame/revive/fixtures/contracts/basic_block.rs new file mode 100644 index 00000000000..0cde7a26463 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/basic_block.rs @@ -0,0 +1,47 @@ +// 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. + +//! Create a basic block that is larger than we allow. + +#![no_std] +#![no_main] + +extern crate common; + +use core::arch::asm; + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // Stores cannot be optimized away because the optimizer cannot + // know whether they have side effects. + let value: u32 = 42; + unsafe { + // Repeat 1001 times to intentionally exceed the allowed basic block limit (1000) + asm!(".rept 1001", "sw {x}, 0(sp)", ".endr", x = in(reg) value); + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs index e13162f8fb4..129adde2cec 100644 --- a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -26,7 +26,8 @@ #![no_std] #![no_main] -use common::input; +extern crate common; + use uapi::{HostFn, HostFnImpl as api}; const BUF_SIZE: usize = 8; @@ -36,7 +37,7 @@ static DATA: [u8; BUF_SIZE] = [1, 2, 3, 4, 5, 6, 7, 8]; /// and expect the call output to match `expected_output`. fn assert_call(callee_address: &[u8; 20], expected_output: [u8; BUF_SIZE]) { let mut output_buf = [0u8; BUF_SIZE]; - let mut output_buf_capped = &mut &mut output_buf[..N]; + let output_buf_capped = &mut &mut output_buf[..N]; api::call( uapi::CallFlags::ALLOW_REENTRY, @@ -62,7 +63,7 @@ fn assert_instantiate(expected_output: [u8; BUF_SIZE]) { api::own_code_hash(&mut code_hash); let mut output_buf = [0u8; BUF_SIZE]; - let mut output_buf_capped = &mut &mut output_buf[..N]; + let output_buf_capped = &mut &mut output_buf[..N]; api::instantiate( &code_hash, diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index d0d7c1bee2a..2d13b9f7095 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -21,7 +21,7 @@ #![no_std] #![no_main] -use common::{input, u256_bytes}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] diff --git a/substrate/frame/revive/fixtures/contracts/sbrk.rs b/substrate/frame/revive/fixtures/contracts/sbrk.rs new file mode 100644 index 00000000000..5b0bba99df8 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/sbrk.rs @@ -0,0 +1,39 @@ +// 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. + +//! Uses the sbrk instruction in order to test that it is rejected. + +#![no_std] +#![no_main] + +extern crate common; + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + polkavm_derive::sbrk(4); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 6d87fd904c3..9986da472c9 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -533,6 +533,10 @@ pub mod pallet { /// The static memory consumption of the blob will be larger than /// [`limits::code::STATIC_MEMORY_BYTES`]. StaticMemoryTooLarge, + /// The program contains a basic block that is larger than allowed. + BasicBlockTooLarge, + /// The program contains an invalid instruction. + InvalidInstruction, /// The contract has reached its maximum number of delegate dependencies. MaxDelegateDependenciesReached, /// The dependency was not found in the contract's delegate dependencies. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 50cdba6d0c9..c6d5ef8d8b1 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -82,7 +82,6 @@ pub mod code { use super::PAGE_SIZE; use crate::{CodeVec, Config, Error, LOG_TARGET}; use alloc::vec::Vec; - use frame_support::ensure; use sp_runtime::DispatchError; /// The maximum length of a code blob in bytes. @@ -109,6 +108,13 @@ pub mod code { /// The code is stored multiple times as part of the compiled program. const EXTRA_OVERHEAD_PER_CODE_BYTE: u32 = 4; + /// The maximum size of a basic block in number of instructions. + /// + /// We need to limit the size of basic blocks because the interpreters lazy compilation + /// compiles one basic block at a time. A malicious program could trigger the compilation + /// of the whole program by creating one giant basic block otherwise. + const BASIC_BLOCK_SIZE: u32 = 1000; + /// Make sure that the various program parts are within the defined limits. pub fn enforce(blob: Vec) -> Result { fn round_page(n: u32) -> u64 { @@ -123,8 +129,30 @@ pub mod code { Error::::CodeRejected })?; - // this is O(n) but it allows us to be more precise - let num_instructions = program.instructions().count() as u64; + // This scans the whole program but we only do it once on code deployment. + // It is safe to do unchecked math in u32 because the size of the program + // was already checked above. + use polkavm_common::program::ISA32_V1_NoSbrk as ISA; + let mut num_instructions: u32 = 0; + let mut max_basic_block_size: u32 = 0; + let mut basic_block_size: u32 = 0; + for inst in program.instructions(ISA) { + num_instructions += 1; + basic_block_size += 1; + if inst.kind.opcode().starts_new_basic_block() { + max_basic_block_size = max_basic_block_size.max(basic_block_size); + basic_block_size = 0; + } + if matches!(inst.kind, polkavm::program::Instruction::invalid) { + log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset); + return Err(>::InvalidInstruction.into()) + } + } + + if max_basic_block_size > BASIC_BLOCK_SIZE { + log::debug!(target: LOG_TARGET, "basic block too large: {max_basic_block_size} limit: {BASIC_BLOCK_SIZE}"); + return Err(Error::::BasicBlockTooLarge.into()) + } // The memory consumptions is the byte size of the whole blob, // minus the RO data payload in the blob, @@ -139,12 +167,17 @@ pub mod code { .saturating_add(round_page(program.rw_data_size())) .saturating_sub(program.rw_data().len() as u64) .saturating_add(round_page(program.stack_size())) - .saturating_add((num_instructions).saturating_mul(BYTES_PER_INSTRUCTION.into())) + .saturating_add( + u64::from(num_instructions).saturating_mul(BYTES_PER_INSTRUCTION.into()), + ) .saturating_add( (program.code().len() as u64).saturating_mul(EXTRA_OVERHEAD_PER_CODE_BYTE.into()), ); - ensure!(memory_size <= STATIC_MEMORY_BYTES as u64, >::StaticMemoryTooLarge); + if memory_size > STATIC_MEMORY_BYTES.into() { + log::debug!(target: LOG_TARGET, "static memory too large: {memory_size} limit: {STATIC_MEMORY_BYTES}"); + return Err(Error::::StaticMemoryTooLarge.into()) + } Ok(blob) } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index c7d1a8b2cf0..4816e65f8f5 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4396,4 +4396,50 @@ mod run_tests { assert_ok!(builder::call(addr).data(data.to_vec()).build()); }); } + + #[test] + fn sbrk_cannot_be_deployed() { + let (code, _) = compile_module("sbrk").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::InvalidInstruction + ); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::InvalidInstruction + ); + }); + } + + #[test] + fn overweight_basic_block_cannot_be_deployed() { + let (code, _) = compile_module("basic_block").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::set_balance(&ALICE, 1_000_000); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + code.clone(), + deposit_limit::(), + ), + >::BasicBlockTooLarge + ); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::BasicBlockTooLarge + ); + }); + } } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index cd274873975..e2256d7dcea 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -291,6 +291,7 @@ impl WasmBlob { let mut module_config = polkavm::ModuleConfig::new(); module_config.set_page_size(limits::PAGE_SIZE); module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); + module_config.set_allow_sbrk(false); let module = polkavm::Module::new(&engine, &module_config, self.code.into_inner().into()) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}"); diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 5e549de3a9f..9a1b2310b4e 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-10-04, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-10-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-jniz7bxx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` @@ -126,7 +126,7 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_700_000 picoseconds. + // Minimum execution time: 2_712_000 picoseconds. Weight::from_parts(2_882_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } @@ -137,10 +137,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `392 + k * (69 ±0)` // Estimated: `382 + k * (70 ±0)` - // Minimum execution time: 13_819_000 picoseconds. - Weight::from_parts(14_021_000, 382) - // Standard Error: 843 - .saturating_add(Weight::from_parts(1_222_715, 0).saturating_mul(k.into())) + // Minimum execution time: 13_394_000 picoseconds. + Weight::from_parts(13_668_000, 382) + // Standard Error: 2_208 + .saturating_add(Weight::from_parts(1_340_842, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -158,12 +158,14 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn call_with_code_per_byte(_c: u32, ) -> Weight { + fn call_with_code_per_byte(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `838` - // Estimated: `4303` - // Minimum execution time: 79_748_000 picoseconds. - Weight::from_parts(82_727_773, 4303) + // Measured: `1466` + // Estimated: `4931` + // Minimum execution time: 80_390_000 picoseconds. + Weight::from_parts(83_627_295, 4931) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -181,16 +183,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `303` // Estimated: `6232` - // Minimum execution time: 179_270_000 picoseconds. - Weight::from_parts(149_933_578, 6232) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) + // Minimum execution time: 184_708_000 picoseconds. + Weight::from_parts(177_995_416, 6232) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_675, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_609, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -209,12 +209,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `657` - // Estimated: `4111` - // Minimum execution time: 156_112_000 picoseconds. - Weight::from_parts(132_715_336, 4111) + // Measured: `1261` + // Estimated: `4706` + // Minimum execution time: 150_137_000 picoseconds. + Weight::from_parts(136_548_469, 4706) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_599, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_531, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -230,10 +230,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `838` - // Estimated: `4303` - // Minimum execution time: 82_111_000 picoseconds. - Weight::from_parts(83_659_000, 4303) + // Measured: `1466` + // Estimated: `4931` + // Minimum execution time: 83_178_000 picoseconds. + Weight::from_parts(84_633_000, 4931) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -248,8 +248,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 49_154_000 picoseconds. - Weight::from_parts(50_856_127, 3574) + // Minimum execution time: 51_526_000 picoseconds. + Weight::from_parts(54_565_973, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -263,8 +263,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 41_347_000 picoseconds. - Weight::from_parts(42_272_000, 3750) + // Minimum execution time: 41_885_000 picoseconds. + Weight::from_parts(42_467_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -274,10 +274,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `495` - // Estimated: `6435` - // Minimum execution time: 25_234_000 picoseconds. - Weight::from_parts(26_339_000, 6435) + // Measured: `492` + // Estimated: `6432` + // Minimum execution time: 24_905_000 picoseconds. + Weight::from_parts(25_483_000, 6432) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -286,17 +286,17 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_509_000 picoseconds. - Weight::from_parts(9_047_452, 0) - // Standard Error: 165 - .saturating_add(Weight::from_parts(197_103, 0).saturating_mul(r.into())) + // Minimum execution time: 6_979_000 picoseconds. + Weight::from_parts(8_272_348, 0) + // Standard Error: 137 + .saturating_add(Weight::from_parts(172_489, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(300_000, 0) + // Minimum execution time: 251_000 picoseconds. + Weight::from_parts(318_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -304,8 +304,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `272` // Estimated: `3737` - // Minimum execution time: 6_537_000 picoseconds. - Weight::from_parts(7_024_000, 3737) + // Minimum execution time: 6_966_000 picoseconds. + Weight::from_parts(7_240_000, 3737) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -314,51 +314,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `369` // Estimated: `3834` - // Minimum execution time: 7_571_000 picoseconds. - Weight::from_parts(8_060_000, 3834) + // Minimum execution time: 7_589_000 picoseconds. + Weight::from_parts(7_958_000, 3834) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 254_000 picoseconds. - Weight::from_parts(281_000, 0) + // Minimum execution time: 235_000 picoseconds. + Weight::from_parts(285_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 279_000 picoseconds. - Weight::from_parts(338_000, 0) + // Minimum execution time: 283_000 picoseconds. + Weight::from_parts(326_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(286_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 198_000 picoseconds. - Weight::from_parts(262_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(665_000, 0) + // Minimum execution time: 651_000 picoseconds. + Weight::from_parts(714_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `103` // Estimated: `0` - // Minimum execution time: 5_393_000 picoseconds. - Weight::from_parts(5_688_000, 0) + // Minimum execution time: 4_476_000 picoseconds. + Weight::from_parts(4_671_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -366,8 +366,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_907_000 picoseconds. - Weight::from_parts(4_154_000, 3517) + // Minimum execution time: 3_800_000 picoseconds. + Weight::from_parts(3_968_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -377,10 +377,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `205 + n * (1 ±0)` // Estimated: `3670 + n * (1 ±0)` - // Minimum execution time: 5_700_000 picoseconds. - Weight::from_parts(6_362_270, 3670) + // Minimum execution time: 5_845_000 picoseconds. + Weight::from_parts(6_473_478, 3670) // Standard Error: 4 - .saturating_add(Weight::from_parts(749, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -391,39 +391,39 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_041_000 picoseconds. - Weight::from_parts(2_216_694, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(620, 0).saturating_mul(n.into())) + // Minimum execution time: 1_980_000 picoseconds. + Weight::from_parts(2_324_567, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(261_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(285_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 226_000 picoseconds. - Weight::from_parts(270_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(291_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 215_000 picoseconds. - Weight::from_parts(273_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(291_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(277_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -431,8 +431,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 5_438_000 picoseconds. - Weight::from_parts(5_695_000, 1552) + // Minimum execution time: 5_650_000 picoseconds. + Weight::from_parts(5_783_000, 1552) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// The range of component `n` is `[0, 262140]`. @@ -440,20 +440,20 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 405_000 picoseconds. - Weight::from_parts(539_737, 0) + // Minimum execution time: 427_000 picoseconds. + Weight::from_parts(351_577, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 222_000 picoseconds. - Weight::from_parts(539_301, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(746_316, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -467,11 +467,11 @@ impl WeightInfo for SubstrateWeight { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `272 + n * (88 ±0)` - // Estimated: `3738 + n * (2563 ±0)` - // Minimum execution time: 15_971_000 picoseconds. - Weight::from_parts(18_759_349, 3738) - // Standard Error: 10_298 - .saturating_add(Weight::from_parts(4_194_635, 0).saturating_mul(n.into())) + // Estimated: `3737 + n * (2563 ±0)` + // Minimum execution time: 15_988_000 picoseconds. + Weight::from_parts(18_796_705, 3737) + // Standard Error: 10_437 + .saturating_add(Weight::from_parts(4_338_085, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -484,22 +484,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_341_000 picoseconds. - Weight::from_parts(4_292_048, 0) - // Standard Error: 2_476 - .saturating_add(Weight::from_parts(193_480, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(959, 0).saturating_mul(n.into())) + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_128_112, 0) + // Standard Error: 2_947 + .saturating_add(Weight::from_parts(198_825, 0).saturating_mul(t.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(1_007, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(758_020, 0) + // Minimum execution time: 292_000 picoseconds. + Weight::from_parts(1_297_376, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(724, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -507,8 +507,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_944_000 picoseconds. - Weight::from_parts(8_358_000, 744) + // Minimum execution time: 7_812_000 picoseconds. + Weight::from_parts(8_171_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -517,8 +517,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_950_000 picoseconds. - Weight::from_parts(45_575_000, 10754) + // Minimum execution time: 44_179_000 picoseconds. + Weight::from_parts(45_068_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -527,8 +527,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 9_095_000 picoseconds. - Weight::from_parts(9_484_000, 744) + // Minimum execution time: 8_964_000 picoseconds. + Weight::from_parts(9_336_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -538,8 +538,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 46_814_000 picoseconds. - Weight::from_parts(47_710_000, 10754) + // Minimum execution time: 45_606_000 picoseconds. + Weight::from_parts(47_190_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -551,12 +551,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_496_000 picoseconds. - Weight::from_parts(9_875_533, 247) - // Standard Error: 34 - .saturating_add(Weight::from_parts(712, 0).saturating_mul(n.into())) - // Standard Error: 34 - .saturating_add(Weight::from_parts(777, 0).saturating_mul(o.into())) + // Minimum execution time: 9_077_000 picoseconds. + Weight::from_parts(9_823_489, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(392, 0).saturating_mul(n.into())) + // Standard Error: 54 + .saturating_add(Weight::from_parts(408, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -568,10 +568,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_185_000 picoseconds. - Weight::from_parts(9_912_715, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(567, 0).saturating_mul(n.into())) + // Minimum execution time: 8_812_000 picoseconds. + Weight::from_parts(9_626_925, 247) + // Standard Error: 77 + .saturating_add(Weight::from_parts(269, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -583,10 +583,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_586_000 picoseconds. - Weight::from_parts(9_536_041, 247) - // Standard Error: 54 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) + // Minimum execution time: 8_143_000 picoseconds. + Weight::from_parts(9_229_363, 247) + // Standard Error: 77 + .saturating_add(Weight::from_parts(1_198, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -597,10 +597,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_813_128, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(880, 0).saturating_mul(n.into())) + // Minimum execution time: 7_899_000 picoseconds. + Weight::from_parts(8_591_860, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(461, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -611,10 +611,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_717_000 picoseconds. - Weight::from_parts(10_635_621, 247) - // Standard Error: 58 - .saturating_add(Weight::from_parts(1_430, 0).saturating_mul(n.into())) + // Minimum execution time: 9_215_000 picoseconds. + Weight::from_parts(10_198_528, 247) + // Standard Error: 75 + .saturating_add(Weight::from_parts(1_521, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -623,36 +623,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_503_000 picoseconds. - Weight::from_parts(1_592_000, 0) + // Minimum execution time: 1_406_000 picoseconds. + Weight::from_parts(1_515_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_903_000 picoseconds. - Weight::from_parts(1_996_000, 0) + // Minimum execution time: 1_782_000 picoseconds. + Weight::from_parts(1_890_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_432_000 picoseconds. - Weight::from_parts(1_534_000, 0) + // Minimum execution time: 1_380_000 picoseconds. + Weight::from_parts(1_422_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_606_000 picoseconds. - Weight::from_parts(1_669_000, 0) + // Minimum execution time: 1_504_000 picoseconds. + Weight::from_parts(1_583_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_089_000, 0) + // Minimum execution time: 1_045_000 picoseconds. + Weight::from_parts(1_138_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -660,59 +660,59 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_317_000 picoseconds. - Weight::from_parts(2_449_933, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(o.into())) + // Minimum execution time: 2_039_000 picoseconds. + Weight::from_parts(2_317_406, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(283, 0).saturating_mul(n.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(347, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_980_000 picoseconds. - Weight::from_parts(2_352_243, 0) + // Minimum execution time: 1_880_000 picoseconds. + Weight::from_parts(2_251_392, 0) // Standard Error: 17 - .saturating_add(Weight::from_parts(372, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(313, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_857_000 picoseconds. - Weight::from_parts(2_082_050, 0) + // Minimum execution time: 1_763_000 picoseconds. + Weight::from_parts(1_951_912, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(268, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_693_000 picoseconds. - Weight::from_parts(1_892_600, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(193, 0).saturating_mul(n.into())) + // Minimum execution time: 1_536_000 picoseconds. + Weight::from_parts(1_779_085, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_620_000 picoseconds. - Weight::from_parts(2_818_388, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(39, 0).saturating_mul(n.into())) + // Minimum execution time: 2_343_000 picoseconds. + Weight::from_parts(2_587_750, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(30, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `103` // Estimated: `0` - // Minimum execution time: 10_207_000 picoseconds. - Weight::from_parts(10_627_000, 0) + // Minimum execution time: 9_250_000 picoseconds. + Weight::from_parts(9_637_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -724,17 +724,17 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `630 + t * (140 ±0)` - // Estimated: `4095 + t * (140 ±0)` - // Minimum execution time: 34_452_000 picoseconds. - Weight::from_parts(34_837_900, 4095) - // Standard Error: 30_385 - .saturating_add(Weight::from_parts(1_981_565, 0).saturating_mul(t.into())) + // Measured: `1221 + t * (103 ±0)` + // Estimated: `4686 + t * (103 ±0)` + // Minimum execution time: 33_333_000 picoseconds. + Weight::from_parts(34_378_774, 4686) + // Standard Error: 41_131 + .saturating_add(Weight::from_parts(1_756_626, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 140).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 103).saturating_mul(t.into())) } /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -742,10 +742,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `457` - // Estimated: `3922` - // Minimum execution time: 27_287_000 picoseconds. - Weight::from_parts(28_450_000, 3922) + // Measured: `1048` + // Estimated: `4513` + // Minimum execution time: 27_096_000 picoseconds. + Weight::from_parts(27_934_000, 4513) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -759,12 +759,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `703` - // Estimated: `4160` - // Minimum execution time: 118_660_000 picoseconds. - Weight::from_parts(104_638_796, 4160) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_296, 0).saturating_mul(i.into())) + // Measured: `1257` + // Estimated: `4715` + // Minimum execution time: 118_412_000 picoseconds. + Weight::from_parts(106_130_041, 4715) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_235, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -773,64 +773,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 683_000 picoseconds. - Weight::from_parts(3_836_563, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) + // Minimum execution time: 614_000 picoseconds. + Weight::from_parts(4_320_897, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_371, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_082_000 picoseconds. - Weight::from_parts(5_027_764, 0) + // Minimum execution time: 1_062_000 picoseconds. + Weight::from_parts(4_571_371, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_644, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_572, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(3_585_640, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_586, 0).saturating_mul(n.into())) + // Minimum execution time: 609_000 picoseconds. + Weight::from_parts(4_008_056, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_497, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(3_763_242, 0) + // Minimum execution time: 608_000 picoseconds. + Weight::from_parts(3_839_383, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_582, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_962_000 picoseconds. - Weight::from_parts(27_938_396, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(5_269, 0).saturating_mul(n.into())) + // Minimum execution time: 43_110_000 picoseconds. + Weight::from_parts(31_941_593, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_233, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_133_000 picoseconds. - Weight::from_parts(48_458_000, 0) + // Minimum execution time: 47_798_000 picoseconds. + Weight::from_parts(49_225_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_249_000 picoseconds. - Weight::from_parts(13_518_000, 0) + // Minimum execution time: 12_576_000 picoseconds. + Weight::from_parts(12_731_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -838,8 +838,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `266` // Estimated: `3731` - // Minimum execution time: 14_696_000 picoseconds. - Weight::from_parts(15_106_000, 3731) + // Minimum execution time: 14_306_000 picoseconds. + Weight::from_parts(15_011_000, 3731) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -847,10 +847,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `304` - // Estimated: `3769` - // Minimum execution time: 10_292_000 picoseconds. - Weight::from_parts(10_670_000, 3769) + // Measured: `303` + // Estimated: `3768` + // Minimum execution time: 10_208_000 picoseconds. + Weight::from_parts(10_514_000, 3768) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -858,10 +858,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `304` + // Measured: `303` // Estimated: `3561` - // Minimum execution time: 9_056_000 picoseconds. - Weight::from_parts(9_350_000, 3561) + // Minimum execution time: 9_062_000 picoseconds. + Weight::from_parts(9_414_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -870,10 +870,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_145_000 picoseconds. - Weight::from_parts(10_744_073, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(84_813, 0).saturating_mul(r.into())) + // Minimum execution time: 8_074_000 picoseconds. + Weight::from_parts(9_646_158, 0) + // Standard Error: 58 + .saturating_add(Weight::from_parts(84_694, 0).saturating_mul(r.into())) } } @@ -885,7 +885,7 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_700_000 picoseconds. + // Minimum execution time: 2_712_000 picoseconds. Weight::from_parts(2_882_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } @@ -896,10 +896,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `392 + k * (69 ±0)` // Estimated: `382 + k * (70 ±0)` - // Minimum execution time: 13_819_000 picoseconds. - Weight::from_parts(14_021_000, 382) - // Standard Error: 843 - .saturating_add(Weight::from_parts(1_222_715, 0).saturating_mul(k.into())) + // Minimum execution time: 13_394_000 picoseconds. + Weight::from_parts(13_668_000, 382) + // Standard Error: 2_208 + .saturating_add(Weight::from_parts(1_340_842, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -917,12 +917,14 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn call_with_code_per_byte(_c: u32, ) -> Weight { + fn call_with_code_per_byte(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `838` - // Estimated: `4303` - // Minimum execution time: 79_748_000 picoseconds. - Weight::from_parts(82_727_773, 4303) + // Measured: `1466` + // Estimated: `4931` + // Minimum execution time: 80_390_000 picoseconds. + Weight::from_parts(83_627_295, 4931) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -940,16 +942,14 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `303` // Estimated: `6232` - // Minimum execution time: 179_270_000 picoseconds. - Weight::from_parts(149_933_578, 6232) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) + // Minimum execution time: 184_708_000 picoseconds. + Weight::from_parts(177_995_416, 6232) // Standard Error: 11 - .saturating_add(Weight::from_parts(4_675, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_609, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -968,12 +968,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `657` - // Estimated: `4111` - // Minimum execution time: 156_112_000 picoseconds. - Weight::from_parts(132_715_336, 4111) + // Measured: `1261` + // Estimated: `4706` + // Minimum execution time: 150_137_000 picoseconds. + Weight::from_parts(136_548_469, 4706) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_599, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_531, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -989,10 +989,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `838` - // Estimated: `4303` - // Minimum execution time: 82_111_000 picoseconds. - Weight::from_parts(83_659_000, 4303) + // Measured: `1466` + // Estimated: `4931` + // Minimum execution time: 83_178_000 picoseconds. + Weight::from_parts(84_633_000, 4931) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1007,8 +1007,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 49_154_000 picoseconds. - Weight::from_parts(50_856_127, 3574) + // Minimum execution time: 51_526_000 picoseconds. + Weight::from_parts(54_565_973, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1022,8 +1022,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 41_347_000 picoseconds. - Weight::from_parts(42_272_000, 3750) + // Minimum execution time: 41_885_000 picoseconds. + Weight::from_parts(42_467_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1033,10 +1033,10 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn set_code() -> Weight { // Proof Size summary in bytes: - // Measured: `495` - // Estimated: `6435` - // Minimum execution time: 25_234_000 picoseconds. - Weight::from_parts(26_339_000, 6435) + // Measured: `492` + // Estimated: `6432` + // Minimum execution time: 24_905_000 picoseconds. + Weight::from_parts(25_483_000, 6432) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1045,17 +1045,17 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_509_000 picoseconds. - Weight::from_parts(9_047_452, 0) - // Standard Error: 165 - .saturating_add(Weight::from_parts(197_103, 0).saturating_mul(r.into())) + // Minimum execution time: 6_979_000 picoseconds. + Weight::from_parts(8_272_348, 0) + // Standard Error: 137 + .saturating_add(Weight::from_parts(172_489, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(300_000, 0) + // Minimum execution time: 251_000 picoseconds. + Weight::from_parts(318_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1063,8 +1063,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `272` // Estimated: `3737` - // Minimum execution time: 6_537_000 picoseconds. - Weight::from_parts(7_024_000, 3737) + // Minimum execution time: 6_966_000 picoseconds. + Weight::from_parts(7_240_000, 3737) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1073,51 +1073,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `369` // Estimated: `3834` - // Minimum execution time: 7_571_000 picoseconds. - Weight::from_parts(8_060_000, 3834) + // Minimum execution time: 7_589_000 picoseconds. + Weight::from_parts(7_958_000, 3834) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 254_000 picoseconds. - Weight::from_parts(281_000, 0) + // Minimum execution time: 235_000 picoseconds. + Weight::from_parts(285_000, 0) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 279_000 picoseconds. - Weight::from_parts(338_000, 0) + // Minimum execution time: 283_000 picoseconds. + Weight::from_parts(326_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 240_000 picoseconds. - Weight::from_parts(286_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 198_000 picoseconds. - Weight::from_parts(262_000, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(665_000, 0) + // Minimum execution time: 651_000 picoseconds. + Weight::from_parts(714_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `103` // Estimated: `0` - // Minimum execution time: 5_393_000 picoseconds. - Weight::from_parts(5_688_000, 0) + // Minimum execution time: 4_476_000 picoseconds. + Weight::from_parts(4_671_000, 0) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) @@ -1125,8 +1125,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `52` // Estimated: `3517` - // Minimum execution time: 3_907_000 picoseconds. - Weight::from_parts(4_154_000, 3517) + // Minimum execution time: 3_800_000 picoseconds. + Weight::from_parts(3_968_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1136,10 +1136,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `205 + n * (1 ±0)` // Estimated: `3670 + n * (1 ±0)` - // Minimum execution time: 5_700_000 picoseconds. - Weight::from_parts(6_362_270, 3670) + // Minimum execution time: 5_845_000 picoseconds. + Weight::from_parts(6_473_478, 3670) // Standard Error: 4 - .saturating_add(Weight::from_parts(749, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1150,39 +1150,39 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_041_000 picoseconds. - Weight::from_parts(2_216_694, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(620, 0).saturating_mul(n.into())) + // Minimum execution time: 1_980_000 picoseconds. + Weight::from_parts(2_324_567, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(261_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(285_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 226_000 picoseconds. - Weight::from_parts(270_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(291_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 215_000 picoseconds. - Weight::from_parts(273_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(291_000, 0) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(277_000, 0) } /// Storage: `TransactionPayment::NextFeeMultiplier` (r:1 w:0) /// Proof: `TransactionPayment::NextFeeMultiplier` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `Measured`) @@ -1190,8 +1190,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `67` // Estimated: `1552` - // Minimum execution time: 5_438_000 picoseconds. - Weight::from_parts(5_695_000, 1552) + // Minimum execution time: 5_650_000 picoseconds. + Weight::from_parts(5_783_000, 1552) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// The range of component `n` is `[0, 262140]`. @@ -1199,20 +1199,20 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 405_000 picoseconds. - Weight::from_parts(539_737, 0) + // Minimum execution time: 427_000 picoseconds. + Weight::from_parts(351_577, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 222_000 picoseconds. - Weight::from_parts(539_301, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(746_316, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(294, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } /// Storage: `Revive::DeletionQueueCounter` (r:1 w:1) /// Proof: `Revive::DeletionQueueCounter` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `Measured`) @@ -1226,11 +1226,11 @@ impl WeightInfo for () { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `272 + n * (88 ±0)` - // Estimated: `3738 + n * (2563 ±0)` - // Minimum execution time: 15_971_000 picoseconds. - Weight::from_parts(18_759_349, 3738) - // Standard Error: 10_298 - .saturating_add(Weight::from_parts(4_194_635, 0).saturating_mul(n.into())) + // Estimated: `3737 + n * (2563 ±0)` + // Minimum execution time: 15_988_000 picoseconds. + Weight::from_parts(18_796_705, 3737) + // Standard Error: 10_437 + .saturating_add(Weight::from_parts(4_338_085, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1243,22 +1243,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_341_000 picoseconds. - Weight::from_parts(4_292_048, 0) - // Standard Error: 2_476 - .saturating_add(Weight::from_parts(193_480, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(959, 0).saturating_mul(n.into())) + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_128_112, 0) + // Standard Error: 2_947 + .saturating_add(Weight::from_parts(198_825, 0).saturating_mul(t.into())) + // Standard Error: 26 + .saturating_add(Weight::from_parts(1_007, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(758_020, 0) + // Minimum execution time: 292_000 picoseconds. + Weight::from_parts(1_297_376, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(814, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(724, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1266,8 +1266,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_944_000 picoseconds. - Weight::from_parts(8_358_000, 744) + // Minimum execution time: 7_812_000 picoseconds. + Weight::from_parts(8_171_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1276,8 +1276,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_950_000 picoseconds. - Weight::from_parts(45_575_000, 10754) + // Minimum execution time: 44_179_000 picoseconds. + Weight::from_parts(45_068_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1286,8 +1286,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 9_095_000 picoseconds. - Weight::from_parts(9_484_000, 744) + // Minimum execution time: 8_964_000 picoseconds. + Weight::from_parts(9_336_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1297,8 +1297,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 46_814_000 picoseconds. - Weight::from_parts(47_710_000, 10754) + // Minimum execution time: 45_606_000 picoseconds. + Weight::from_parts(47_190_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1310,12 +1310,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_496_000 picoseconds. - Weight::from_parts(9_875_533, 247) - // Standard Error: 34 - .saturating_add(Weight::from_parts(712, 0).saturating_mul(n.into())) - // Standard Error: 34 - .saturating_add(Weight::from_parts(777, 0).saturating_mul(o.into())) + // Minimum execution time: 9_077_000 picoseconds. + Weight::from_parts(9_823_489, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(392, 0).saturating_mul(n.into())) + // Standard Error: 54 + .saturating_add(Weight::from_parts(408, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1327,10 +1327,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_185_000 picoseconds. - Weight::from_parts(9_912_715, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(567, 0).saturating_mul(n.into())) + // Minimum execution time: 8_812_000 picoseconds. + Weight::from_parts(9_626_925, 247) + // Standard Error: 77 + .saturating_add(Weight::from_parts(269, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1342,10 +1342,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_586_000 picoseconds. - Weight::from_parts(9_536_041, 247) - // Standard Error: 54 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) + // Minimum execution time: 8_143_000 picoseconds. + Weight::from_parts(9_229_363, 247) + // Standard Error: 77 + .saturating_add(Weight::from_parts(1_198, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1356,10 +1356,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_813_128, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(880, 0).saturating_mul(n.into())) + // Minimum execution time: 7_899_000 picoseconds. + Weight::from_parts(8_591_860, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(461, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1370,10 +1370,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_717_000 picoseconds. - Weight::from_parts(10_635_621, 247) - // Standard Error: 58 - .saturating_add(Weight::from_parts(1_430, 0).saturating_mul(n.into())) + // Minimum execution time: 9_215_000 picoseconds. + Weight::from_parts(10_198_528, 247) + // Standard Error: 75 + .saturating_add(Weight::from_parts(1_521, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1382,36 +1382,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_503_000 picoseconds. - Weight::from_parts(1_592_000, 0) + // Minimum execution time: 1_406_000 picoseconds. + Weight::from_parts(1_515_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_903_000 picoseconds. - Weight::from_parts(1_996_000, 0) + // Minimum execution time: 1_782_000 picoseconds. + Weight::from_parts(1_890_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_432_000 picoseconds. - Weight::from_parts(1_534_000, 0) + // Minimum execution time: 1_380_000 picoseconds. + Weight::from_parts(1_422_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_606_000 picoseconds. - Weight::from_parts(1_669_000, 0) + // Minimum execution time: 1_504_000 picoseconds. + Weight::from_parts(1_583_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_089_000, 0) + // Minimum execution time: 1_045_000 picoseconds. + Weight::from_parts(1_138_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1419,59 +1419,59 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_317_000 picoseconds. - Weight::from_parts(2_449_933, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(n.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(o.into())) + // Minimum execution time: 2_039_000 picoseconds. + Weight::from_parts(2_317_406, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(283, 0).saturating_mul(n.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(347, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_980_000 picoseconds. - Weight::from_parts(2_352_243, 0) + // Minimum execution time: 1_880_000 picoseconds. + Weight::from_parts(2_251_392, 0) // Standard Error: 17 - .saturating_add(Weight::from_parts(372, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(313, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_857_000 picoseconds. - Weight::from_parts(2_082_050, 0) + // Minimum execution time: 1_763_000 picoseconds. + Weight::from_parts(1_951_912, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(268, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_693_000 picoseconds. - Weight::from_parts(1_892_600, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(193, 0).saturating_mul(n.into())) + // Minimum execution time: 1_536_000 picoseconds. + Weight::from_parts(1_779_085, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_620_000 picoseconds. - Weight::from_parts(2_818_388, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(39, 0).saturating_mul(n.into())) + // Minimum execution time: 2_343_000 picoseconds. + Weight::from_parts(2_587_750, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(30, 0).saturating_mul(n.into())) } fn seal_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `103` // Estimated: `0` - // Minimum execution time: 10_207_000 picoseconds. - Weight::from_parts(10_627_000, 0) + // Minimum execution time: 9_250_000 picoseconds. + Weight::from_parts(9_637_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1483,17 +1483,17 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `630 + t * (140 ±0)` - // Estimated: `4095 + t * (140 ±0)` - // Minimum execution time: 34_452_000 picoseconds. - Weight::from_parts(34_837_900, 4095) - // Standard Error: 30_385 - .saturating_add(Weight::from_parts(1_981_565, 0).saturating_mul(t.into())) + // Measured: `1221 + t * (103 ±0)` + // Estimated: `4686 + t * (103 ±0)` + // Minimum execution time: 33_333_000 picoseconds. + Weight::from_parts(34_378_774, 4686) + // Standard Error: 41_131 + .saturating_add(Weight::from_parts(1_756_626, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 140).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 103).saturating_mul(t.into())) } /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1501,10 +1501,10 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `457` - // Estimated: `3922` - // Minimum execution time: 27_287_000 picoseconds. - Weight::from_parts(28_450_000, 3922) + // Measured: `1048` + // Estimated: `4513` + // Minimum execution time: 27_096_000 picoseconds. + Weight::from_parts(27_934_000, 4513) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1518,12 +1518,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `703` - // Estimated: `4160` - // Minimum execution time: 118_660_000 picoseconds. - Weight::from_parts(104_638_796, 4160) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_296, 0).saturating_mul(i.into())) + // Measured: `1257` + // Estimated: `4715` + // Minimum execution time: 118_412_000 picoseconds. + Weight::from_parts(106_130_041, 4715) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_235, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1532,64 +1532,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 683_000 picoseconds. - Weight::from_parts(3_836_563, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(n.into())) + // Minimum execution time: 614_000 picoseconds. + Weight::from_parts(4_320_897, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_371, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_082_000 picoseconds. - Weight::from_parts(5_027_764, 0) + // Minimum execution time: 1_062_000 picoseconds. + Weight::from_parts(4_571_371, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_644, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_572, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(3_585_640, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_586, 0).saturating_mul(n.into())) + // Minimum execution time: 609_000 picoseconds. + Weight::from_parts(4_008_056, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_497, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(3_763_242, 0) + // Minimum execution time: 608_000 picoseconds. + Weight::from_parts(3_839_383, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_582, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_962_000 picoseconds. - Weight::from_parts(27_938_396, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(5_269, 0).saturating_mul(n.into())) + // Minimum execution time: 43_110_000 picoseconds. + Weight::from_parts(31_941_593, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_233, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_133_000 picoseconds. - Weight::from_parts(48_458_000, 0) + // Minimum execution time: 47_798_000 picoseconds. + Weight::from_parts(49_225_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_249_000 picoseconds. - Weight::from_parts(13_518_000, 0) + // Minimum execution time: 12_576_000 picoseconds. + Weight::from_parts(12_731_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1597,8 +1597,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `266` // Estimated: `3731` - // Minimum execution time: 14_696_000 picoseconds. - Weight::from_parts(15_106_000, 3731) + // Minimum execution time: 14_306_000 picoseconds. + Weight::from_parts(15_011_000, 3731) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1606,10 +1606,10 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) fn lock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `304` - // Estimated: `3769` - // Minimum execution time: 10_292_000 picoseconds. - Weight::from_parts(10_670_000, 3769) + // Measured: `303` + // Estimated: `3768` + // Minimum execution time: 10_208_000 picoseconds. + Weight::from_parts(10_514_000, 3768) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1617,10 +1617,10 @@ impl WeightInfo for () { /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `MaxEncodedLen`) fn unlock_delegate_dependency() -> Weight { // Proof Size summary in bytes: - // Measured: `304` + // Measured: `303` // Estimated: `3561` - // Minimum execution time: 9_056_000 picoseconds. - Weight::from_parts(9_350_000, 3561) + // Minimum execution time: 9_062_000 picoseconds. + Weight::from_parts(9_414_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1629,9 +1629,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_145_000 picoseconds. - Weight::from_parts(10_744_073, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(84_813, 0).saturating_mul(r.into())) + // Minimum execution time: 8_074_000 picoseconds. + Weight::from_parts(9_646_158, 0) + // Standard Error: 58 + .saturating_add(Weight::from_parts(84_694, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 52de77a1094..8705781db00 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -21,7 +21,7 @@ codec = { features = [ ], optional = true, workspace = true } [target.'cfg(target_arch = "riscv32")'.dependencies] -polkavm-derive = { version = "0.11.0" } +polkavm-derive = { version = "0.12.0" } [package.metadata.docs.rs] default-target = ["wasm32-unknown-unknown"] -- GitLab From 9128dca3c544a5327f0a89241aea1409d67c81b0 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:37:34 +0200 Subject: [PATCH 067/127] [Staking] Noop refactor to prep pallet for currency fungible migration (#5399) This is a no-op refactor of staking pallet to move all `T::Currency` api calls under one module. A followup PR (https://github.com/paritytech/polkadot-sdk/pull/5501) will implement the Currency <> Fungible migration for the pallet. Introduces the new `asset` module that centralizes all interaction with `T::Currency`. This is an attempt to try minimising staking logic changes to minimal parts of the codebase. ## Things of note - `T::Currency::free_balance` in current implementation includes both staked (locked) and liquid tokens (kinda sounds wrong to call it free then). This PR renames it to `stakeable_balance` (any better name suggestions?). With #5501, this will become `free balance that can be held/staked` + `already held/staked balance`. --- .../frame/delegated-staking/src/impls.rs | 4 +- .../frame/delegated-staking/src/tests.rs | 2 +- .../test-staking-e2e/src/lib.rs | 41 +- .../test-staking-e2e/src/mock.rs | 5 +- substrate/frame/fast-unstake/src/tests.rs | 20 +- .../benchmarking/src/inner.rs | 4 +- .../frame/nomination-pools/src/adapter.rs | 2 +- .../frame/offences/benchmarking/src/inner.rs | 63 +-- substrate/frame/root-offences/src/tests.rs | 19 +- substrate/frame/staking/src/asset.rs | 125 +++++ substrate/frame/staking/src/benchmarking.rs | 63 +-- substrate/frame/staking/src/ledger.rs | 18 +- substrate/frame/staking/src/lib.rs | 1 + substrate/frame/staking/src/pallet/impls.rs | 45 +- substrate/frame/staking/src/pallet/mod.rs | 53 +-- substrate/frame/staking/src/slashing.rs | 14 +- substrate/frame/staking/src/testing_utils.rs | 14 +- substrate/frame/staking/src/tests.rs | 435 +++++++++--------- substrate/primitives/staking/src/lib.rs | 2 +- 19 files changed, 538 insertions(+), 392 deletions(-) create mode 100644 substrate/frame/staking/src/asset.rs diff --git a/substrate/frame/delegated-staking/src/impls.rs b/substrate/frame/delegated-staking/src/impls.rs index 4e6812dee24..a443df7b20f 100644 --- a/substrate/frame/delegated-staking/src/impls.rs +++ b/substrate/frame/delegated-staking/src/impls.rs @@ -124,7 +124,7 @@ impl DelegationMigrator for Pallet { /// Only used for testing. #[cfg(feature = "runtime-benchmarks")] - fn migrate_to_direct_staker(agent: Agent) { + fn force_kill_agent(agent: Agent) { >::remove(agent.clone().get()); >::iter() .filter(|(_, delegation)| delegation.agent == agent.clone().get()) @@ -136,8 +136,6 @@ impl DelegationMigrator for Pallet { ); >::remove(&delegator); }); - - T::CoreStaking::migrate_to_direct_staker(&agent.get()); } } diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs index 2c965e18b1b..b7b82a43771 100644 --- a/substrate/frame/delegated-staking/src/tests.rs +++ b/substrate/frame/delegated-staking/src/tests.rs @@ -676,7 +676,7 @@ mod staking_integration { // in equal parts. lets try to migrate this nominator into delegate based stake. // all balance currently is in 200 - assert_eq!(Balances::free_balance(agent), agent_amount); + assert_eq!(pallet_staking::asset::stakeable_balance::(&agent), agent_amount); // to migrate, nominator needs to set an account as a proxy delegator where staked funds // will be moved and delegated back to this old nominator account. This should be funded 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 0dc202ff211..13514969438 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 @@ -322,24 +322,24 @@ fn automatic_unbonding_pools() { 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 init_stakeable_balance_2 = pallet_staking::asset::stakeable_balance::(&2); + let init_stakeable_balance_3 = pallet_staking::asset::stakeable_balance::(&3); let pool_bonded_account = Pools::generate_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); + assert_eq!(staked_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); + assert_eq!(staked_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!(staked_amount_for(pool_bonded_account), 25); assert_eq!(TotalValueLocked::::get(), 25); @@ -350,7 +350,7 @@ fn automatic_unbonding_pools() { 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); + assert_eq!(staked_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); @@ -372,8 +372,8 @@ fn automatic_unbonding_pools() { 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); + let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account); + assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 26); // now unbonding 3 will work, although the pool's ledger still has the unlocking chunks // filled up. @@ -391,20 +391,21 @@ fn automatic_unbonding_pools() { ); // 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); + assert_eq!(pallet_staking::asset::stakeable_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)); + assert_eq!(staked_before_withdraw_pool - 10, staked_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); + // from the pool's non staked balance. + assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 26); + assert_eq!(pallet_staking::asset::staked::(&pool_bonded_account), 15); assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10)); - assert_eq!(Balances::free_balance(pool_bonded_account), 16); + assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 16); - assert_eq!(Balances::free_balance(2), 20); + assert_eq!(pallet_staking::asset::stakeable_balance::(&2), 20); assert_eq!(TotalValueLocked::::get(), 15); // 3 cannot withdraw yet. @@ -423,9 +424,15 @@ fn automatic_unbonding_pools() { 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!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 6); // 5 init bonded + ED + assert_eq!( + pallet_staking::asset::stakeable_balance::(&2), + init_stakeable_balance_2 + ); + assert_eq!( + pallet_staking::asset::stakeable_balance::(&3), + init_stakeable_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 e45452c1ddf..f20e3983b09 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 @@ -915,9 +915,8 @@ 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 staked_amount_for(account_id: AccountId) -> Balance { + pallet_staking::asset::staked::(&account_id) } pub(crate) fn staking_events() -> Vec> { diff --git a/substrate/frame/fast-unstake/src/tests.rs b/substrate/frame/fast-unstake/src/tests.rs index 77128872f28..7c11f381ca1 100644 --- a/substrate/frame/fast-unstake/src/tests.rs +++ b/substrate/frame/fast-unstake/src/tests.rs @@ -137,15 +137,16 @@ fn deregister_works() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); - assert_eq!(::Currency::reserved_balance(&1), 0); + // reserved balance prior to registering for fast unstake. + let pre_reserved = ::Currency::reserved_balance(&1); // Controller account registers for fast unstake. assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); - assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); + assert_eq!(::Currency::reserved_balance(&1) - pre_reserved, Deposit::get()); // Controller then changes mind and deregisters. assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(1))); - assert_eq!(::Currency::reserved_balance(&1), 0); + assert_eq!(::Currency::reserved_balance(&1) - pre_reserved, 0); // Ensure stash no longer exists in the queue. assert_eq!(Queue::::get(1), None); @@ -243,7 +244,8 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // given - assert_eq!(::Currency::reserved_balance(&1), 0); + // reserved balance prior to registering for fast unstake. + let pre_reserved = ::Currency::reserved_balance(&1); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); @@ -251,7 +253,10 @@ mod on_idle { assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(7))); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(9))); - assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); + assert_eq!( + ::Currency::reserved_balance(&1) - pre_reserved, + Deposit::get() + ); assert_eq!(Queue::::count(), 5); assert_eq!(Head::::get(), None); @@ -279,6 +284,9 @@ mod on_idle { // when next_block(true); + // pre_reserve may change due to unstaked amount. + let pre_reserved = ::Currency::reserved_balance(&1); + // then assert_eq!( Head::::get(), @@ -289,7 +297,7 @@ mod on_idle { ); assert_eq!(Queue::::count(), 3); - assert_eq!(::Currency::reserved_balance(&1), 0); + assert_eq!(::Currency::reserved_balance(&1) - pre_reserved, 0); assert_eq!( fast_unstake_events_since_last_call(), diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs index 2a455942511..b0c8f3655a5 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs @@ -41,7 +41,7 @@ use sp_runtime::{ traits::{Bounded, StaticLookup, Zero}, Perbill, }; -use sp_staking::EraIndex; +use sp_staking::{EraIndex, StakingUnchecked}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; @@ -131,6 +131,8 @@ fn migrate_to_transfer_stake(pool_id: PoolId) { ) .expect("member should have enough balance to transfer"); }); + + pallet_staking::Pallet::::migrate_to_direct_staker(&pool_acc); } fn vote_to_balance( diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs index 272b3b60612..f125919dabf 100644 --- a/substrate/frame/nomination-pools/src/adapter.rs +++ b/substrate/frame/nomination-pools/src/adapter.rs @@ -460,6 +460,6 @@ impl< #[cfg(feature = "runtime-benchmarks")] fn remove_as_agent(pool: Pool) { - Delegation::migrate_to_direct_staker(pool.into()) + Delegation::force_kill_agent(pool.into()) } } diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs index b16e5be653d..573114de074 100644 --- a/substrate/frame/offences/benchmarking/src/inner.rs +++ b/substrate/frame/offences/benchmarking/src/inner.rs @@ -20,7 +20,7 @@ use alloc::{vec, vec::Vec}; use frame_benchmarking::v1::{account, benchmarks}; -use frame_support::traits::{Currency, Get}; +use frame_support::traits::Get; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; use sp_runtime::{ @@ -77,8 +77,7 @@ where } type LookupSourceOf = <::Lookup as StaticLookup>::Source; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; +type BalanceOf = ::CurrencyBalance; struct Offender { pub controller: T::AccountId, @@ -89,7 +88,7 @@ struct Offender { } fn bond_amount() -> BalanceOf { - T::Currency::minimum_balance().saturating_mul(10_000u32.into()) + pallet_staking::asset::existential_deposit::().saturating_mul(10_000u32.into()) } fn create_offender(n: u32, nominators: u32) -> Result, &'static str> { @@ -99,7 +98,7 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' let amount = bond_amount::(); // add twice as much balance to prevent the account from being killed. let free_amount = amount.saturating_mul(2u32.into()); - T::Currency::make_free_balance_be(&stash, free_amount); + pallet_staking::asset::set_stakeable_balance::(&stash, free_amount); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), amount, @@ -116,7 +115,7 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' for i in 0..nominators { let nominator_stash: T::AccountId = account("nominator stash", n * MAX_NOMINATORS + i, SEED); - T::Currency::make_free_balance_be(&nominator_stash, free_amount); + pallet_staking::asset::set_stakeable_balance::(&nominator_stash, free_amount); Staking::::bond( RawOrigin::Signed(nominator_stash.clone()).into(), @@ -172,6 +171,14 @@ fn make_offenders( } benchmarks! { + where_clause { + where + ::RuntimeEvent: TryInto>, + ::RuntimeEvent: TryInto>, + ::RuntimeEvent: TryInto, + ::RuntimeEvent: TryInto>, + } + report_offence_grandpa { let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); @@ -196,17 +203,19 @@ benchmarks! { let _ = Offences::::report_offence(reporters, offence); } verify { - // make sure that all slashes have been applied #[cfg(test)] - assert_eq!( - System::::event_count(), 0 - + 1 // offence - + 3 // reporter (reward + endowment) - + 1 // offenders reported - + 3 // offenders slashed - + 1 // offenders chilled - + 3 * n // nominators slashed - ); + { + // make sure that all slashes have been applied + // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter + // account endowed + some funds rescinded from issuance. + assert_eq!(System::::read_events_for_pallet::>().len(), 2 * (n + 1) as usize + 3); + // (n nominators + one validator) * slashed + Slash Reported + assert_eq!(System::::read_events_for_pallet::>().len(), 1 * (n + 1) as usize + 1); + // offence + assert_eq!(System::::read_events_for_pallet::().len(), 1); + // reporter new account + assert_eq!(System::::read_events_for_pallet::>().len(), 1); + } } report_offence_babe { @@ -233,17 +242,19 @@ benchmarks! { let _ = Offences::::report_offence(reporters, offence); } verify { - // make sure that all slashes have been applied #[cfg(test)] - assert_eq!( - System::::event_count(), 0 - + 1 // offence - + 3 // reporter (reward + endowment) - + 1 // offenders reported - + 3 // offenders slashed - + 1 // offenders chilled - + 3 * n // nominators slashed - ); + { + // make sure that all slashes have been applied + // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter + // account endowed + some funds rescinded from issuance. + assert_eq!(System::::read_events_for_pallet::>().len(), 2 * (n + 1) as usize + 3); + // (n nominators + one validator) * slashed + Slash Reported + assert_eq!(System::::read_events_for_pallet::>().len(), 1 * (n + 1) as usize + 1); + // offence + assert_eq!(System::::read_events_for_pallet::().len(), 1); + // reporter new account + assert_eq!(System::::read_events_for_pallet::>().len(), 1); + } } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/frame/root-offences/src/tests.rs b/substrate/frame/root-offences/src/tests.rs index f96884d750d..289bb708efb 100644 --- a/substrate/frame/root-offences/src/tests.rs +++ b/substrate/frame/root-offences/src/tests.rs @@ -17,7 +17,8 @@ use super::*; use frame_support::{assert_err, assert_ok}; -use mock::{active_era, start_session, Balances, ExtBuilder, RootOffences, RuntimeOrigin, System}; +use mock::{active_era, start_session, ExtBuilder, RootOffences, RuntimeOrigin, System, Test as T}; +use pallet_staking::asset; #[test] fn create_offence_fails_given_signed_origin() { @@ -35,18 +36,18 @@ fn create_offence_works_given_root_origin() { assert_eq!(active_era(), 0); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::staked::(&11), 1000); let offenders = [(11, Perbill::from_percent(50))].to_vec(); assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); System::assert_last_event(Event::OffenceCreated { offenders }.into()); // the slash should be applied right away. - assert_eq!(Balances::free_balance(11), 500); + assert_eq!(asset::staked::(&11), 500); // the other validator should keep their balance, because we only created // an offences for the first validator. - assert_eq!(Balances::free_balance(21), 1000); + assert_eq!(asset::staked::(&21), 1000); }) } @@ -58,7 +59,7 @@ fn create_offence_wont_slash_non_active_validators() { assert_eq!(active_era(), 0); // 31 is not an active validator. - assert_eq!(Balances::free_balance(31), 500); + assert_eq!(asset::staked::(&31), 500); let offenders = [(31, Perbill::from_percent(20)), (11, Perbill::from_percent(20))].to_vec(); assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); @@ -66,10 +67,10 @@ fn create_offence_wont_slash_non_active_validators() { System::assert_last_event(Event::OffenceCreated { offenders }.into()); // so 31 didn't get slashed. - assert_eq!(Balances::free_balance(31), 500); + assert_eq!(asset::staked::(&31), 500); // but 11 is an active validator so they got slashed. - assert_eq!(Balances::free_balance(11), 800); + assert_eq!(asset::staked::(&11), 800); }) } @@ -81,7 +82,7 @@ fn create_offence_wont_slash_idle() { assert_eq!(active_era(), 0); // 41 is idle. - assert_eq!(Balances::free_balance(41), 1000); + assert_eq!(asset::staked::(&41), 1000); let offenders = [(41, Perbill::from_percent(50))].to_vec(); assert_ok!(RootOffences::create_offence(RuntimeOrigin::root(), offenders.clone())); @@ -89,6 +90,6 @@ fn create_offence_wont_slash_idle() { System::assert_last_event(Event::OffenceCreated { offenders }.into()); // 41 didn't get slashed. - assert_eq!(Balances::free_balance(41), 1000); + assert_eq!(asset::staked::(&41), 1000); }) } diff --git a/substrate/frame/staking/src/asset.rs b/substrate/frame/staking/src/asset.rs new file mode 100644 index 00000000000..23368b1f8fc --- /dev/null +++ b/substrate/frame/staking/src/asset.rs @@ -0,0 +1,125 @@ +// 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. + +//! Contains all the interactions with [`Config::Currency`] to manipulate the underlying staking +//! asset. + +use frame_support::traits::{Currency, InspectLockableCurrency, LockableCurrency}; + +use crate::{BalanceOf, Config, NegativeImbalanceOf, PositiveImbalanceOf}; + +/// Existential deposit for the chain. +pub fn existential_deposit() -> BalanceOf { + T::Currency::minimum_balance() +} + +/// Total issuance of the chain. +pub fn total_issuance() -> BalanceOf { + T::Currency::total_issuance() +} + +/// Total balance of `who`. Includes both, free and reserved. +pub fn total_balance(who: &T::AccountId) -> BalanceOf { + T::Currency::total_balance(who) +} + +/// Stakeable balance of `who`. +/// +/// This includes balance free to stake along with any balance that is already staked. +pub fn stakeable_balance(who: &T::AccountId) -> BalanceOf { + T::Currency::free_balance(who) +} + +/// Balance of `who` that is currently at stake. +/// +/// The staked amount is locked and cannot be transferred out of `who`s account. +pub fn staked(who: &T::AccountId) -> BalanceOf { + T::Currency::balance_locked(crate::STAKING_ID, who) +} + +/// Set balance that can be staked for `who`. +/// +/// This includes any balance that is already staked. +#[cfg(any(test, feature = "runtime-benchmarks"))] +pub fn set_stakeable_balance(who: &T::AccountId, value: BalanceOf) { + T::Currency::make_free_balance_be(who, value); +} + +/// Update `amount` at stake for `who`. +/// +/// Overwrites the existing stake amount. If passed amount is lower than the existing stake, the +/// difference is unlocked. +pub fn update_stake(who: &T::AccountId, amount: BalanceOf) { + T::Currency::set_lock( + crate::STAKING_ID, + who, + amount, + frame_support::traits::WithdrawReasons::all(), + ); +} + +/// Kill the stake of `who`. +/// +/// All locked amount is unlocked. +pub fn kill_stake(who: &T::AccountId) { + T::Currency::remove_lock(crate::STAKING_ID, who); +} + +/// Slash the value from `who`. +/// +/// A negative imbalance is returned which can be resolved to deposit the slashed value. +pub fn slash( + who: &T::AccountId, + value: BalanceOf, +) -> (NegativeImbalanceOf, BalanceOf) { + T::Currency::slash(who, value) +} + +/// Mint `value` into an existing account `who`. +/// +/// This does not increase the total issuance. +pub fn mint_existing( + who: &T::AccountId, + value: BalanceOf, +) -> Option> { + T::Currency::deposit_into_existing(who, value).ok() +} + +/// Mint reward and create account for `who` if it does not exist. +/// +/// This does not increase the total issuance. +pub fn mint_creating(who: &T::AccountId, value: BalanceOf) -> PositiveImbalanceOf { + T::Currency::deposit_creating(who, value) +} + +/// Deposit newly issued or slashed `value` into `who`. +pub fn deposit_slashed(who: &T::AccountId, value: NegativeImbalanceOf) { + T::Currency::resolve_creating(who, value) +} + +/// Issue `value` increasing total issuance. +/// +/// Creates a negative imbalance. +pub fn issue(value: BalanceOf) -> NegativeImbalanceOf { + T::Currency::issue(value) +} + +/// Burn the amount from the total issuance. +#[cfg(feature = "runtime-benchmarks")] +pub fn burn(amount: BalanceOf) -> PositiveImbalanceOf { + T::Currency::burn(amount) +} diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 1f8580d7a3e..a25085a1803 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -18,7 +18,7 @@ //! Staking pallet benchmarking. use super::*; -use crate::{ConfigOp, Pallet as Staking}; +use crate::{asset, ConfigOp, Pallet as Staking}; use testing_utils::*; use codec::Decode; @@ -26,7 +26,7 @@ use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProv use frame_support::{ pallet_prelude::*, storage::bounded_vec::BoundedVec, - traits::{Currency, Get, Imbalance, UnfilteredDispatchable}, + traits::{Get, Imbalance, UnfilteredDispatchable}, }; use sp_runtime::{ traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, @@ -132,7 +132,7 @@ pub fn create_validator_with_nominators( ErasRewardPoints::::insert(current_era, reward); // Create reward pool - let total_payout = T::Currency::minimum_balance() + let total_payout = asset::existential_deposit::() .saturating_mul(upper_bound.into()) .saturating_mul(1000u32.into()); >::insert(current_era, total_payout); @@ -167,7 +167,7 @@ impl ListScenario { ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); // burn the entire issuance. - let i = T::Currency::burn(T::Currency::total_issuance()); + let i = asset::burn::(asset::total_issuance::()); core::mem::forget(i); // create accounts with the origin weight @@ -197,7 +197,7 @@ impl ListScenario { let dest_weight_as_vote = T::VoterList::score_update_worst_case(&origin_stash1, is_increase); - let total_issuance = T::Currency::total_issuance(); + let total_issuance = asset::total_issuance::(); let dest_weight = T::CurrencyToVote::to_currency(dest_weight_as_vote as u128, total_issuance); @@ -223,7 +223,7 @@ benchmarks! { bond { let stash = create_funded_user::("stash", USER_SEED, 100); let reward_destination = RewardDestination::Staked; - let amount = T::Currency::minimum_balance() * 10u32.into(); + let amount = asset::existential_deposit::() * 10u32.into(); whitelist_account!(stash); }: _(RawOrigin::Signed(stash.clone()), amount, reward_destination) verify { @@ -235,7 +235,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup the worst case list scenario. @@ -249,7 +249,7 @@ benchmarks! { let original_bonded: BalanceOf = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; - let _ = T::Currency::deposit_into_existing(&stash, max_additional).unwrap(); + let _ = asset::mint_existing::(&stash, max_additional).unwrap(); whitelist_account!(stash); }: _(RawOrigin::Signed(stash), max_additional) @@ -264,7 +264,7 @@ benchmarks! { clear_validators_and_nominators::(); // setup the worst case list scenario. - let total_issuance = T::Currency::total_issuance(); + let total_issuance = asset::total_issuance::(); // the weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) @@ -292,7 +292,7 @@ benchmarks! { let s in 0 .. MAX_SPANS; let (stash, controller) = create_stash_controller::(0, 100, RewardDestination::Staked)?; add_slashing_spans::(&stash, s); - let amount = T::Currency::minimum_balance() * 5u32.into(); // Half of total + let amount = asset::existential_deposit::() * 5u32.into(); // Half of total Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; CurrentEra::::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; @@ -312,7 +312,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. @@ -322,7 +322,7 @@ benchmarks! { add_slashing_spans::(&stash, s); assert!(T::VoterList::contains(&stash)); - let ed = T::Currency::minimum_balance(); + let ed = asset::existential_deposit::(); let mut ledger = Ledger::::get(&controller).unwrap(); ledger.active = ed - One::one(); Ledger::::insert(&controller, ledger); @@ -422,7 +422,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note we don't care about the destination position, because // we are just doing an insert into the origin position. @@ -448,7 +448,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. @@ -564,7 +564,7 @@ benchmarks! { // Clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. @@ -611,21 +611,21 @@ benchmarks! { >::insert(current_era, validator.clone(), >::validators(&validator)); let caller = whitelisted_caller(); - let balance_before = T::Currency::free_balance(&validator); + let balance_before = asset::stakeable_balance::(&validator); let mut nominator_balances_before = Vec::new(); for (stash, _) in &nominators { - let balance = T::Currency::free_balance(stash); + let balance = asset::stakeable_balance::(stash); nominator_balances_before.push(balance); } }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) verify { - let balance_after = T::Currency::free_balance(&validator); + let balance_after = asset::stakeable_balance::(&validator); ensure!( balance_before < balance_after, "Balance of validator stash should have increased after payout.", ); for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) { - let balance_after = T::Currency::free_balance(stash); + let balance_after = asset::stakeable_balance::(stash); ensure!( balance_before < &balance_after, "Balance of nominator stash should have increased after payout.", @@ -640,7 +640,7 @@ benchmarks! { clear_validators_and_nominators::(); let origin_weight = MinNominatorBond::::get() - .max(T::Currency::minimum_balance()) + .max(asset::existential_deposit::()) // we use 100 to play friendly with the list threshold values in the mock .max(100u32.into()); @@ -686,7 +686,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. @@ -697,7 +697,7 @@ benchmarks! { add_slashing_spans::(&stash, s); let l = StakingLedger::::new( stash.clone(), - T::Currency::minimum_balance() - One::one(), + asset::existential_deposit::() - One::one(), ); Ledger::::insert(&controller, l); @@ -764,7 +764,7 @@ benchmarks! { ErasRewardPoints::::insert(current_era, reward); // Create reward pool - let total_payout = T::Currency::minimum_balance() * 1000u32.into(); + let total_payout = asset::existential_deposit::() * 1000u32.into(); >::insert(current_era, total_payout); let caller: T::AccountId = whitelisted_caller(); @@ -793,8 +793,8 @@ benchmarks! { staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); } Ledger::::insert(controller, staking_ledger); - let slash_amount = T::Currency::minimum_balance() * 10u32.into(); - let balance_before = T::Currency::free_balance(&stash); + let slash_amount = asset::existential_deposit::() * 10u32.into(); + let balance_before = asset::stakeable_balance::(&stash); }: { crate::slashing::do_slash::( &stash, @@ -804,7 +804,7 @@ benchmarks! { EraIndex::zero() ); } verify { - let balance_after = T::Currency::free_balance(&stash); + let balance_after = asset::stakeable_balance::(&stash); assert!(balance_before > balance_after); } @@ -890,7 +890,7 @@ benchmarks! { // clean up any existing state. clear_validators_and_nominators::(); - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); + let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. @@ -972,7 +972,7 @@ benchmarks! { #[cfg(test)] mod tests { use super::*; - use crate::mock::{Balances, ExtBuilder, RuntimeOrigin, Staking, Test}; + use crate::mock::{ExtBuilder, RuntimeOrigin, Staking, Test}; use frame_support::assert_ok; #[test] @@ -1019,16 +1019,17 @@ mod tests { let current_era = CurrentEra::::get().unwrap(); - let original_free_balance = Balances::free_balance(&validator_stash); + let original_stakeable_balance = asset::stakeable_balance::(&validator_stash); assert_ok!(Staking::payout_stakers_by_page( RuntimeOrigin::signed(1337), validator_stash, current_era, 0 )); - let new_free_balance = Balances::free_balance(&validator_stash); + let new_stakeable_balance = asset::stakeable_balance::(&validator_stash); - assert!(original_free_balance < new_free_balance); + // reward increases stakeable balance + assert!(original_stakeable_balance < new_stakeable_balance); }); } diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs index dc4b4fc326b..ac3be04cf60 100644 --- a/substrate/frame/staking/src/ledger.rs +++ b/substrate/frame/staking/src/ledger.rs @@ -31,15 +31,12 @@ //! performed through the methods exposed by the [`StakingLedger`] implementation in order to ensure //! state consistency. -use frame_support::{ - defensive, ensure, - traits::{Defensive, LockableCurrency}, -}; +use frame_support::{defensive, ensure, traits::Defensive}; use sp_staking::{StakingAccount, StakingInterface}; use crate::{ - BalanceOf, Bonded, Config, Error, Ledger, Pallet, Payee, RewardDestination, StakingLedger, - VirtualStakers, STAKING_ID, + asset, BalanceOf, Bonded, Config, Error, Ledger, Pallet, Payee, RewardDestination, + StakingLedger, VirtualStakers, }; #[cfg(any(feature = "runtime-benchmarks", test))] @@ -190,12 +187,7 @@ impl StakingLedger { // We skip locking virtual stakers. if !Pallet::::is_virtual_staker(&self.stash) { // for direct stakers, update lock on stash based on ledger. - T::Currency::set_lock( - STAKING_ID, - &self.stash, - self.total, - frame_support::traits::WithdrawReasons::all(), - ); + asset::update_stake::(&self.stash, self.total); } Ledger::::insert( @@ -269,7 +261,7 @@ impl StakingLedger { // kill virtual staker if it exists. if >::take(&stash).is_none() { // if not virtual staker, clear locks. - T::Currency::remove_lock(STAKING_ID, &ledger.stash); + asset::kill_stake::(&ledger.stash); } Ok(()) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 9e59cbd3d0c..19d999109d8 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -295,6 +295,7 @@ pub(crate) mod mock; #[cfg(test)] mod tests; +pub mod asset; pub mod election_size_tracker; pub mod inflation; pub mod ledger; diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 2df3bc084eb..6c4fe8140e8 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -27,8 +27,8 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Currency, Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, - InspectLockableCurrency, Len, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, + Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len, OnUnbalanced, + TryCollect, UnixTime, }, weights::Weight, }; @@ -50,7 +50,7 @@ use sp_staking::{ }; use crate::{ - election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, + asset, election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, @@ -96,7 +96,7 @@ impl Pallet { pub(crate) fn inspect_bond_state( stash: &T::AccountId, ) -> Result> { - let lock = T::Currency::balance_locked(crate::STAKING_ID, &stash); + let lock = asset::staked::(&stash); let controller = >::get(stash).ok_or_else(|| { if lock == Zero::zero() { @@ -142,7 +142,7 @@ impl Pallet { pub fn weight_of_fn() -> Box VoteWeight> { // NOTE: changing this to unboxed `impl Fn(..)` return type and the pallet will still // compile, while some types in mock fail to resolve. - let issuance = T::Currency::total_issuance(); + let issuance = asset::total_issuance::(); Box::new(move |who: &T::AccountId| -> VoteWeight { Self::slashable_balance_of_vote_weight(who, issuance) }) @@ -150,7 +150,7 @@ impl Pallet { /// Same as `weight_of_fn`, but made for one time use. pub fn weight_of(who: &T::AccountId) -> VoteWeight { - let issuance = T::Currency::total_issuance(); + let issuance = asset::total_issuance::(); Self::slashable_balance_of_vote_weight(who, issuance) } @@ -164,7 +164,7 @@ impl Pallet { } else { // additional amount or actual balance of stash whichever is lower. additional.min( - T::Currency::free_balance(stash) + asset::stakeable_balance::(stash) .checked_sub(&ledger.total) .ok_or(ArithmeticError::Overflow)?, ) @@ -173,7 +173,7 @@ impl Pallet { ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; ledger.active = ledger.active.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; // last check: the new active amount of ledger must be more than ED. - ensure!(ledger.active >= T::Currency::minimum_balance(), Error::::InsufficientBond); + ensure!(ledger.active >= asset::existential_deposit::(), Error::::InsufficientBond); // NOTE: ledger must be updated prior to calling `Self::weight_of`. ledger.update()?; @@ -198,7 +198,7 @@ impl Pallet { } let new_total = ledger.total; - let ed = T::Currency::minimum_balance(); + let ed = asset::existential_deposit::(); let used_weight = if ledger.unlocking.is_empty() && (ledger.active < ed || ledger.active.is_zero()) { // This account must have called `unbond()` with some value that caused the active @@ -414,12 +414,12 @@ impl Pallet { let dest = Self::payee(StakingAccount::Stash(stash.clone()))?; let maybe_imbalance = match dest { - RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), + RewardDestination::Stash => asset::mint_existing::(stash, amount), RewardDestination::Staked => Self::ledger(Stash(stash.clone())) .and_then(|mut ledger| { ledger.active += amount; ledger.total += amount; - let r = T::Currency::deposit_into_existing(stash, amount).ok(); + let r = asset::mint_existing::(stash, amount); let _ = ledger .update() @@ -429,7 +429,7 @@ impl Pallet { }) .unwrap_or_default(), RewardDestination::Account(ref dest_account) => - Some(T::Currency::deposit_creating(&dest_account, amount)), + Some(asset::mint_creating::(&dest_account, amount)), RewardDestination::None => None, #[allow(deprecated)] RewardDestination::Controller => Self::bonded(stash) @@ -437,7 +437,7 @@ impl Pallet { defensive!("Paying out controller as reward destination which is deprecated and should be migrated."); // This should never happen once payees with a `Controller` variant have been migrated. // But if it does, just pay the controller account. - T::Currency::deposit_creating(&controller, amount) + asset::mint_creating::(&controller, amount) }), }; maybe_imbalance.map(|imbalance| (imbalance, dest)) @@ -576,7 +576,7 @@ impl Pallet { let era_duration = (now_as_millis_u64.defensive_saturating_sub(active_era_start)) .saturated_into::(); let staked = Self::eras_total_stake(&active_era.index); - let issuance = T::Currency::total_issuance(); + let issuance = asset::total_issuance::(); let (validator_payout, remainder) = T::EraPayout::era_payout(staked, issuance, era_duration); @@ -597,7 +597,7 @@ impl Pallet { // Set ending era reward. >::insert(&active_era.index, validator_payout); - T::RewardRemainder::on_unbalanced(T::Currency::issue(remainder)); + T::RewardRemainder::on_unbalanced(asset::issue::(remainder)); // Clear disabled validators. >::kill(); @@ -748,7 +748,7 @@ impl Pallet { fn collect_exposures( supports: BoundedSupportsOf, ) -> BoundedVec<(T::AccountId, Exposure>), MaxWinnersOf> { - let total_issuance = T::Currency::total_issuance(); + let total_issuance = asset::total_issuance::(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) }; @@ -1581,7 +1581,7 @@ impl ScoreProvider for Pallet { // also, we play a trick to make sure that a issuance based-`CurrencyToVote` behaves well: // This will make sure that total issuance is zero, thus the currency to vote will be a 1-1 // conversion. - let imbalance = T::Currency::burn(T::Currency::total_issuance()); + let imbalance = asset::burn::(asset::total_issuance::()); // kinda ugly, but gets the job done. The fact that this works here is a HUGE exception. // Don't try this pattern in other places. core::mem::forget(imbalance); @@ -1919,7 +1919,7 @@ impl StakingInterface for Pallet { impl sp_staking::StakingUnchecked for Pallet { fn migrate_to_virtual_staker(who: &Self::AccountId) { - T::Currency::remove_lock(crate::STAKING_ID, who); + asset::kill_stake::(who); VirtualStakers::::insert(who, ()); } @@ -1956,12 +1956,7 @@ impl sp_staking::StakingUnchecked for Pallet { fn migrate_to_direct_staker(who: &Self::AccountId) { assert!(VirtualStakers::::contains_key(who)); let ledger = StakingLedger::::get(Stash(who.clone())).unwrap(); - T::Currency::set_lock( - crate::STAKING_ID, - who, - ledger.total, - frame_support::traits::WithdrawReasons::all(), - ); + asset::update_stake::(who, ledger.total); VirtualStakers::::remove(who); } } @@ -2097,7 +2092,7 @@ impl Pallet { // ensure locks consistency. if VirtualStakers::::contains_key(stash.clone()) { ensure!( - T::Currency::balance_locked(crate::STAKING_ID, &stash) == Zero::zero(), + asset::staked::(&stash) == Zero::zero(), "virtual stakers should not have any locked balance" ); ensure!( diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 8a4482f52ad..28aa4f89b62 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -25,8 +25,8 @@ use frame_election_provider_support::{ use frame_support::{ pallet_prelude::*, traits::{ - Currency, Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, - InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, WithdrawReasons, + Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, + InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, BoundedVec, @@ -48,11 +48,11 @@ mod impls; pub use impls::*; use crate::{ - slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, DisablingStrategy, - EraPayout, EraRewardPoints, Exposure, ExposurePage, Forcing, LedgerIntegrityState, - MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, - RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, - ValidatorPrefs, + asset, slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, + DisablingStrategy, EraPayout, EraRewardPoints, Exposure, ExposurePage, Forcing, + LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, + PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, + UnlockChunk, ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -779,7 +779,7 @@ pub mod pallet { status ); assert!( - T::Currency::free_balance(stash) >= balance, + asset::stakeable_balance::(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(>::bond( @@ -1023,14 +1023,14 @@ pub mod pallet { } // Reject a bond which is considered to be _dust_. - if value < T::Currency::minimum_balance() { + if value < asset::existential_deposit::() { return Err(Error::::InsufficientBond.into()) } // Would fail if account has no provider. frame_system::Pallet::::inc_consumers(&stash)?; - let stash_balance = T::Currency::free_balance(&stash); + let stash_balance = asset::stakeable_balance::(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); let ledger = StakingLedger::::new(stash.clone(), value); @@ -1068,7 +1068,7 @@ pub mod pallet { /// Schedule a portion of the stash to be unlocked ready for transfer out after the bond /// period ends. If this leaves an amount actively bonded less than - /// T::Currency::minimum_balance(), then it is increased to the full amount. + /// [`asset::existential_deposit`], then it is increased to the full amount. /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// @@ -1124,7 +1124,7 @@ pub mod pallet { ledger.active -= value; // Avoid there being a dust balance left in the staking system. - if ledger.active < T::Currency::minimum_balance() { + if ledger.active < asset::existential_deposit::() { value += ledger.active; ledger.active = Zero::zero(); } @@ -1654,7 +1654,10 @@ pub mod pallet { let initial_unlocking = ledger.unlocking.len() as u32; let (ledger, rebonded_value) = ledger.rebond(value); // Last check: the new active amount of ledger must be more than ED. - ensure!(ledger.active >= T::Currency::minimum_balance(), Error::::InsufficientBond); + ensure!( + ledger.active >= asset::existential_deposit::(), + Error::::InsufficientBond + ); Self::deposit_event(Event::::Bonded { stash: ledger.stash.clone(), @@ -1706,8 +1709,8 @@ pub mod pallet { // virtual stakers should not be allowed to be reaped. ensure!(!Self::is_virtual_staker(&stash), Error::::VirtualStakerNotAllowed); - let ed = T::Currency::minimum_balance(); - let origin_balance = T::Currency::total_balance(&stash); + let ed = asset::existential_deposit::(); + let origin_balance = asset::total_balance::(&stash); let ledger_total = Self::ledger(Stash(stash.clone())).map(|l| l.total).unwrap_or_default(); let reapable = origin_balance < ed || @@ -2074,8 +2077,8 @@ pub mod pallet { // cannot restore ledger for virtual stakers. ensure!(!Self::is_virtual_staker(&stash), Error::::VirtualStakerNotAllowed); - let current_lock = T::Currency::balance_locked(crate::STAKING_ID, &stash); - let stash_balance = T::Currency::free_balance(&stash); + let current_lock = asset::staked::(&stash); + let stash_balance = asset::stakeable_balance::(&stash); let (new_controller, new_total) = match Self::inspect_bond_state(&stash) { Ok(LedgerIntegrityState::Corrupted) => { @@ -2084,12 +2087,7 @@ pub mod pallet { let new_total = if let Some(total) = maybe_total { let new_total = total.min(stash_balance); // enforce lock == ledger.amount. - T::Currency::set_lock( - crate::STAKING_ID, - &stash, - new_total, - WithdrawReasons::all(), - ); + asset::update_stake::(&stash, new_total); new_total } else { current_lock @@ -2116,18 +2114,13 @@ pub mod pallet { // to enforce a new ledger.total and staking lock for this stash. let new_total = maybe_total.ok_or(Error::::CannotRestoreLedger)?.min(stash_balance); - T::Currency::set_lock( - crate::STAKING_ID, - &stash, - new_total, - WithdrawReasons::all(), - ); + asset::update_stake::(&stash, new_total); Ok((stash.clone(), new_total)) }, Err(Error::::BadState) => { // the stash and ledger do not exist but lock is lingering. - T::Currency::remove_lock(crate::STAKING_ID, &stash); + asset::kill_stake::(&stash); ensure!( Self::inspect_bond_state(&stash) == Err(Error::::NotStash), Error::::BadState diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index 9bc8197c50b..9fb782265b8 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -50,15 +50,15 @@ //! Based on research at use crate::{ - BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure, NegativeImbalanceOf, - NominatorSlashInEra, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, - ValidatorSlashInEra, + asset, BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure, + NegativeImbalanceOf, NominatorSlashInEra, Pallet, Perbill, SessionInterface, SpanSlash, + UnappliedSlash, ValidatorSlashInEra, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Defensive, DefensiveSaturating, Imbalance, OnUnbalanced}, + traits::{Defensive, DefensiveSaturating, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -578,7 +578,7 @@ pub fn do_slash( Err(_) => return, // nothing to do. }; - let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); + let value = ledger.slash(value, asset::existential_deposit::(), slash_era); if value.is_zero() { // nothing to do return @@ -586,7 +586,7 @@ pub fn do_slash( // Skip slashing for virtual stakers. The pallets managing them should handle the slashing. if !Pallet::::is_virtual_staker(stash) { - let (imbalance, missing) = T::Currency::slash(stash, value); + let (imbalance, missing) = asset::slash::(stash, value); slashed_imbalance.subsume(imbalance); if !missing.is_zero() { @@ -656,7 +656,7 @@ fn pay_reporters( // this cancels out the reporter reward imbalance internally, leading // to no change in total issuance. - T::Currency::resolve_creating(reporter, reporter_reward); + asset::deposit_slashed::(reporter, reporter_reward); } // the rest goes to the on-slash imbalance handler (e.g. treasury) diff --git a/substrate/frame/staking/src/testing_utils.rs b/substrate/frame/staking/src/testing_utils.rs index 65aaa5f09de..efd4a40f1ab 100644 --- a/substrate/frame/staking/src/testing_utils.rs +++ b/substrate/frame/staking/src/testing_utils.rs @@ -28,7 +28,7 @@ use rand_chacha::{ use sp_io::hashing::blake2_256; use frame_election_provider_support::SortedListProvider; -use frame_support::{pallet_prelude::*, traits::Currency}; +use frame_support::pallet_prelude::*; use sp_runtime::{traits::StaticLookup, Perbill}; const SEED: u32 = 0; @@ -53,8 +53,8 @@ pub fn create_funded_user( balance_factor: u32, ) -> T::AccountId { let user = account(string, n, SEED); - let balance = T::Currency::minimum_balance() * balance_factor.into(); - let _ = T::Currency::make_free_balance_be(&user, balance); + let balance = asset::existential_deposit::() * balance_factor.into(); + let _ = asset::set_stakeable_balance::(&user, balance); user } @@ -65,7 +65,7 @@ pub fn create_funded_user_with_balance( balance: BalanceOf, ) -> T::AccountId { let user = account(string, n, SEED); - let _ = T::Currency::make_free_balance_be(&user, balance); + let _ = asset::set_stakeable_balance::(&user, balance); user } @@ -77,7 +77,7 @@ pub fn create_stash_controller( ) -> Result<(T::AccountId, T::AccountId), &'static str> { let staker = create_funded_user::("stash", n, balance_factor); let amount = - T::Currency::minimum_balance().max(1u64.into()) * (balance_factor / 10).max(1).into(); + asset::existential_deposit::().max(1u64.into()) * (balance_factor / 10).max(1).into(); Staking::::bond(RawOrigin::Signed(staker.clone()).into(), amount, destination)?; Ok((staker.clone(), staker)) } @@ -96,7 +96,7 @@ pub fn create_unique_stash_controller( } else { create_funded_user::("controller", n, balance_factor) }; - let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); + let amount = asset::existential_deposit::() * (balance_factor / 10).max(1).into(); Staking::::bond(RawOrigin::Signed(stash.clone()).into(), amount, destination)?; // update ledger to be a *different* controller to stash @@ -129,7 +129,7 @@ pub fn create_stash_and_dead_payee( let staker = create_funded_user::("stash", n, 0); // payee has no funds let payee = create_funded_user::("payee", n, 0); - let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); + let amount = asset::existential_deposit::() * (balance_factor / 10).max(1).into(); Staking::::bond( RawOrigin::Signed(staker.clone()).into(), amount, diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index dd178a95bec..639f4096456 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -18,7 +18,7 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; -use crate::ledger::StakingLedgerInspect; +use crate::{asset, ledger::StakingLedgerInspect}; use frame_election_provider_support::{ bounds::{DataProviderBounds, ElectionBoundsBuilder}, ElectionProvider, SortedListProvider, Support, @@ -27,7 +27,7 @@ use frame_support::{ assert_noop, assert_ok, assert_storage_noop, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, - traits::{Currency, Get, InspectLockableCurrency, ReservableCurrency}, + traits::{Currency, Get, ReservableCurrency}, }; use mock::*; @@ -229,8 +229,8 @@ fn basic_setup_works() { assert_eq!(active_era(), 0); // Account 10 has `balance_factor` free balance - assert_eq!(Balances::free_balance(10), 1); - assert_eq!(Balances::free_balance(10), 1); + assert_eq!(asset::stakeable_balance::(&10), 1); + assert_eq!(asset::stakeable_balance::(&10), 1); // New era is not being forced assert_eq!(Staking::force_era(), Forcing::NotForcing); @@ -311,9 +311,9 @@ fn change_controller_already_paired_once_stash() { #[test] fn rewards_should_work() { ExtBuilder::default().nominate(true).session_per_era(3).build_and_execute(|| { - let init_balance_11 = Balances::total_balance(&11); - let init_balance_21 = Balances::total_balance(&21); - let init_balance_101 = Balances::total_balance(&101); + let init_balance_11 = asset::total_balance::(&11); + let init_balance_21 = asset::total_balance::(&21); + let init_balance_101 = asset::total_balance::(&101); // Set payees Payee::::insert(11, RewardDestination::Account(11)); @@ -332,9 +332,9 @@ fn rewards_should_work() { start_session(1); assert_eq_uvec!(Session::validators(), vec![11, 21]); - assert_eq!(Balances::total_balance(&11), init_balance_11); - assert_eq!(Balances::total_balance(&21), init_balance_21); - assert_eq!(Balances::total_balance(&101), init_balance_101); + assert_eq!(asset::total_balance::(&11), init_balance_11); + assert_eq!(asset::total_balance::(&21), init_balance_21); + assert_eq!(asset::total_balance::(&101), init_balance_101); assert_eq!( Staking::eras_reward_points(active_era()), EraRewardPoints { @@ -363,17 +363,17 @@ fn rewards_should_work() { mock::make_all_reward_payment(0); assert_eq_error_rate!( - Balances::total_balance(&11), + asset::total_balance::(&11), init_balance_11 + part_for_11 * total_payout_0 * 2 / 3, 2, ); assert_eq_error_rate!( - Balances::total_balance(&21), + asset::total_balance::(&21), init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, 2, ); assert_eq_error_rate!( - Balances::total_balance(&101), + asset::total_balance::(&101), init_balance_101 + part_for_101_from_11 * total_payout_0 * 2 / 3 + part_for_101_from_21 * total_payout_0 * 1 / 3, @@ -402,17 +402,17 @@ fn rewards_should_work() { mock::make_all_reward_payment(1); assert_eq_error_rate!( - Balances::total_balance(&11), + asset::total_balance::(&11), init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1), 2, ); assert_eq_error_rate!( - Balances::total_balance(&21), + asset::total_balance::(&21), init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, 2, ); assert_eq_error_rate!( - Balances::total_balance(&101), + asset::total_balance::(&101), init_balance_101 + part_for_101_from_11 * (total_payout_0 * 2 / 3 + total_payout_1) + part_for_101_from_21 * total_payout_0 * 1 / 3, @@ -429,7 +429,7 @@ fn staking_should_work() { // put some money in account that we'll use. for i in 1..5 { - let _ = Balances::make_free_balance_be(&i, 2000); + let _ = asset::set_stakeable_balance::(&i, 2000); } // --- Block 2: @@ -611,7 +611,7 @@ fn nominating_and_rewards_should_work() { // give the man some money let initial_balance = 1000; for i in [1, 3, 5, 11, 21].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); + let _ = asset::set_stakeable_balance::(&i, initial_balance); } // bond two account pairs and state interest in nomination. @@ -636,12 +636,12 @@ fn nominating_and_rewards_should_work() { assert_eq_uvec!(validator_controllers(), vec![21, 11]); // old validators must have already received some rewards. - let initial_balance_41 = Balances::total_balance(&41); - let mut initial_balance_21 = Balances::total_balance(&21); + let initial_balance_41 = asset::total_balance::(&41); + let mut initial_balance_21 = asset::total_balance::(&21); mock::make_all_reward_payment(0); - assert_eq!(Balances::total_balance(&41), initial_balance_41 + total_payout_0 / 2); - assert_eq!(Balances::total_balance(&21), initial_balance_21 + total_payout_0 / 2); - initial_balance_21 = Balances::total_balance(&21); + assert_eq!(asset::total_balance::(&41), initial_balance_41 + total_payout_0 / 2); + assert_eq!(asset::total_balance::(&21), initial_balance_21 + total_payout_0 / 2); + initial_balance_21 = asset::total_balance::(&21); assert_eq!(ErasStakersPaged::::iter_prefix_values((active_era(),)).count(), 2); assert_eq!( @@ -683,30 +683,30 @@ fn nominating_and_rewards_should_work() { // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 assert_eq_error_rate!( - Balances::total_balance(&1), + asset::total_balance::(&1), initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), 2, ); // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 - assert_eq!(Balances::total_balance(&3), initial_balance); + assert_eq!(asset::total_balance::(&3), initial_balance); // 333 is the reward destination for 3. assert_eq_error_rate!( - Balances::total_balance(&333), + asset::total_balance::(&333), 2 * payout_for_11 / 9 + 3 * payout_for_21 / 11, 2 ); // Validator 11: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 assert_eq_error_rate!( - Balances::total_balance(&11), + asset::total_balance::(&11), initial_balance + 5 * payout_for_11 / 9, 2, ); // Validator 21: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = // 5/11 assert_eq_error_rate!( - Balances::total_balance(&21), + asset::total_balance::(&21), initial_balance_21 + 5 * payout_for_21 / 11, 2, ); @@ -993,7 +993,7 @@ fn cannot_transfer_staked_balance() { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); // Confirm account 11 has some free balance - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); // Confirm account 11 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); // Confirm account 11 cannot transfer as a result @@ -1003,7 +1003,7 @@ fn cannot_transfer_staked_balance() { ); // Give account 11 extra free balance - let _ = Balances::make_free_balance_be(&11, 10000); + let _ = asset::set_stakeable_balance::(&11, 10000); // Confirm that account 11 can now transfer some balance assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 21, 1)); }); @@ -1018,7 +1018,7 @@ fn cannot_transfer_staked_balance_2() { // Confirm account 21 is stashed assert_eq!(Staking::bonded(&21), Some(21)); // Confirm account 21 has some free balance - assert_eq!(Balances::free_balance(21), 2000); + assert_eq!(asset::stakeable_balance::(&21), 2000); // Confirm account 21 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), &21).total, 1000); // Confirm account 21 can transfer at most 1000 @@ -1037,14 +1037,14 @@ fn cannot_reserve_staked_balance() { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); // Confirm account 11 has some free balance - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); // Confirm account 11 (via controller 10) is totally staked assert_eq!(Staking::eras_stakers(active_era(), &11).own, 1000); // Confirm account 11 cannot reserve as a result assert_noop!(Balances::reserve(&11, 1), BalancesError::::LiquidityRestrictions); // Give account 11 extra free balance - let _ = Balances::make_free_balance_be(&11, 10000); + let _ = asset::set_stakeable_balance::(&11, 10000); // Confirm account 11 can now reserve balance assert_ok!(Balances::reserve(&11, 1)); }); @@ -1057,9 +1057,9 @@ fn reward_destination_works() { // Check that account 11 is a validator assert!(Session::validators().contains(&11)); // Check the balance of the validator account - assert_eq!(Balances::free_balance(10), 1); + assert_eq!(asset::stakeable_balance::(&10), 1); // Check the balance of the stash account - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); // Check how much is at stake assert_eq!( Staking::ledger(11.into()).unwrap(), @@ -1082,7 +1082,7 @@ fn reward_destination_works() { // Check that RewardDestination is Staked assert_eq!(Staking::payee(11.into()), Some(RewardDestination::Staked)); // Check that reward went to the stash account of validator - assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); + assert_eq!(asset::stakeable_balance::(&11), 1000 + total_payout_0); // Check that amount at stake increased accordingly assert_eq!( Staking::ledger(11.into()).unwrap(), @@ -1111,7 +1111,7 @@ fn reward_destination_works() { // Check that RewardDestination is Stash assert_eq!(Staking::payee(11.into()), Some(RewardDestination::Stash)); // Check that reward went to the stash account - assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); + assert_eq!(asset::stakeable_balance::(&11), 1000 + total_payout_0 + total_payout_1); // Record this value let recorded_stash_balance = 1000 + total_payout_0 + total_payout_1; // Check that amount at stake is NOT increased @@ -1133,7 +1133,7 @@ fn reward_destination_works() { >::insert(&11, RewardDestination::Account(11)); // Check controller balance - assert_eq!(Balances::free_balance(11), 23150); + assert_eq!(asset::stakeable_balance::(&11), 23150); // Compute total payout now for whole duration as other parameter won't change let total_payout_2 = current_total_payout_for_duration(reward_time_per_era()); @@ -1145,7 +1145,7 @@ fn reward_destination_works() { // Check that RewardDestination is Account(11) assert_eq!(Staking::payee(11.into()), Some(RewardDestination::Account(11))); // Check that reward went to the controller account - assert_eq!(Balances::free_balance(11), recorded_stash_balance + total_payout_2); + assert_eq!(asset::stakeable_balance::(&11), recorded_stash_balance + total_payout_2); // Check that amount at stake is NOT increased assert_eq!( Staking::ledger(11.into()).unwrap(), @@ -1179,8 +1179,8 @@ fn validator_payment_prefs_work() { mock::start_active_era(1); mock::make_all_reward_payment(0); - let balance_era_1_11 = Balances::total_balance(&11); - let balance_era_1_101 = Balances::total_balance(&101); + let balance_era_1_11 = asset::total_balance::(&11); + let balance_era_1_101 = asset::total_balance::(&101); // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); @@ -1194,8 +1194,16 @@ fn validator_payment_prefs_work() { let shared_cut = total_payout_1 - taken_cut; let reward_of_10 = shared_cut * exposure_1.own / exposure_1.total + taken_cut; let reward_of_100 = shared_cut * exposure_1.others[0].value / exposure_1.total; - assert_eq_error_rate!(Balances::total_balance(&11), balance_era_1_11 + reward_of_10, 2); - assert_eq_error_rate!(Balances::total_balance(&101), balance_era_1_101 + reward_of_100, 2); + assert_eq_error_rate!( + asset::total_balance::(&11), + balance_era_1_11 + reward_of_10, + 2 + ); + assert_eq_error_rate!( + asset::total_balance::(&101), + balance_era_1_101 + reward_of_100, + 2 + ); }); } @@ -1222,7 +1230,7 @@ fn bond_extra_works() { ); // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); + let _ = asset::set_stakeable_balance::(&11, 1000000); // Call the bond_extra function from controller, add only 100 assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); @@ -1284,13 +1292,13 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); + let _ = asset::set_stakeable_balance::(&11, 1000000); // Initial config should be correct assert_eq!(active_era(), 0); // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&11), 1000000); + assert_eq!(asset::total_balance::(&11), 1000000); // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -1495,7 +1503,7 @@ fn rebond_works() { assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); + let _ = asset::set_stakeable_balance::(&11, 1000000); // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -1621,7 +1629,7 @@ fn rebond_is_fifo() { assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); + let _ = asset::set_stakeable_balance::(&11, 1000000); // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -1717,7 +1725,7 @@ fn rebond_emits_right_value_in_event() { assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); // Give account 11 some large free balance greater than total - let _ = Balances::make_free_balance_be(&11, 1000000); + let _ = asset::set_stakeable_balance::(&11, 1000000); // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -1852,8 +1860,8 @@ fn reward_to_stake_works() { assert_eq!(Staking::eras_stakers(active_era(), &21).total, 2000); // Give the man some money. - let _ = Balances::make_free_balance_be(&10, 1000); - let _ = Balances::make_free_balance_be(&20, 1000); + let _ = asset::set_stakeable_balance::(&10, 1000); + let _ = asset::set_stakeable_balance::(&20, 1000); // Bypass logic and change current exposure EraInfo::::set_exposure(0, &21, Exposure { total: 69, own: 69, others: vec![] }); @@ -1880,7 +1888,7 @@ fn reward_to_stake_works() { assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); assert_eq!(Staking::eras_stakers(active_era(), &21).total, 2000); - let _11_balance = Balances::free_balance(&11); + let _11_balance = asset::stakeable_balance::(&11); assert_eq!(_11_balance, 1000 + total_payout_0 / 2); // Trigger another new era as the info are frozen before the era start. @@ -1899,7 +1907,7 @@ fn reap_stash_works() { .balance_factor(10) .build_and_execute(|| { // given - assert_eq!(Balances::balance_locked(STAKING_ID, &11), 10 * 1000); + assert_eq!(asset::staked::(&11), 10 * 1000); assert_eq!(Staking::bonded(&11), Some(11)); assert!(>::contains_key(&11)); @@ -1926,7 +1934,7 @@ fn reap_stash_works() { assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); // lock is removed. - assert_eq!(Balances::balance_locked(STAKING_ID, &11), 0); + assert_eq!(asset::staked::(&11), 0); }); } @@ -1937,7 +1945,7 @@ fn reap_stash_works_with_existential_deposit_zero() { .balance_factor(10) .build_and_execute(|| { // given - assert_eq!(Balances::balance_locked(STAKING_ID, &11), 10 * 1000); + assert_eq!(asset::staked::(&11), 10 * 1000); assert_eq!(Staking::bonded(&11), Some(11)); assert!(>::contains_key(&11)); @@ -1964,7 +1972,7 @@ fn reap_stash_works_with_existential_deposit_zero() { assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); // lock is removed. - assert_eq!(Balances::balance_locked(STAKING_ID, &11), 0); + assert_eq!(asset::staked::(&11), 0); }); } @@ -2111,8 +2119,8 @@ fn bond_with_little_staked_value_bounded() { // setup assert_ok!(Staking::chill(RuntimeOrigin::signed(31))); assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Stash)); - let init_balance_1 = Balances::free_balance(&1); - let init_balance_11 = Balances::free_balance(&11); + let init_balance_1 = asset::stakeable_balance::(&1); + let init_balance_11 = asset::stakeable_balance::(&11); // Stingy validator. assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 1, RewardDestination::Account(1))); @@ -2137,12 +2145,12 @@ fn bond_with_little_staked_value_bounded() { // Old ones are rewarded. assert_eq_error_rate!( - Balances::free_balance(11), + asset::stakeable_balance::(&11), init_balance_11 + total_payout_0 / 3, 1 ); // no rewards paid to 2. This was initial election. - assert_eq!(Balances::free_balance(1), init_balance_1); + assert_eq!(asset::stakeable_balance::(&1), init_balance_1); // reward era 2 let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); @@ -2155,12 +2163,12 @@ fn bond_with_little_staked_value_bounded() { // 2 is now rewarded. assert_eq_error_rate!( - Balances::free_balance(1), + asset::stakeable_balance::(&1), init_balance_1 + total_payout_1 / 3, 1 ); assert_eq_error_rate!( - Balances::free_balance(&11), + asset::stakeable_balance::(&11), init_balance_11 + total_payout_0 / 3 + total_payout_1 / 3, 2, ); @@ -2188,7 +2196,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { // give the man some money. let initial_balance = 1000; for i in [1, 2, 3, 4].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); + let _ = asset::set_stakeable_balance::(&i, initial_balance); } assert_ok!(Staking::bond( @@ -2241,7 +2249,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { // give the man some money. let initial_balance = 1000; for i in [1, 2, 3, 4].iter() { - let _ = Balances::make_free_balance_be(i, initial_balance); + let _ = asset::set_stakeable_balance::(&i, initial_balance); } assert_ok!(Staking::bond( @@ -2317,7 +2325,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { assert!(stake.checked_mul(reward_slash).is_none()); // Set staker - let _ = Balances::make_free_balance_be(&11, stake); + let _ = asset::set_stakeable_balance::(&11, stake); let exposure = Exposure:: { total: stake, own: stake, others: vec![] }; let reward = EraRewardPoints:: { @@ -2330,11 +2338,11 @@ fn reward_validator_slashing_validator_does_not_overflow() { EraInfo::::set_exposure(0, &11, exposure); ErasValidatorReward::::insert(0, stake); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); - assert_eq!(Balances::total_balance(&11), stake * 2); + assert_eq!(asset::total_balance::(&11), stake * 2); // Set staker - let _ = Balances::make_free_balance_be(&11, stake); - let _ = Balances::make_free_balance_be(&2, stake); + let _ = asset::set_stakeable_balance::(&11, stake); + let _ = asset::set_stakeable_balance::(&2, stake); // only slashes out of bonded stake are applied. without this line, it is 0. Staking::bond(RuntimeOrigin::signed(2), stake - 1, RewardDestination::Staked).unwrap(); @@ -2358,8 +2366,8 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(Balances::total_balance(&11), stake - 1); - assert_eq!(Balances::total_balance(&2), 1); + assert_eq!(asset::total_balance::(&11), stake - 1); + assert_eq!(asset::total_balance::(&2), 1); }) } @@ -2526,7 +2534,7 @@ fn slashing_performed_according_exposure() { ); // The stash account should be slashed for 250 (50% of 500). - assert_eq!(Balances::free_balance(11), 1000 - 250); + assert_eq!(asset::stakeable_balance::(&11), 1000 - 250); }); } @@ -2619,8 +2627,8 @@ fn reporters_receive_their_slice() { // 50% * (10% * initial_balance / 2) let reward = (initial_balance / 20) / 2; let reward_each = reward / 2; // split into two pieces. - assert_eq!(Balances::free_balance(1), 10 + reward_each); - assert_eq!(Balances::free_balance(2), 20 + reward_each); + assert_eq!(asset::stakeable_balance::(&1), 10 + reward_each); + assert_eq!(asset::stakeable_balance::(&2), 20 + reward_each); }); } @@ -2645,7 +2653,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - 0) // 50% * (10% * initial_balance * 20%) let reward = (initial_balance / 5) / 20; - assert_eq!(Balances::free_balance(1), 10 + reward); + assert_eq!(asset::stakeable_balance::(&1), 10 + reward); on_offence_now( &[OffenceDetails { @@ -2660,7 +2668,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - prior_payout) // 50% * (10% * (initial_balance / 2) - prior_payout) let reward = ((initial_balance / 20) - prior_payout) / 2; - assert_eq!(Balances::free_balance(1), 10 + prior_payout + reward); + assert_eq!(asset::stakeable_balance::(&1), 10 + prior_payout + reward); }); } @@ -2668,14 +2676,17 @@ fn subsequent_reports_in_same_span_pay_out_less() { fn invulnerables_are_not_slashed() { // For invulnerable validators no slashing is performed. ExtBuilder::default().invulnerables(vec![11]).build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(21), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&21), 2000); let exposure = Staking::eras_stakers(active_era(), &21); let initial_balance = Staking::slashable_balance_of(&21); - let nominator_balances: Vec<_> = - exposure.others.iter().map(|o| Balances::free_balance(&o.who)).collect(); + let nominator_balances: Vec<_> = exposure + .others + .iter() + .map(|o| asset::stakeable_balance::(&o.who)) + .collect(); on_offence_now( &[ @@ -2692,14 +2703,14 @@ fn invulnerables_are_not_slashed() { ); // The validator 11 hasn't been slashed, but 21 has been. - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); // 2000 - (0.2 * initial_balance) - assert_eq!(Balances::free_balance(21), 2000 - (2 * initial_balance / 10)); + assert_eq!(asset::stakeable_balance::(&21), 2000 - (2 * initial_balance / 10)); // ensure that nominators were slashed as well. for (initial_balance, other) in nominator_balances.into_iter().zip(exposure.others) { assert_eq!( - Balances::free_balance(&other.who), + asset::stakeable_balance::(&other.who), initial_balance - (2 * other.value / 10), ); } @@ -2710,7 +2721,7 @@ fn invulnerables_are_not_slashed() { fn dont_slash_if_fraction_is_zero() { // Don't slash if the fraction is zero. ExtBuilder::default().build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); on_offence_now( &[OffenceDetails { @@ -2721,7 +2732,7 @@ fn dont_slash_if_fraction_is_zero() { ); // The validator hasn't been slashed. The new era is not forced. - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); assert_eq!(Staking::force_era(), Forcing::NotForcing); }); } @@ -2731,7 +2742,7 @@ fn only_slash_for_max_in_era() { // multiple slashes within one era are only applied if it is more than any previous slash in the // same era. ExtBuilder::default().build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); on_offence_now( &[OffenceDetails { @@ -2742,7 +2753,7 @@ fn only_slash_for_max_in_era() { ); // The validator has been slashed and has been force-chilled. - assert_eq!(Balances::free_balance(11), 500); + assert_eq!(asset::stakeable_balance::(&11), 500); assert_eq!(Staking::force_era(), Forcing::NotForcing); on_offence_now( @@ -2754,7 +2765,7 @@ fn only_slash_for_max_in_era() { ); // The validator has not been slashed additionally. - assert_eq!(Balances::free_balance(11), 500); + assert_eq!(asset::stakeable_balance::(&11), 500); on_offence_now( &[OffenceDetails { @@ -2765,7 +2776,7 @@ fn only_slash_for_max_in_era() { ); // The validator got slashed 10% more. - assert_eq!(Balances::free_balance(11), 400); + assert_eq!(asset::stakeable_balance::(&11), 400); }) } @@ -2776,7 +2787,7 @@ fn garbage_collection_after_slashing() { .existential_deposit(2) .balance_factor(2) .build_and_execute(|| { - assert_eq!(Balances::free_balance(11), 2000); + assert_eq!(asset::stakeable_balance::(&11), 2000); on_offence_now( &[OffenceDetails { @@ -2786,7 +2797,7 @@ fn garbage_collection_after_slashing() { &[Perbill::from_percent(10)], ); - assert_eq!(Balances::free_balance(11), 2000 - 200); + assert_eq!(asset::stakeable_balance::(&11), 2000 - 200); assert!(SlashingSpans::::get(&11).is_some()); assert_eq!(SpanSlash::::get(&(11, 0)).amount(), &200); @@ -2801,8 +2812,8 @@ fn garbage_collection_after_slashing() { // validator and nominator slash in era are garbage-collected by era change, // so we don't test those here. - assert_eq!(Balances::free_balance(11), 2); - assert_eq!(Balances::total_balance(&11), 2); + assert_eq!(asset::stakeable_balance::(&11), 2); + assert_eq!(asset::total_balance::(&11), 2); let slashing_spans = SlashingSpans::::get(&11).unwrap(); assert_eq!(slashing_spans.iter().count(), 2); @@ -2826,11 +2837,11 @@ fn garbage_collection_on_window_pruning() { ExtBuilder::default().build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); let now = active_era(); let exposure = Staking::eras_stakers(now, &11); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( @@ -2841,8 +2852,8 @@ fn garbage_collection_on_window_pruning() { &[Perbill::from_percent(10)], ); - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + assert_eq!(asset::stakeable_balance::(&11), 900); + assert_eq!(asset::stakeable_balance::(&101), 2000 - (nominated_value / 10)); assert!(ValidatorSlashInEra::::get(&now, &11).is_some()); assert!(NominatorSlashInEra::::get(&now, &101).is_some()); @@ -2867,9 +2878,9 @@ fn slashing_nominators_by_span_max() { mock::start_active_era(2); mock::start_active_era(3); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(21), 2000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&21), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); assert_eq!(Staking::slashable_balance_of(&21), 1000); let exposure_11 = Staking::eras_stakers(active_era(), &11); @@ -2886,10 +2897,10 @@ fn slashing_nominators_by_span_max() { 2, ); - assert_eq!(Balances::free_balance(11), 900); + assert_eq!(asset::stakeable_balance::(&11), 900); let slash_1_amount = Perbill::from_percent(10) * nominated_value_11; - assert_eq!(Balances::free_balance(101), 2000 - slash_1_amount); + assert_eq!(asset::stakeable_balance::(&101), 2000 - slash_1_amount); let expected_spans = vec![ slashing::SlashingSpan { index: 1, start: 4, length: None }, @@ -2913,14 +2924,14 @@ fn slashing_nominators_by_span_max() { ); // 11 was not further slashed, but 21 and 101 were. - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(21), 1700); + assert_eq!(asset::stakeable_balance::(&11), 900); + assert_eq!(asset::stakeable_balance::(&21), 1700); let slash_2_amount = Perbill::from_percent(30) * nominated_value_21; assert!(slash_2_amount > slash_1_amount); // only the maximum slash in a single span is taken. - assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); + assert_eq!(asset::stakeable_balance::(&101), 2000 - slash_2_amount); // third slash: in same era and on same validator as first, higher // in-era value, but lower slash value than slash 2. @@ -2934,15 +2945,15 @@ fn slashing_nominators_by_span_max() { ); // 11 was further slashed, but 21 and 101 were not. - assert_eq!(Balances::free_balance(11), 800); - assert_eq!(Balances::free_balance(21), 1700); + assert_eq!(asset::stakeable_balance::(&11), 800); + assert_eq!(asset::stakeable_balance::(&21), 1700); let slash_3_amount = Perbill::from_percent(20) * nominated_value_21; assert!(slash_3_amount < slash_2_amount); assert!(slash_3_amount > slash_1_amount); // only the maximum slash in a single span is taken. - assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount); + assert_eq!(asset::stakeable_balance::(&101), 2000 - slash_2_amount); }); } @@ -2953,7 +2964,7 @@ fn slashes_are_summed_across_spans() { mock::start_active_era(2); mock::start_active_era(3); - assert_eq!(Balances::free_balance(21), 2000); + assert_eq!(asset::stakeable_balance::(&21), 2000); assert_eq!(Staking::slashable_balance_of(&21), 1000); let get_span = |account| SlashingSpans::::get(&account).unwrap(); @@ -2972,7 +2983,7 @@ fn slashes_are_summed_across_spans() { ]; assert_eq!(get_span(21).iter().collect::>(), expected_spans); - assert_eq!(Balances::free_balance(21), 1900); + assert_eq!(asset::stakeable_balance::(&21), 1900); // 21 has been force-chilled. re-signal intent to validate. Staking::validate(RuntimeOrigin::signed(21), Default::default()).unwrap(); @@ -2996,7 +3007,7 @@ fn slashes_are_summed_across_spans() { ]; assert_eq!(get_span(21).iter().collect::>(), expected_spans); - assert_eq!(Balances::free_balance(21), 1810); + assert_eq!(asset::stakeable_balance::(&21), 1810); }); } @@ -3005,10 +3016,10 @@ fn deferred_slashes_are_deferred() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); let exposure = Staking::eras_stakers(active_era(), &11); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; System::reset_events(); @@ -3024,25 +3035,25 @@ fn deferred_slashes_are_deferred() { // nominations are not removed regardless of the deferring. assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); mock::start_active_era(2); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); mock::start_active_era(3); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. mock::start_active_era(4); - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + assert_eq!(asset::stakeable_balance::(&11), 900); + assert_eq!(asset::stakeable_balance::(&101), 2000 - (nominated_value / 10)); assert!(matches!( staking_events_since_last_call().as_slice(), @@ -3140,8 +3151,8 @@ fn staker_cannot_bail_deferred_slash() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); let exposure = Staking::eras_stakers(active_era(), &11); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; @@ -3173,20 +3184,20 @@ fn staker_cannot_bail_deferred_slash() { ); // no slash yet. - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); // no slash yet. mock::start_active_era(2); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); assert_eq!(Staking::current_era().unwrap(), 2); assert_eq!(active_era(), 2); // no slash yet. mock::start_active_era(3); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); assert_eq!(Staking::current_era().unwrap(), 3); assert_eq!(active_era(), 3); @@ -3203,8 +3214,8 @@ fn staker_cannot_bail_deferred_slash() { // after being deferred for at least 2 full eras. mock::start_active_era(4); - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + assert_eq!(asset::stakeable_balance::(&11), 900); + assert_eq!(asset::stakeable_balance::(&101), 2000 - (nominated_value / 10)); // and the leftover of the funds can now be unbonded. }) @@ -3215,10 +3226,10 @@ fn remove_deferred() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); let exposure = Staking::eras_stakers(active_era(), &11); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; // deferred to start of era 4. @@ -3227,8 +3238,8 @@ fn remove_deferred() { &[Perbill::from_percent(10)], ); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); mock::start_active_era(2); @@ -3249,13 +3260,13 @@ fn remove_deferred() { // cancel one of them. assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0])); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); mock::start_active_era(3); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. @@ -3280,8 +3291,8 @@ fn remove_deferred() { let actual_slash = total_slash - initial_slash; // 5% slash (15 - 10) processed now. - assert_eq!(Balances::free_balance(11), 950); - assert_eq!(Balances::free_balance(101), 2000 - actual_slash); + assert_eq!(asset::stakeable_balance::(&11), 950); + assert_eq!(asset::stakeable_balance::(&101), 2000 - actual_slash); }) } @@ -3290,10 +3301,10 @@ fn remove_multi_deferred() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); let exposure = Staking::eras_stakers(active_era(), &11); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); on_offence_now( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], @@ -3363,8 +3374,8 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]); // pre-slash balance - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); // 100 has approval for 11 as of now assert!(Staking::nominators(101).unwrap().targets.contains(&11)); @@ -3398,8 +3409,8 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid // post-slash balance let nominator_slash_amount_11 = 125 / 10; - assert_eq!(Balances::free_balance(11), 900); - assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); + assert_eq!(asset::stakeable_balance::(&11), 900); + assert_eq!(asset::stakeable_balance::(&101), 2000 - nominator_slash_amount_11); // check that validator was disabled. assert!(is_disabled(11)); @@ -3663,8 +3674,8 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // Consumed weight for all payout_stakers dispatches that fail let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); - let init_balance_11 = Balances::total_balance(&11); - let init_balance_101 = Balances::total_balance(&101); + let init_balance_11 = asset::total_balance::(&11); + let init_balance_101 = asset::total_balance::(&101); let part_for_11 = Perbill::from_rational::(1000, 1125); let part_for_101 = Perbill::from_rational::(125, 1125); @@ -3728,11 +3739,11 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // only era 1 and 2 can be rewarded. assert_eq!( - Balances::total_balance(&11), + asset::total_balance::(&11), init_balance_11 + part_for_11 * (total_payout_1 + total_payout_2), ); assert_eq!( - Balances::total_balance(&101), + asset::total_balance::(&101), init_balance_101 + part_for_101 * (total_payout_1 + total_payout_2), ); }); @@ -3749,18 +3760,18 @@ fn zero_slash_keeps_nominators() { .build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(asset::stakeable_balance::(&11), 1000); let exposure = Staking::eras_stakers(active_era(), &11); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&101), 2000); on_offence_now( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(0)], ); - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::stakeable_balance::(&101), 2000); // 11 is not removed but disabled assert!(Validators::::iter().any(|(stash, _)| stash == 11)); @@ -3837,7 +3848,7 @@ fn test_nominators_over_max_exposure_page_size_are_rewarded() { for i in 0..=MaxExposurePageSize::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; - Balances::make_free_balance_be(&stash, balance); + asset::set_stakeable_balance::(&stash, balance); assert_ok!(Staking::bond( RuntimeOrigin::signed(stash), balance, @@ -3859,13 +3870,13 @@ fn test_nominators_over_max_exposure_page_size_are_rewarded() { while i < MaxExposurePageSize::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; - assert!(Balances::free_balance(&stash) > balance); + assert!(asset::stakeable_balance::(&stash) > balance); i += 1; } // Assert overflowing nominators from page 1 are also rewarded let stash = 10_000 + i as AccountId; - assert!(Balances::free_balance(&stash) > (10_000 + i) as Balance); + assert!(asset::stakeable_balance::(&stash) > (10_000 + i) as Balance); }); } @@ -3878,7 +3889,7 @@ fn test_nominators_are_rewarded_for_all_exposure_page() { for i in 0..nominator_count { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; - Balances::make_free_balance_be(&stash, balance); + asset::set_stakeable_balance::(&stash, balance); assert_ok!(Staking::bond( RuntimeOrigin::signed(stash), balance, @@ -3900,9 +3911,10 @@ fn test_nominators_are_rewarded_for_all_exposure_page() { // Assert all nominators are rewarded according to their stake for i in 0..nominator_count { // balance of the nominator after the reward payout. - let current_balance = Balances::free_balance(&((10000 + i) as AccountId)); + let current_balance = asset::stakeable_balance::(&((10000 + i) as AccountId)); // balance of the nominator in the previous iteration. - let previous_balance = Balances::free_balance(&((10000 + i - 1) as AccountId)); + let previous_balance = + asset::stakeable_balance::(&((10000 + i - 1) as AccountId)); // balance before the reward. let original_balance = 10_000 + i as Balance; @@ -3958,7 +3970,7 @@ fn test_multi_page_payout_stakers_by_page() { RewardOnUnbalanceWasCalled::set(false); System::reset_events(); - let controller_balance_before_p0_payout = Balances::free_balance(&11); + let controller_balance_before_p0_payout = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0)); @@ -3972,7 +3984,7 @@ fn test_multi_page_payout_stakers_by_page() { ] )); - let controller_balance_after_p0_payout = Balances::free_balance(&11); + let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left assert!(pallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); @@ -3996,7 +4008,7 @@ fn test_multi_page_payout_stakers_by_page() { ] )); // verify the validator was not rewarded the second time - assert_eq!(Balances::free_balance(&11), controller_balance_after_p0_payout); + assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); // verify all rewards have been paid out assert_eq_error_rate!( @@ -4007,9 +4019,9 @@ fn test_multi_page_payout_stakers_by_page() { assert!(RewardOnUnbalanceWasCalled::get()); // Top 64 nominators of validator 11 automatically paid out, including the validator - assert!(Balances::free_balance(&11) > balance); + assert!(asset::stakeable_balance::(&11) > balance); for i in 0..100 { - assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance); + assert!(asset::stakeable_balance::(&(1000 + i)) > balance + i as Balance); } // verify we no longer track rewards in `legacy_claimed_rewards` vec @@ -4178,7 +4190,7 @@ fn test_multi_page_payout_stakers_backward_compatible() { let pre_payout_total_issuance = pallet_balances::TotalIssuance::::get(); RewardOnUnbalanceWasCalled::set(false); - let controller_balance_before_p0_payout = Balances::free_balance(&11); + let controller_balance_before_p0_payout = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); // page 0 is claimed @@ -4187,7 +4199,7 @@ fn test_multi_page_payout_stakers_backward_compatible() { Error::::AlreadyClaimed.with_weight(err_weight) ); - let controller_balance_after_p0_payout = Balances::free_balance(&11); + let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left assert!(pallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); @@ -4206,7 +4218,7 @@ fn test_multi_page_payout_stakers_backward_compatible() { ); // verify the validator was not rewarded the second time - assert_eq!(Balances::free_balance(&11), controller_balance_after_p0_payout); + assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); // verify all rewards have been paid out assert_eq_error_rate!( @@ -4218,9 +4230,9 @@ fn test_multi_page_payout_stakers_backward_compatible() { // verify all nominators of validator 11 are paid out, including the validator // Validator payout goes to controller. - assert!(Balances::free_balance(&11) > balance); + assert!(asset::stakeable_balance::(&11) > balance); for i in 0..100 { - assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance); + assert!(asset::stakeable_balance::(&(1000 + i)) > balance + i as Balance); } // verify we no longer track rewards in `legacy_claimed_rewards` vec @@ -4578,25 +4590,29 @@ fn test_commission_paid_across_pages() { let payout = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(2); - let initial_balance = Balances::free_balance(&11); + let initial_balance = asset::stakeable_balance::(&11); // Payout rewards for first exposure page assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0)); - let controller_balance_after_p0_payout = Balances::free_balance(&11); + let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // some commission is paid assert!(initial_balance < controller_balance_after_p0_payout); // payout all pages for i in 1..4 { - let before_balance = Balances::free_balance(&11); + let before_balance = asset::stakeable_balance::(&11); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, i)); - let after_balance = Balances::free_balance(&11); + let after_balance = asset::stakeable_balance::(&11); // some commission is paid for every page assert!(before_balance < after_balance); } - assert_eq_error_rate!(Balances::free_balance(&11), initial_balance + payout / 2, 1,); + assert_eq_error_rate!( + asset::stakeable_balance::(&11), + initial_balance + payout / 2, + 1, + ); }); } @@ -4852,7 +4868,7 @@ fn payout_to_any_account_works() { assert_ok!(Staking::set_payee(RuntimeOrigin::signed(1234), RewardDestination::Account(42))); // Reward Destination account doesn't exist - assert_eq!(Balances::free_balance(42), 0); + assert_eq!(asset::stakeable_balance::(&42), 0); mock::start_active_era(1); Staking::reward_by_ids(vec![(11, 1)]); @@ -4862,7 +4878,7 @@ fn payout_to_any_account_works() { assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0)); // Payment is successful - assert!(Balances::free_balance(42) > 0); + assert!(asset::stakeable_balance::(&42) > 0); }) } @@ -5661,9 +5677,9 @@ fn chill_other_works() { let a = 4 * i; let b = 4 * i + 2; let c = 4 * i + 3; - Balances::make_free_balance_be(&a, 100_000); - Balances::make_free_balance_be(&b, 100_000); - Balances::make_free_balance_be(&c, 100_000); + asset::set_stakeable_balance::(&a, 100_000); + asset::set_stakeable_balance::(&b, 100_000); + asset::set_stakeable_balance::(&c, 100_000); // Nominator assert_ok!(Staking::bond(RuntimeOrigin::signed(a), 1000, RewardDestination::Stash)); @@ -6859,7 +6875,7 @@ fn test_runtime_api_pending_rewards() { // Set staker for v in validator_one..=validator_three { - let _ = Balances::make_free_balance_be(&v, stake); + let _ = asset::set_stakeable_balance::(&v, stake); assert_ok!(Staking::bond(RuntimeOrigin::signed(v), stake, RewardDestination::Staked)); } @@ -7049,7 +7065,7 @@ mod staking_interface { assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); // lock is removed. - assert_eq!(Balances::balance_locked(STAKING_ID, &11), 0); + assert_eq!(asset::staked::(&11), 0); }); } @@ -7086,12 +7102,12 @@ mod staking_unchecked { fn virtual_bond_does_not_lock() { ExtBuilder::default().build_and_execute(|| { mock::start_active_era(1); - assert_eq!(Balances::free_balance(10), 1); + assert_eq!(asset::stakeable_balance::(&10), 1); // 10 can bond more than its balance amount since we do not require lock for virtual // bonding. assert_ok!(::virtual_bond(&10, 100, &15)); // nothing is locked on 10. - assert_eq!(Balances::balance_locked(STAKING_ID, &10), 0); + assert_eq!(asset::staked::(&10), 0); // adding more balance does not lock anything as well. assert_ok!(::bond_extra(&10, 1000)); // but ledger is updated correctly. @@ -7118,7 +7134,7 @@ mod staking_unchecked { Ok(Stake { total: 1100, active: 900 }) ); // still no locks. - assert_eq!(Balances::balance_locked(STAKING_ID, &10), 0); + assert_eq!(asset::staked::(&10), 0); mock::start_active_era(2); // cannot withdraw without waiting for unbonding period. @@ -7218,11 +7234,11 @@ mod staking_unchecked { fn migrate_virtual_staker() { ExtBuilder::default().build_and_execute(|| { // give some balance to 200 - Balances::make_free_balance_be(&200, 2000); + asset::set_stakeable_balance::(&200, 2000); // stake assert_ok!(Staking::bond(RuntimeOrigin::signed(200), 1000, RewardDestination::Staked)); - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &200), 1000); + assert_eq!(asset::staked::(&200), 1000); // migrate them to virtual staker ::migrate_to_virtual_staker(&200); @@ -7230,7 +7246,7 @@ mod staking_unchecked { assert_ok!(::set_payee(&200, &201)); // ensure the balance is not locked anymore - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &200), 0); + assert_eq!(asset::staked::(&200), 0); // and they are marked as virtual stakers assert_eq!(Pallet::::is_virtual_staker(&200), true); @@ -7300,7 +7316,7 @@ mod staking_unchecked { assert!(is_disabled(11)); // but virtual nominator's balance is not slashed. - assert_eq!(Balances::free_balance(&101), nominator_balance); + assert_eq!(asset::stakeable_balance::(&101), nominator_balance); // but slash is broadcasted to slash observers. assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_share); }) @@ -7332,9 +7348,9 @@ mod staking_unchecked { assert_ok!(::set_payee(&101, &102)); // cache values - let validator_balance = Balances::free_balance(&11); + let validator_balance = asset::stakeable_balance::(&11); let validator_stake = Staking::ledger(11.into()).unwrap().total; - let nominator_balance = Balances::free_balance(&101); + let nominator_balance = asset::stakeable_balance::(&101); let nominator_stake = Staking::ledger(101.into()).unwrap().total; // 11 goes offline @@ -7353,14 +7369,14 @@ mod staking_unchecked { // all validator stake is slashed assert_eq_error_rate!( validator_balance - validator_stake, - Balances::free_balance(&11), + asset::stakeable_balance::(&11), 1 ); // Because slashing happened. assert!(is_disabled(11)); // Virtual nominator's balance is not slashed. - assert_eq!(Balances::free_balance(&101), nominator_balance); + assert_eq!(asset::stakeable_balance::(&101), nominator_balance); // Slash is broadcasted to slash observers. assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_stake); @@ -7900,7 +7916,7 @@ mod ledger_recovery { ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| { setup_double_bonded_ledgers(); - let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333); + let lock_333_before = asset::staked::(&333); // get into corrupted and killed ledger state by killing a corrupted ledger: // init state: @@ -7936,14 +7952,14 @@ mod ledger_recovery { // side effects on 333 - ledger, bonded, payee, lock should be completely empty. // however, 333 lock remains. - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // NOK + assert_eq!(asset::staked::(&333), lock_333_before); // NOK assert!(Bonded::::get(&333).is_none()); // OK assert!(Payee::::get(&333).is_none()); // OK assert!(Ledger::::get(&444).is_none()); // OK // side effects on 444 - ledger, bonded, payee, lock should remain be intact. // however, 444 lock was removed. - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), 0); // NOK + assert_eq!(asset::staked::(&444), 0); // NOK assert!(Bonded::::get(&444).is_some()); // OK assert!(Payee::::get(&444).is_some()); // OK assert!(Ledger::::get(&555).is_none()); // NOK @@ -7957,7 +7973,7 @@ mod ledger_recovery { ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| { setup_double_bonded_ledgers(); - let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333); + let lock_333_before = asset::staked::(&333); // get into corrupted and killed ledger state by killing a corrupted ledger: // init state: @@ -7992,14 +8008,14 @@ mod ledger_recovery { assert_eq!(Staking::inspect_bond_state(&444), Err(Error::::NotStash)); // side effects on 333 - ledger, bonded, payee, lock should be intact. - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // OK + assert_eq!(asset::staked::(&333), lock_333_before); // OK assert_eq!(Bonded::::get(&333), Some(444)); // OK assert!(Payee::::get(&333).is_some()); // OK // however, ledger associated with its controller was killed. assert!(Ledger::::get(&444).is_none()); // NOK // side effects on 444 - ledger, bonded, payee, lock should be completely removed. - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), 0); // OK + assert_eq!(asset::staked::(&444), 0); // OK assert!(Bonded::::get(&444).is_none()); // OK assert!(Payee::::get(&444).is_none()); // OK assert!(Ledger::::get(&555).is_none()); // OK @@ -8080,7 +8096,7 @@ mod ledger_recovery { setup_double_bonded_ledgers(); // ledger.total == lock - let total_444_before_corruption = Balances::balance_locked(crate::STAKING_ID, &444); + let total_444_before_corruption = asset::staked::(&444); // get into corrupted and killed ledger state by killing a corrupted ledger: // init state: @@ -8182,8 +8198,8 @@ mod ledger_recovery { ExtBuilder::default().has_stakers(true).build_and_execute(|| { setup_double_bonded_ledgers(); - let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333); - let lock_444_before = Balances::balance_locked(crate::STAKING_ID, &444); + let lock_333_before = asset::staked::(&333); + let lock_444_before = asset::staked::(&444); // get into corrupted and killed ledger state by killing a corrupted ledger: // init state: @@ -8203,16 +8219,13 @@ mod ledger_recovery { // if 444 bonds extra, the locks remain in sync. bond_extra_no_checks(&444, 40); - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), lock_444_before + 40); + assert_eq!(asset::staked::(&333), lock_333_before); + assert_eq!(asset::staked::(&444), lock_444_before + 40); // however if 333 bonds extra, the wrong lock is updated. bond_extra_no_checks(&333, 30); - assert_eq!( - Balances::balance_locked(crate::STAKING_ID, &333), - lock_444_before + 40 + 30 - ); //not OK - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), lock_444_before + 40); // OK + assert_eq!(asset::staked::(&333), lock_444_before + 40 + 30); //not OK + assert_eq!(asset::staked::(&444), lock_444_before + 40); // OK // recover the ledger bonded by 333 stash. Note that the total/lock needs to be // re-written since on-chain data lock has become out of sync. @@ -8247,9 +8260,9 @@ mod ledger_recovery { let ledger_444 = Bonded::::get(&444).and_then(Ledger::::get).unwrap(); assert_eq!(ledger_333.total, lock_333_before + 30); - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), ledger_333.total); + assert_eq!(asset::staked::(&333), ledger_333.total); assert_eq!(ledger_444.total, lock_444_before + 40); - assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), ledger_444.total); + assert_eq!(asset::staked::(&444), ledger_444.total); // try-state checks are ok now. assert_ok!(Staking::do_try_state(System::block_number())); diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 5e94524816a..17010a8907f 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -619,7 +619,7 @@ pub trait DelegationMigrator { /// /// Also removed from [`StakingUnchecked`] as a Virtual Staker. Useful for testing. #[cfg(feature = "runtime-benchmarks")] - fn migrate_to_direct_staker(agent: Agent); + fn force_kill_agent(agent: Agent); } sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); -- GitLab From 9a37238295392a4a367fb45c0ca3d7f73ab32080 Mon Sep 17 00:00:00 2001 From: davidk-pt Date: Tue, 8 Oct 2024 12:24:40 +0300 Subject: [PATCH 068/127] Add migration to clear unapproved proposals from treasury pallet (#5892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves https://github.com/polkadot-fellows/runtimes/issues/459 Tested with rococo/westend/kusama/polkadot runtimes --------- Co-authored-by: DavidK Co-authored-by: Muharem Co-authored-by: Shawn Tabrizi Co-authored-by: Dónal Murray --- Cargo.lock | 1 + polkadot/runtime/rococo/src/lib.rs | 4 + prdoc/pr_5892.prdoc | 17 +++ substrate/frame/treasury/Cargo.toml | 2 + substrate/frame/treasury/src/lib.rs | 1 + substrate/frame/treasury/src/migration.rs | 133 ++++++++++++++++++++++ 6 files changed, 158 insertions(+) create mode 100644 prdoc/pr_5892.prdoc create mode 100644 substrate/frame/treasury/src/migration.rs diff --git a/Cargo.lock b/Cargo.lock index e89b62af01b..478e6df4977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12830,6 +12830,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", + "log", "pallet-balances", "pallet-utility", "parity-scale-codec", diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index ab6563525ff..1b5f7b5157d 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -33,6 +33,7 @@ use frame_support::{ dynamic_params::{dynamic_pallet_params, dynamic_params}, traits::FromContains, }; +use pallet_balances::WeightInfo; use pallet_nis::WithMaximumOf; use polkadot_primitives::{ slashing, @@ -1601,6 +1602,8 @@ pub mod migrations { pub const TechnicalMembershipPalletName: &'static str = "TechnicalMembership"; pub const TipsPalletName: &'static str = "Tips"; pub const PhragmenElectionPalletId: LockIdentifier = *b"phrelect"; + /// Weight for balance unreservations + pub BalanceUnreserveWeight: Weight = weights::pallet_balances_balances::WeightInfo::::force_unreserve(); } // Special Config for Gov V1 pallets, allowing us to run migrations for them without @@ -1656,6 +1659,7 @@ pub mod migrations { pallet_elections_phragmen::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, pallet_democracy::migrations::unlock_and_unreserve_all_funds::UnlockAndUnreserveAllFunds, pallet_tips::migrations::unreserve_deposits::UnreserveDeposits, + pallet_treasury::migration::cleanup_proposals::Migration, // Delete all Gov v1 pallet storage key/values. diff --git a/prdoc/pr_5892.prdoc b/prdoc/pr_5892.prdoc new file mode 100644 index 00000000000..b909e443328 --- /dev/null +++ b/prdoc/pr_5892.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: "Treasury: add migration to clean up unapproved deprecated proposals" + +doc: + - audience: Runtime Dev + description: | + It is no longer possible to create `Proposals` storage item in `pallet-treasury` due to migration from + governance v1 model but there are some `Proposals` whose bonds are still on hold with no way to release them. + The purpose of this migration is to clear `Proposals` which are stuck and return bonds to the proposers. + +crates: + - name: pallet-treasury + bump: patch + - name: rococo-runtime + bump: patch diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 55bdd4f7a49..93a3d9bea93 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -30,6 +30,7 @@ frame-system = { workspace = true } pallet-balances = { workspace = true } sp-runtime = { workspace = true } sp-core = { optional = true, workspace = true } +log = { workspace = true } [dev-dependencies] sp-io = { workspace = true, default-features = true } @@ -43,6 +44,7 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-balances/std", "pallet-utility/std", "scale-info/std", diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs index edb39f23064..ad74495ce09 100644 --- a/substrate/frame/treasury/src/lib.rs +++ b/substrate/frame/treasury/src/lib.rs @@ -73,6 +73,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod migration; #[cfg(test)] mod tests; pub mod weights; diff --git a/substrate/frame/treasury/src/migration.rs b/substrate/frame/treasury/src/migration.rs new file mode 100644 index 00000000000..c0de4ce4310 --- /dev/null +++ b/substrate/frame/treasury/src/migration.rs @@ -0,0 +1,133 @@ +// 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. + +//! Treasury pallet migrations. + +use super::*; +use alloc::collections::BTreeSet; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +use core::marker::PhantomData; +use frame_support::{defensive, traits::OnRuntimeUpgrade}; + +/// The log target for this pallet. +const LOG_TARGET: &str = "runtime::treasury"; + +pub mod cleanup_proposals { + use super::*; + + /// Migration to cleanup unapproved proposals to return the bonds back to the proposers. + /// Proposals can no longer be created and the `Proposal` storage item will be removed in the + /// future. + /// + /// `UnreserveWeight` returns `Weight` of `unreserve_balance` operation which is perfomed during + /// this migration. + pub struct Migration(PhantomData<(T, I, UnreserveWeight)>); + + impl, I: 'static, UnreserveWeight: Get> OnRuntimeUpgrade + for Migration + { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let mut approval_index = BTreeSet::new(); + for approval in Approvals::::get().iter() { + approval_index.insert(*approval); + } + + let mut proposals_processed = 0; + for (proposal_index, p) in Proposals::::iter() { + if !approval_index.contains(&proposal_index) { + let err_amount = T::Currency::unreserve(&p.proposer, p.bond); + if err_amount.is_zero() { + Proposals::::remove(proposal_index); + log::info!( + target: LOG_TARGET, + "Released bond amount of {:?} to proposer {:?}", + p.bond, + p.proposer, + ); + } else { + defensive!( + "err_amount is non zero for proposal {:?}", + (proposal_index, err_amount) + ); + Proposals::::mutate_extant(proposal_index, |proposal| { + proposal.value = err_amount; + }); + log::info!( + target: LOG_TARGET, + "Released partial bond amount of {:?} to proposer {:?}", + p.bond - err_amount, + p.proposer, + ); + } + proposals_processed += 1; + } + } + + log::info!( + target: LOG_TARGET, + "Migration for pallet-treasury finished, released {} proposal bonds.", + proposals_processed, + ); + + // calculate and return migration weights + let approvals_read = 1; + T::DbWeight::get().reads_writes( + proposals_processed as u64 + approvals_read, + proposals_processed as u64, + ) + UnreserveWeight::get() * proposals_processed + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let value = ( + Proposals::::iter_values().count() as u32, + Approvals::::get().len() as u32, + ); + log::info!( + target: LOG_TARGET, + "Proposals and Approvals count {:?}", + value, + ); + Ok(value.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let (old_proposals_count, old_approvals_count) = + <(u32, u32)>::decode(&mut &state[..]).expect("Known good"); + let new_proposals_count = Proposals::::iter_values().count() as u32; + let new_approvals_count = Approvals::::get().len() as u32; + + log::info!( + target: LOG_TARGET, + "Proposals and Approvals count {:?}", + (new_proposals_count, new_approvals_count), + ); + + ensure!( + new_proposals_count <= old_proposals_count, + "Proposals after migration should be less or equal to old proposals" + ); + ensure!( + new_approvals_count == old_approvals_count, + "Approvals after migration should remain the same" + ); + Ok(()) + } + } +} -- GitLab From 97a6ea53a0154a87d2ceb4ffc4f4dfe7fa8db95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 8 Oct 2024 11:56:23 +0200 Subject: [PATCH 069/127] SolochainDefaultConfig: Use correct `AccountData` (#5941) `AccountData` should be set to `()` and not to `AccountInfo`. Closes: https://github.com/paritytech/polkadot-sdk/issues/5922 --------- Co-authored-by: Shawn Tabrizi --- prdoc/pr_5941.prdoc | 16 ++++++++++++++++ substrate/frame/system/src/lib.rs | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_5941.prdoc diff --git a/prdoc/pr_5941.prdoc b/prdoc/pr_5941.prdoc new file mode 100644 index 00000000000..4e88400f4ef --- /dev/null +++ b/prdoc/pr_5941.prdoc @@ -0,0 +1,16 @@ +title: "`SolochainDefaultConfig`: Use correct `AccountData`" + +doc: + - audience: Runtime Dev + description: | + `SolochainDefaultConfig` by default was setting `AccountData` to `AccountInfo`. + Thus, the actual account data was recursively nested the same type. By default + it should be set `()`, because this is the only reasonable `AccountData`. + + If you have used `SolochainDefaultConfig` before and did not overwrite, `AccountData` + you should now overwrite it to `AccountInfo` or you will need to write a migration to + change the data. + +crates: + - name: frame-system + bump: patch diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 662b7f1a94b..a5c5f1ed2e4 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -364,7 +364,7 @@ pub mod pallet { type MaxConsumers = frame_support::traits::ConstU32<128>; /// The default data to be stored in an account. - type AccountData = crate::AccountInfo; + type AccountData = (); /// What to do if a new account is created. type OnNewAccount = (); -- GitLab From 4b40e762080b3b05a88988eb2d6dca0d86b166c8 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:02:47 +0100 Subject: [PATCH 070/127] improve prdoc generation (#5931) Related to https://github.com/paritytech/polkadot-sdk/pull/5924#issuecomment-2393558697 improve prdoc arguments validation & help: - convert audiences options to snake_case. Fixes https://github.com/paritytech/polkadot-sdk/issues/5927 - support more than one audiences - define allowed bump options - infer --pr from the actual PR (now it's optional, can still be overwritten) ![image](https://github.com/user-attachments/assets/24e18fe2-2f67-4ce0-90e4-34f6c2f860c9) Test evidence: https://github.com/paritytech-stg/polkadot-sdk/pull/52/commits/6dd274e3678d287c163cfa6cb696acee9852767d --- .github/scripts/cmd/cmd.py | 2 +- .github/scripts/generate-prdoc.py | 28 +++++++++++++++++----------- .github/workflows/cmd.yml | 28 +++++++++++++++++++++------- .github/workflows/command-prdoc.yml | 19 ++++++++----------- docs/contributor/commands-readme.md | 1 + 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index b5835f1f308..b421ab753f7 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -77,7 +77,7 @@ generate_prdoc = importlib.util.module_from_spec(spec) spec.loader.exec_module(generate_prdoc) parser_prdoc = subparsers.add_parser('prdoc', help='Generates PR documentation') -generate_prdoc.setup_parser(parser_prdoc) +generate_prdoc.setup_parser(parser_prdoc, pr_required=False) def main(): global args, unknown, runtimesMatrix diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index f05d517a580..edcdb82cd22 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -7,7 +7,7 @@ It downloads and parses the patch from the GitHub API to opulate the prdoc with This will delete any prdoc that already exists for the PR if `--force` is passed. Usage: - python generate-prdoc.py --pr 1234 --audience "TODO" --bump "TODO" + python generate-prdoc.py --pr 1234 --audience node_dev --bump patch """ import argparse @@ -110,27 +110,33 @@ def setup_yaml(): yaml.add_representer(str, yaml_multiline_string_presenter) # parse_args is also used by cmd/cmd.py -def setup_parser(parser=None): +# if pr_required is False, then --pr is optional, as it can be derived from the PR comment body +def setup_parser(parser=None, pr_required=True): + allowed_audiences = ["runtime_dev", "runtime_user", "node_dev", "node_operator"] if parser is None: parser = argparse.ArgumentParser() - parser.add_argument("--pr", type=int, required=True, help="The PR number to generate the PrDoc for." ) - parser.add_argument("--audience", type=str, default="TODO", help="The audience of whom the changes may concern.") - parser.add_argument("--bump", type=str, default="TODO", help="A default bump level for all crates.") - parser.add_argument("--force", type=str, help="Whether to overwrite any existing PrDoc.") - + parser.add_argument("--pr", type=int, required=pr_required, help="The PR number to generate the PrDoc for.") + parser.add_argument("--audience", type=str, nargs='*', choices=allowed_audiences, default=["todo"], help="The audience of whom the changes may concern. Example: --audience runtime_dev node_dev") + parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "no_change"], help="A default bump level for all crates. Example: --bump patch") + parser.add_argument("--force", action="store_true", help="Whether to overwrite any existing PrDoc.") return parser +def snake_to_title(s): + return ' '.join(word.capitalize() for word in s.split('_')) + def main(args): - force = True if (args.force or "false").lower() == "true" else False - print(f"Args: {args}, force: {force}") + print(f"Args: {args}, force: {args.force}") setup_yaml() try: - from_pr_number(args.pr, args.audience, args.bump, force) + # Convert snake_case audience arguments to title case + mapped_audiences = [snake_to_title(a) for a in args.audience] + from_pr_number(args.pr, mapped_audiences, args.bump, args.force) return 0 except Exception as e: print(f"Error generating prdoc: {e}") return 1 if __name__ == "__main__": - args = setup_parser().parse_args() + parser = setup_parser() + args = parser.parse_args() main(args) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 0c9c48a123b..f8bc7cb5b60 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -152,15 +152,15 @@ jobs: id: get-pr-comment with: text: ${{ github.event.comment.body }} - regex: '^(\/cmd )([-\/\s\w.=:]+)$' # see explanation in docs/contributor/commands-readme.md#examples + regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples - name: Save output of help id: help env: CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command run: | - echo 'help<> $GITHUB_OUTPUT python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt + echo 'help<> $GITHUB_OUTPUT python3 .github/scripts/cmd/cmd.py $CMD >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT @@ -291,7 +291,18 @@ jobs: id: get-pr-comment with: text: ${{ github.event.comment.body }} - regex: '^(\/cmd )([-\/\s\w.=:]+)$' # see explanation in docs/contributor/commands-readme.md#examples + regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples + + # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically + - name: Prepare PR Number argument + id: pr-arg + run: | + CMD="${{ steps.get-pr-comment.outputs.group2 }}" + if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then + echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + else + echo "arg=" >> $GITHUB_OUTPUT + fi - name: Build workflow link if: ${{ !contains(github.event.comment.body, '--quiet') }} @@ -314,7 +325,8 @@ jobs: echo "run_url=$runLink" >> $GITHUB_OUTPUT - name: Comment PR (Start) - if: ${{ !contains(github.event.comment.body, '--quiet') }} + # No need to comment on prdoc start or if --quiet + if: ${{ !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -344,14 +356,15 @@ jobs: id: cmd env: CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + PR_ARG: ${{ steps.pr-arg.outputs.arg }} run: | - echo "Running command: '$CMD' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'" + echo "Running command: '$CMD $PR_ARG' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'" echo "RUST_NIGHTLY_VERSION: $RUST_NIGHTLY_VERSION" # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' git remote -v python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt - python3 .github/scripts/cmd/cmd.py $CMD + python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG git status git diff @@ -395,7 +408,8 @@ jobs: } >> $GITHUB_OUTPUT - name: Comment PR (End) - if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') }} + # No need to comment on prdoc success or --quiet + if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} uses: actions/github-script@v7 env: SUBWEIGHT: "${{ steps.subweight.outputs.result }}" diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index 2154c8a987f..7022e8e0e00 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -14,7 +14,7 @@ on: required: true options: - "TODO" - - "no change" + - "no_change" - "patch" - "minor" - "major" @@ -25,18 +25,15 @@ on: required: true options: - "TODO" - - "Runtime Dev" - - "Runtime User" - - "Node Dev" - - "Node Operator" + - "runtime_dev" + - "runtime_user" + - "node_dev" + - "node_operator" overwrite: - type: choice + type: boolean description: Overwrite existing PrDoc - default: "true" + default: true required: true - options: - - "true" - - "false" concurrency: group: command-prdoc @@ -81,4 +78,4 @@ jobs: with: commit_message: Add PrDoc (auto generated) branch: ${{ steps.gh.outputs.branch }} - file_pattern: 'prdoc/*.prdoc' + file_pattern: "prdoc/*.prdoc" diff --git a/docs/contributor/commands-readme.md b/docs/contributor/commands-readme.md index 861c3ac784d..3a0fadc3bb2 100644 --- a/docs/contributor/commands-readme.md +++ b/docs/contributor/commands-readme.md @@ -44,4 +44,5 @@ the default branch. The regex in cmd.yml is: `^(\/cmd )([-\/\s\w.=:]+)$` accepts only alphanumeric, space, "-", "/", "=", ":", "." chars. `/cmd bench --runtime bridge-hub-westend --pallet=pallet_name` +`/cmd prdoc --audience runtime_dev runtime_user --bump patch --force` `/cmd update-ui --image=docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v202407161507 --clean` -- GitLab From a4dce869cb1c80a5c02b4355cdcbf75ead654556 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 8 Oct 2024 15:13:02 +0300 Subject: [PATCH 071/127] Omni-Node renamings (#5915) - moved the omni-node lib from `cumulus/polkadot-parachain/polkadot-parachain-lib` to `cumulus/polkadot-omni-node/lib` - renamed `polkadot-parachain-lib` to `polkadot-omni-node-lib` - added `polkadot-omni-node` binary Related to https://github.com/paritytech/polkadot-sdk/issues/5566 --- Cargo.lock | 123 +++++++++--------- Cargo.toml | 7 +- cumulus/polkadot-omni-node/Cargo.toml | 29 +++++ cumulus/polkadot-omni-node/build.rs | 22 ++++ .../lib}/Cargo.toml | 2 +- .../lib}/src/cli.rs | 0 .../lib}/src/command.rs | 0 .../lib}/src/common/aura.rs | 0 .../lib}/src/common/chain_spec.rs | 0 .../lib}/src/common/command.rs | 0 .../lib}/src/common/mod.rs | 0 .../lib}/src/common/rpc.rs | 0 .../lib}/src/common/runtime.rs | 0 .../lib}/src/common/spec.rs | 0 .../lib}/src/common/types.rs | 0 .../lib}/src/fake_runtime_api/mod.rs | 0 .../lib}/src/fake_runtime_api/utils.rs | 0 .../lib}/src/lib.rs | 0 .../lib}/src/nodes/aura.rs | 0 .../lib}/src/nodes/manual_seal.rs | 0 .../lib}/src/nodes/mod.rs | 0 .../lib}/src/tests/benchmark_storage_works.rs | 0 .../lib}/src/tests/common.rs | 0 .../src/tests/polkadot_argument_parsing.rs | 0 .../lib}/src/tests/polkadot_mdns_issue.rs | 0 .../lib}/src/tests/purge_chain_works.rs | 0 .../tests/running_the_node_and_interrupt.rs | 0 cumulus/polkadot-omni-node/src/main.rs | 60 +++++++++ cumulus/polkadot-parachain/Cargo.toml | 13 +- .../src/chain_spec/asset_hubs.rs | 2 +- .../src/chain_spec/bridge_hubs.rs | 6 +- .../src/chain_spec/collectives.rs | 2 +- .../src/chain_spec/coretime.rs | 6 +- .../src/chain_spec/glutton.rs | 2 +- .../polkadot-parachain/src/chain_spec/mod.rs | 2 +- .../src/chain_spec/penpal.rs | 2 +- .../src/chain_spec/people.rs | 6 +- .../src/chain_spec/rococo_parachain.rs | 2 +- cumulus/polkadot-parachain/src/main.rs | 2 +- .../src/guides/enable_elastic_scaling_mvp.rs | 4 +- prdoc/pr_5915.prdoc | 18 +++ umbrella/Cargo.toml | 14 +- umbrella/src/lib.rs | 8 +- 43 files changed, 232 insertions(+), 100 deletions(-) create mode 100644 cumulus/polkadot-omni-node/Cargo.toml create mode 100644 cumulus/polkadot-omni-node/build.rs rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/Cargo.toml (99%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/cli.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/command.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/aura.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/chain_spec.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/command.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/mod.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/rpc.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/runtime.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/spec.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/common/types.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/fake_runtime_api/mod.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/fake_runtime_api/utils.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/lib.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/nodes/aura.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/nodes/manual_seal.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/nodes/mod.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/benchmark_storage_works.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/common.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/polkadot_argument_parsing.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/polkadot_mdns_issue.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/purge_chain_works.rs (100%) rename cumulus/{polkadot-parachain/polkadot-parachain-lib => polkadot-omni-node/lib}/src/tests/running_the_node_and_interrupt.rs (100%) create mode 100644 cumulus/polkadot-omni-node/src/main.rs create mode 100644 prdoc/pr_5915.prdoc diff --git a/Cargo.lock b/Cargo.lock index 478e6df4977..4d5565e433b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14840,69 +14840,16 @@ dependencies = [ ] [[package]] -name = "polkadot-overseer" -version = "7.0.0" -dependencies = [ - "assert_matches", - "async-trait", - "femme", - "futures", - "futures-timer", - "orchestra", - "parking_lot 0.12.3", - "polkadot-node-metrics", - "polkadot-node-network-protocol", - "polkadot-node-primitives", - "polkadot-node-subsystem-test-helpers", - "polkadot-node-subsystem-types", - "polkadot-primitives", - "polkadot-primitives-test-helpers", - "prioritized-metered-channel", - "sc-client-api", - "sp-api 26.0.0", - "sp-core 28.0.0", - "tikv-jemalloc-ctl", - "tracing-gum", -] - -[[package]] -name = "polkadot-parachain-bin" -version = "4.0.0" +name = "polkadot-omni-node" +version = "0.1.0" dependencies = [ - "asset-hub-rococo-runtime", - "asset-hub-westend-runtime", - "bp-messages", - "bridge-hub-rococo-runtime", - "bridge-hub-westend-runtime", - "collectives-westend-runtime", "color-eyre", - "contracts-rococo-runtime", - "coretime-rococo-runtime", - "coretime-westend-runtime", - "cumulus-primitives-core", - "glutton-westend-runtime", - "hex-literal", - "log", - "parachains-common", - "penpal-runtime", - "people-rococo-runtime", - "people-westend-runtime", - "polkadot-parachain-lib", - "rococo-parachain-runtime", - "sc-chain-spec", - "sc-cli", - "sc-service", - "serde", - "serde_json", - "sp-core 28.0.0", - "sp-genesis-builder", - "staging-xcm", + "polkadot-omni-node-lib", "substrate-build-script-utils", - "testnet-parachains-constants", ] [[package]] -name = "polkadot-parachain-lib" +name = "polkadot-omni-node-lib" version = "0.1.0" dependencies = [ "assert_cmd", @@ -14975,6 +14922,66 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "polkadot-overseer" +version = "7.0.0" +dependencies = [ + "assert_matches", + "async-trait", + "femme", + "futures", + "futures-timer", + "orchestra", + "parking_lot 0.12.3", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem-test-helpers", + "polkadot-node-subsystem-types", + "polkadot-primitives", + "polkadot-primitives-test-helpers", + "prioritized-metered-channel", + "sc-client-api", + "sp-api 26.0.0", + "sp-core 28.0.0", + "tikv-jemalloc-ctl", + "tracing-gum", +] + +[[package]] +name = "polkadot-parachain-bin" +version = "4.0.0" +dependencies = [ + "asset-hub-rococo-runtime", + "asset-hub-westend-runtime", + "bridge-hub-rococo-runtime", + "bridge-hub-westend-runtime", + "collectives-westend-runtime", + "color-eyre", + "contracts-rococo-runtime", + "coretime-rococo-runtime", + "coretime-westend-runtime", + "cumulus-primitives-core", + "glutton-westend-runtime", + "hex-literal", + "log", + "parachains-common", + "penpal-runtime", + "people-rococo-runtime", + "people-westend-runtime", + "polkadot-omni-node-lib", + "rococo-parachain-runtime", + "sc-chain-spec", + "sc-cli", + "sc-service", + "serde", + "serde_json", + "sp-core 28.0.0", + "sp-genesis-builder", + "staging-xcm", + "substrate-build-script-utils", +] + [[package]] name = "polkadot-parachain-primitives" version = "6.0.0" @@ -15408,8 +15415,8 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", + "polkadot-omni-node-lib", "polkadot-overseer", - "polkadot-parachain-lib", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-rpc", diff --git a/Cargo.toml b/Cargo.toml index df662ac2698..bd37dee5146 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,8 +133,9 @@ members = [ "cumulus/parachains/runtimes/test-utils", "cumulus/parachains/runtimes/testing/penpal", "cumulus/parachains/runtimes/testing/rococo-parachain", + "cumulus/polkadot-omni-node", + "cumulus/polkadot-omni-node/lib", "cumulus/polkadot-parachain", - "cumulus/polkadot-parachain/polkadot-parachain-lib", "cumulus/primitives/aura", "cumulus/primitives/core", "cumulus/primitives/parachain-inherent", @@ -541,6 +542,7 @@ members = [ ] default-members = [ + "cumulus/polkadot-omni-node", "cumulus/polkadot-parachain", "polkadot", "substrate/bin/node/cli", @@ -1052,8 +1054,9 @@ polkadot-node-subsystem = { path = "polkadot/node/subsystem", default-features = polkadot-node-subsystem-test-helpers = { path = "polkadot/node/subsystem-test-helpers" } polkadot-node-subsystem-types = { path = "polkadot/node/subsystem-types", default-features = false } polkadot-node-subsystem-util = { path = "polkadot/node/subsystem-util", default-features = false } +polkadot-omni-node = { path = "cumulus/polkadot-omni-node", default-features = false } +polkadot-omni-node-lib = { path = "cumulus/polkadot-omni-node/lib", default-features = false } polkadot-overseer = { path = "polkadot/node/overseer", default-features = false } -polkadot-parachain-lib = { path = "cumulus/polkadot-parachain/polkadot-parachain-lib", default-features = false } polkadot-parachain-primitives = { path = "polkadot/parachain", default-features = false } polkadot-primitives = { path = "polkadot/primitives", default-features = false } polkadot-primitives-test-helpers = { path = "polkadot/primitives/test-helpers" } diff --git a/cumulus/polkadot-omni-node/Cargo.toml b/cumulus/polkadot-omni-node/Cargo.toml new file mode 100644 index 00000000000..a736e1ef80c --- /dev/null +++ b/cumulus/polkadot-omni-node/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "polkadot-omni-node" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +build = "build.rs" +description = "Generic binary that can run a parachain node with u32 block number and Aura consensus" +license = "Apache-2.0" + +[lints] +workspace = true + +[dependencies] +color-eyre = { workspace = true } + +# Local +polkadot-omni-node-lib = { workspace = true } + +[build-dependencies] +substrate-build-script-utils = { workspace = true, default-features = true } + +[features] +default = [] +runtime-benchmarks = [ + "polkadot-omni-node-lib/runtime-benchmarks", +] +try-runtime = [ + "polkadot-omni-node-lib/try-runtime", +] diff --git a/cumulus/polkadot-omni-node/build.rs b/cumulus/polkadot-omni-node/build.rs new file mode 100644 index 00000000000..8c498735eae --- /dev/null +++ b/cumulus/polkadot-omni-node/build.rs @@ -0,0 +1,22 @@ +// 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 . + +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + rerun_if_git_head_changed(); +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml similarity index 99% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml rename to cumulus/polkadot-omni-node/lib/Cargo.toml index 5e4c9fcf1a3..3dd482f4ada 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "polkadot-parachain-lib" +name = "polkadot-omni-node-lib" version = "0.1.0" authors.workspace = true edition.workspace = true diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-omni-node/lib/src/cli.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs rename to cumulus/polkadot-omni-node/lib/src/cli.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-omni-node/lib/src/command.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs rename to cumulus/polkadot-omni-node/lib/src/command.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/aura.rs b/cumulus/polkadot-omni-node/lib/src/common/aura.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/aura.rs rename to cumulus/polkadot-omni-node/lib/src/common/aura.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/chain_spec.rs b/cumulus/polkadot-omni-node/lib/src/common/chain_spec.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/chain_spec.rs rename to cumulus/polkadot-omni-node/lib/src/common/chain_spec.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs b/cumulus/polkadot-omni-node/lib/src/common/command.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/command.rs rename to cumulus/polkadot-omni-node/lib/src/common/command.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs b/cumulus/polkadot-omni-node/lib/src/common/mod.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/mod.rs rename to cumulus/polkadot-omni-node/lib/src/common/mod.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/rpc.rs rename to cumulus/polkadot-omni-node/lib/src/common/rpc.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/runtime.rs rename to cumulus/polkadot-omni-node/lib/src/common/runtime.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-omni-node/lib/src/common/spec.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs rename to cumulus/polkadot-omni-node/lib/src/common/spec.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs b/cumulus/polkadot-omni-node/lib/src/common/types.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/types.rs rename to cumulus/polkadot-omni-node/lib/src/common/types.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/mod.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/mod.rs rename to cumulus/polkadot-omni-node/lib/src/fake_runtime_api/mod.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/fake_runtime_api/utils.rs rename to cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs b/cumulus/polkadot-omni-node/lib/src/lib.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/lib.rs rename to cumulus/polkadot-omni-node/lib/src/lib.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs rename to cumulus/polkadot-omni-node/lib/src/nodes/aura.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs rename to cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs b/cumulus/polkadot-omni-node/lib/src/nodes/mod.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs rename to cumulus/polkadot-omni-node/lib/src/nodes/mod.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs b/cumulus/polkadot-omni-node/lib/src/tests/benchmark_storage_works.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/benchmark_storage_works.rs rename to cumulus/polkadot-omni-node/lib/src/tests/benchmark_storage_works.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs b/cumulus/polkadot-omni-node/lib/src/tests/common.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/common.rs rename to cumulus/polkadot-omni-node/lib/src/tests/common.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs b/cumulus/polkadot-omni-node/lib/src/tests/polkadot_argument_parsing.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_argument_parsing.rs rename to cumulus/polkadot-omni-node/lib/src/tests/polkadot_argument_parsing.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs b/cumulus/polkadot-omni-node/lib/src/tests/polkadot_mdns_issue.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/polkadot_mdns_issue.rs rename to cumulus/polkadot-omni-node/lib/src/tests/polkadot_mdns_issue.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs b/cumulus/polkadot-omni-node/lib/src/tests/purge_chain_works.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/purge_chain_works.rs rename to cumulus/polkadot-omni-node/lib/src/tests/purge_chain_works.rs diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs b/cumulus/polkadot-omni-node/lib/src/tests/running_the_node_and_interrupt.rs similarity index 100% rename from cumulus/polkadot-parachain/polkadot-parachain-lib/src/tests/running_the_node_and_interrupt.rs rename to cumulus/polkadot-omni-node/lib/src/tests/running_the_node_and_interrupt.rs diff --git a/cumulus/polkadot-omni-node/src/main.rs b/cumulus/polkadot-omni-node/src/main.rs new file mode 100644 index 00000000000..a86ec6f6fde --- /dev/null +++ b/cumulus/polkadot-omni-node/src/main.rs @@ -0,0 +1,60 @@ +// 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 . + +//! Basic polkadot omni-node. +//! +//! It can be used to start a parachain node from a provided chain spec file. +//! It is only compatible with runtimes that use block number `u32` and `Aura` consensus. +//! +//! Example: `polkadot-omni-node --chain [chain_spec.json]` + +#![warn(missing_docs)] +#![warn(unused_extern_crates)] + +use polkadot_omni_node_lib::{ + chain_spec::DiskChainSpecLoader, run, runtime::DefaultRuntimeResolver, CliConfig as CliConfigT, + RunConfig, +}; + +struct CliConfig; + +impl CliConfigT for CliConfig { + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "https://github.com/paritytech/polkadot-sdk/issues/new".into() + } + + fn copyright_start_year() -> u16 { + 2017 + } +} + +fn main() -> color_eyre::eyre::Result<()> { + color_eyre::install()?; + + let config = RunConfig { + chain_spec_loader: Box::new(DiskChainSpecLoader), + runtime_resolver: Box::new(DefaultRuntimeResolver), + }; + Ok(run::(config)?) +} diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index fb95853c492..426679ce0cd 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -22,7 +22,7 @@ serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } # Local -polkadot-parachain-lib = { features = ["rococo-native", "westend-native"], workspace = true } +polkadot-omni-node-lib = { features = ["rococo-native", "westend-native"], workspace = true } rococo-parachain-runtime = { workspace = true } glutton-westend-runtime = { workspace = true } asset-hub-rococo-runtime = { workspace = true, default-features = true } @@ -37,10 +37,6 @@ penpal-runtime = { workspace = true } people-rococo-runtime = { workspace = true } people-westend-runtime = { workspace = true } parachains-common = { workspace = true, default-features = true } -testnet-parachains-constants = { features = [ - "rococo", - "westend", -], workspace = true } # Substrate sp-core = { workspace = true, default-features = true } @@ -55,9 +51,6 @@ xcm = { workspace = true, default-features = true } # Cumulus cumulus-primitives-core = { workspace = true, default-features = true } -# Bridges -bp-messages = { workspace = true, default-features = true } - [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } @@ -66,7 +59,7 @@ default = [] runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "parachains-common/runtime-benchmarks", - "polkadot-parachain-lib/runtime-benchmarks", + "polkadot-omni-node-lib/runtime-benchmarks", "sc-service/runtime-benchmarks", "asset-hub-rococo-runtime/runtime-benchmarks", @@ -84,7 +77,7 @@ runtime-benchmarks = [ "rococo-parachain-runtime/runtime-benchmarks", ] try-runtime = [ - "polkadot-parachain-lib/try-runtime", + "polkadot-omni-node-lib/try-runtime", "asset-hub-rococo-runtime/try-runtime", "asset-hub-westend-runtime/try-runtime", diff --git a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs index 699c0b5ce77..ec2afc743de 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/asset_hubs.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; +use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; pub fn asset_hub_westend_development_config() -> GenericChainSpec { diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index af399be9eac..839e93d0a67 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -15,7 +15,7 @@ // along with Cumulus. If not, see . use cumulus_primitives_core::ParaId; -use polkadot_parachain_lib::chain_spec::GenericChainSpec; +use polkadot_omni_node_lib::chain_spec::GenericChainSpec; use sc_chain_spec::{ChainSpec, ChainType}; use std::str::FromStr; @@ -127,7 +127,7 @@ fn ensure_id(id: &str) -> Result<&str, String> { /// Sub-module for Rococo setup pub mod rococo { use super::{ChainType, ParaId}; - use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; + use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; pub(crate) const BRIDGE_HUB_ROCOCO: &str = "bridge-hub-rococo"; pub(crate) const BRIDGE_HUB_ROCOCO_LOCAL: &str = "bridge-hub-rococo-local"; @@ -175,7 +175,7 @@ pub mod kusama { /// Sub-module for Westend setup. pub mod westend { use super::{ChainType, ParaId}; - use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; + use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; pub(crate) const BRIDGE_HUB_WESTEND: &str = "bridge-hub-westend"; pub(crate) const BRIDGE_HUB_WESTEND_LOCAL: &str = "bridge-hub-westend-local"; diff --git a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs index 227e15fdff8..0d2f66b5acc 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/collectives.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/collectives.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; +use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; /// Collectives Westend Development Config. diff --git a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs index fec3f56e6d3..e42e95ad16f 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -15,7 +15,7 @@ // along with Cumulus. If not, see . use cumulus_primitives_core::ParaId; -use polkadot_parachain_lib::chain_spec::GenericChainSpec; +use polkadot_omni_node_lib::chain_spec::GenericChainSpec; use sc_chain_spec::{ChainSpec, ChainType}; use std::{borrow::Cow, str::FromStr}; @@ -150,7 +150,7 @@ pub mod rococo { get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId, Balance}; - use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; + use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; @@ -248,7 +248,7 @@ pub mod westend { get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId, Balance}; - use polkadot_parachain_lib::chain_spec::Extensions; + use polkadot_omni_node_lib::chain_spec::Extensions; use sp_core::sr25519; pub(crate) const CORETIME_WESTEND: &str = "coretime-westend"; diff --git a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs index 136411b93e8..e70f86f725a 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs @@ -17,7 +17,7 @@ use crate::chain_spec::get_account_id_from_seed; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; +use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index 16fdbdc3865..a820fdbd13e 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -18,7 +18,7 @@ use cumulus_primitives_core::ParaId; pub(crate) use parachains_common::genesis_config_helpers::{ get_account_id_from_seed, get_collator_keys_from_seed, get_from_seed, }; -use polkadot_parachain_lib::{ +use polkadot_omni_node_lib::{ chain_spec::{GenericChainSpec, LoadSpec}, runtime::{ AuraConsensusId, BlockNumber, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, diff --git a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs index 5645bf06b67..006c5c9b53c 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs @@ -17,7 +17,7 @@ use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; +use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/people.rs b/cumulus/polkadot-parachain/src/chain_spec/people.rs index 3c1150d9542..b89f1c3a5fe 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/people.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/people.rs @@ -16,7 +16,7 @@ use cumulus_primitives_core::ParaId; use parachains_common::Balance as PeopleBalance; -use polkadot_parachain_lib::chain_spec::GenericChainSpec; +use polkadot_omni_node_lib::chain_spec::GenericChainSpec; use sc_chain_spec::ChainSpec; use std::str::FromStr; @@ -124,7 +124,7 @@ pub mod rococo { get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId}; - use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; + use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; @@ -234,7 +234,7 @@ pub mod westend { get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, AuraId}; - use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; + use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; use sp_core::sr25519; diff --git a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs index 251926838d2..39762f590cf 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs @@ -20,7 +20,7 @@ use crate::chain_spec::{get_from_seed, SAFE_XCM_VERSION}; use cumulus_primitives_core::ParaId; use hex_literal::hex; use parachains_common::{genesis_config_helpers::get_account_id_from_seed, AccountId}; -use polkadot_parachain_lib::chain_spec::{Extensions, GenericChainSpec}; +use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use rococo_parachain_runtime::AuraId; use sc_chain_spec::ChainType; use sp_core::{crypto::UncheckedInto, sr25519}; diff --git a/cumulus/polkadot-parachain/src/main.rs b/cumulus/polkadot-parachain/src/main.rs index f2dce552c51..c8464be937c 100644 --- a/cumulus/polkadot-parachain/src/main.rs +++ b/cumulus/polkadot-parachain/src/main.rs @@ -21,7 +21,7 @@ mod chain_spec; -use polkadot_parachain_lib::{run, CliConfig as CliConfigT, RunConfig}; +use polkadot_omni_node_lib::{run, CliConfig as CliConfigT, RunConfig}; struct CliConfig; diff --git a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs index 14a0b610796..812e674d163 100644 --- a/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs +++ b/docs/sdk/src/guides/enable_elastic_scaling_mvp.rs @@ -85,7 +85,7 @@ //! This phase consists of plugging in the new slot-based collator. //! //! 1. In `node/src/service.rs` import the slot based collator instead of the lookahead collator. -#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs", slot_based_colator_import)] +#![doc = docify::embed!("../../cumulus/polkadot-omni-node/lib/src/nodes/aura.rs", slot_based_colator_import)] //! //! 2. In `start_consensus()` //! - Remove the `overseer_handle` param (also remove the @@ -94,7 +94,7 @@ //! `slot_drift` field with a value of `Duration::from_secs(1)`. //! - Replace the single future returned by `aura::run` with the two futures returned by it and //! spawn them as separate tasks: -#![doc = docify::embed!("../../cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs", launch_slot_based_collator)] +#![doc = docify::embed!("../../cumulus/polkadot-omni-node/lib/src/nodes/aura.rs", launch_slot_based_collator)] //! //! 3. In `start_parachain_node()` remove the `overseer_handle` param passed to `start_consensus`. //! diff --git a/prdoc/pr_5915.prdoc b/prdoc/pr_5915.prdoc new file mode 100644 index 00000000000..a9303e2563d --- /dev/null +++ b/prdoc/pr_5915.prdoc @@ -0,0 +1,18 @@ +# 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: Omni-Node renamings + +doc: + - audience: Node Dev + description: | + This PR renames the `polkadot-parachain-lib` crate to `polkadot-omni-node-lib` and introduces a new + `polkadot-omni-node` binary. + +crates: + - name: polkadot-omni-node-lib + bump: patch + - name: polkadot-parachain-bin + bump: patch + - name: polkadot-sdk + bump: patch diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 3e7edc73982..e05bf129bbd 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -335,7 +335,7 @@ runtime-benchmarks = [ "parachains-common?/runtime-benchmarks", "polkadot-cli?/runtime-benchmarks", "polkadot-node-metrics?/runtime-benchmarks", - "polkadot-parachain-lib?/runtime-benchmarks", + "polkadot-omni-node-lib?/runtime-benchmarks", "polkadot-parachain-primitives?/runtime-benchmarks", "polkadot-primitives?/runtime-benchmarks", "polkadot-runtime-common?/runtime-benchmarks", @@ -466,7 +466,7 @@ try-runtime = [ "pallet-xcm-bridge-hub?/try-runtime", "pallet-xcm?/try-runtime", "polkadot-cli?/try-runtime", - "polkadot-parachain-lib?/try-runtime", + "polkadot-omni-node-lib?/try-runtime", "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains?/try-runtime", "polkadot-sdk-frame?/try-runtime", @@ -600,7 +600,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-overseer", "polkadot-parachain-lib", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-omni-node-lib", "polkadot-overseer", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", @@ -2082,13 +2082,13 @@ path = "../polkadot/node/subsystem-util" default-features = false optional = true -[dependencies.polkadot-overseer] -path = "../polkadot/node/overseer" +[dependencies.polkadot-omni-node-lib] +path = "../cumulus/polkadot-omni-node/lib" default-features = false optional = true -[dependencies.polkadot-parachain-lib] -path = "../cumulus/polkadot-parachain/polkadot-parachain-lib" +[dependencies.polkadot-overseer] +path = "../polkadot/node/overseer" default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 656273029fe..7778706d515 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -898,14 +898,14 @@ pub use polkadot_node_subsystem_types; #[cfg(feature = "polkadot-node-subsystem-util")] pub use polkadot_node_subsystem_util; +/// Helper library that can be used to build a parachain node. +#[cfg(feature = "polkadot-omni-node-lib")] +pub use polkadot_omni_node_lib; + /// System overseer of the Polkadot node. #[cfg(feature = "polkadot-overseer")] pub use polkadot_overseer; -/// Helper library that can be used to build a parachain node. -#[cfg(feature = "polkadot-parachain-lib")] -pub use polkadot_parachain_lib; - /// Types and utilities for creating and working with parachains. #[cfg(feature = "polkadot-parachain-primitives")] pub use polkadot_parachain_primitives; -- GitLab From c482333560b1d0372824f29c5b85fad5d45f281d Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 8 Oct 2024 14:41:40 +0100 Subject: [PATCH 072/127] [omni-bencher] Make all runtimes work (#5872) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add `--exclude-pallets` to exclude some pallets from runtimes where we dont have genesis presets yet - Make `--genesis-builder-policy=none` work with `--runtime` - CI: Run the frame-omni-bencher for all runtimes --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: ggwpez Co-authored-by: Bastian Köcher Co-authored-by: Branislav Kontur Co-authored-by: alvicsam Co-authored-by: Maksym H Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Maksym H <1177472+mordamax@users.noreply.github.com> --- .github/scripts/cmd/cmd.py | 5 +- .github/scripts/cmd/test_cmd.py | 169 ++++++++++++++---- .../workflows/check-frame-omni-bencher.yml | 57 +++--- .github/workflows/runtimes-matrix.json | 36 ++-- .../contracts-rococo/src/contracts.rs | 5 +- prdoc/pr_5872.prdoc | 13 ++ .../benchmarking-cli/src/pallet/command.rs | 89 +++++---- .../frame/benchmarking-cli/src/pallet/mod.rs | 4 + .../benchmarking-cli/src/pallet/types.rs | 16 +- .../benchmarking-cli/src/pallet/writer.rs | 3 - 10 files changed, 280 insertions(+), 117 deletions(-) create mode 100644 prdoc/pr_5872.prdoc diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index b421ab753f7..01f36ea375c 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -104,7 +104,7 @@ def main(): print(f'-- listing pallets for benchmark for {runtime["name"]}') wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" output = os.popen( - f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file}").read() + f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file} {runtime['bench_flags']}").read() raw_pallets = output.strip().split('\n') all_pallets = set() @@ -182,7 +182,8 @@ def main(): f"--repeat=20 " \ f"--heap-pages=4096 " \ f"{f'--template={template} ' if template else ''}" \ - f"--no-storage-info --no-min-squares --no-median-slopes" + f"--no-storage-info --no-min-squares --no-median-slopes " \ + f"{config['bench_flags']}" print(f'-- Running: {cmd} \n') status = os.system(cmd) if status != 0 and not args.continue_on_fail: diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index f4e70903347..0316c7ff1bb 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -7,13 +7,45 @@ import argparse # Mock data for runtimes-matrix.json mock_runtimes_matrix = [ - {"name": "dev", "package": "kitchensink-runtime", "path": "substrate/frame", "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", "bench_features": "runtime-benchmarks,riscv"}, - {"name": "westend", "package": "westend-runtime", "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, - {"name": "rococo", "package": "rococo-runtime", "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "bench_features": "runtime-benchmarks"}, - {"name": "asset-hub-westend", "package": "asset-hub-westend-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks"}, + { + "name": "dev", + "package": "kitchensink-runtime", + "path": "substrate/frame", + "header": "substrate/HEADER-APACHE2", + "template": "substrate/.maintain/frame-weight-template.hbs", + "bench_features": "runtime-benchmarks,riscv", + "bench_flags": "--flag1 --flag2" + }, + { + "name": "westend", + "package": "westend-runtime", + "path": "polkadot/runtime/westend", + "header": "polkadot/file_header.txt", + "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--flag3 --flag4" + }, + { + "name": "rococo", + "package": "rococo-runtime", + "path": "polkadot/runtime/rococo", + "header": "polkadot/file_header.txt", + "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "" + }, + { + "name": "asset-hub-westend", + "package": "asset-hub-westend-runtime", + "path": "cumulus/parachains/runtimes/assets/asset-hub-westend", + "header": "cumulus/file_header.txt", + "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--flag7 --flag8" + } ] -def get_mock_bench_output(runtime, pallets, output_path, header, template = None): +def get_mock_bench_output(runtime, pallets, output_path, header, bench_flags, template = None): return f"frame-omni-bencher v1 benchmark pallet --extrinsic=* " \ f"--runtime=target/release/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ f"--pallet={pallets} --header={header} " \ @@ -21,7 +53,8 @@ def get_mock_bench_output(runtime, pallets, output_path, header, template = None f"--wasm-execution=compiled " \ f"--steps=50 --repeat=20 --heap-pages=4096 " \ f"{f'--template={template} ' if template else ''}" \ - f"--no-storage-info --no-min-squares --no-median-slopes" + f"--no-storage-info --no-min-squares --no-median-slopes " \ + f"{bench_flags}" class TestCmd(unittest.TestCase): @@ -89,10 +122,29 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), - call(get_mock_bench_output('kitchensink', 'pallet_balances', './substrate/frame/balances/src/weights.rs', os.path.abspath('substrate/HEADER-APACHE2'), "substrate/.maintain/frame-weight-template.hbs")), - call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', os.path.abspath('polkadot/file_header.txt'))), + call(get_mock_bench_output( + runtime='kitchensink', + pallets='pallet_balances', + output_path='./substrate/frame/balances/src/weights.rs', + header=os.path.abspath('substrate/HEADER-APACHE2'), + bench_flags='--flag1 --flag2', + template="substrate/.maintain/frame-weight-template.hbs" + )), + call(get_mock_bench_output( + runtime='westend', + pallets='pallet_balances', + output_path='./polkadot/runtime/westend/src/weights', + header=os.path.abspath('polkadot/file_header.txt'), + bench_flags='--flag3 --flag4' + )), # skips rococo benchmark - call(get_mock_bench_output('asset-hub-westend', 'pallet_balances', './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', os.path.abspath('cumulus/file_header.txt'))), + call(get_mock_bench_output( + runtime='asset-hub-westend', + pallets='pallet_balances', + output_path='./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', + header=os.path.abspath('cumulus/file_header.txt'), + bench_flags='--flag7 --flag8' + )), ] self.mock_system.assert_has_calls(expected_calls, any_order=True) @@ -121,8 +173,20 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls - call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), - call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), + call(get_mock_bench_output( + runtime='westend', + pallets='pallet_balances', + output_path='./polkadot/runtime/westend/src/weights', + header=header_path, + bench_flags='--flag3 --flag4' + )), + call(get_mock_bench_output( + runtime='westend', + pallets='pallet_staking', + output_path='./polkadot/runtime/westend/src/weights', + header=header_path, + bench_flags='--flag3 --flag4' + )), ] self.mock_system.assert_has_calls(expected_calls, any_order=True) @@ -153,11 +217,12 @@ class TestCmd(unittest.TestCase): # Westend runtime calls call(get_mock_bench_output( - 'westend', - 'pallet_xcm_benchmarks::generic', - './polkadot/runtime/westend/src/weights/xcm', - header_path, - "polkadot/xcm/pallet-xcm-benchmarks/template.hbs" + runtime='westend', + pallets='pallet_xcm_benchmarks::generic', + output_path='./polkadot/runtime/westend/src/weights/xcm', + header=header_path, + bench_flags='--flag3 --flag4', + template="polkadot/xcm/pallet-xcm-benchmarks/template.hbs" )), ] self.mock_system.assert_has_calls(expected_calls, any_order=True) @@ -188,11 +253,35 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), # Westend runtime calls - call(get_mock_bench_output('westend', 'pallet_staking', './polkadot/runtime/westend/src/weights', header_path)), - call(get_mock_bench_output('westend', 'pallet_balances', './polkadot/runtime/westend/src/weights', header_path)), + call(get_mock_bench_output( + runtime='westend', + pallets='pallet_staking', + output_path='./polkadot/runtime/westend/src/weights', + header=header_path, + bench_flags='--flag3 --flag4' + )), + call(get_mock_bench_output( + runtime='westend', + pallets='pallet_balances', + output_path='./polkadot/runtime/westend/src/weights', + header=header_path, + bench_flags='--flag3 --flag4' + )), # Rococo runtime calls - call(get_mock_bench_output('rococo', 'pallet_staking', './polkadot/runtime/rococo/src/weights', header_path)), - call(get_mock_bench_output('rococo', 'pallet_balances', './polkadot/runtime/rococo/src/weights', header_path)), + call(get_mock_bench_output( + runtime='rococo', + pallets='pallet_staking', + output_path='./polkadot/runtime/rococo/src/weights', + header=header_path, + bench_flags='' + )), + call(get_mock_bench_output( + runtime='rococo', + pallets='pallet_balances', + output_path='./polkadot/runtime/rococo/src/weights', + header=header_path, + bench_flags='' + )), ] self.mock_system.assert_has_calls(expected_calls, any_order=True) @@ -223,11 +312,12 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks,riscv"), # Westend runtime calls call(get_mock_bench_output( - 'kitchensink', - 'pallet_balances', - manifest_dir + "/src/weights.rs", - header_path, - "substrate/.maintain/frame-weight-template.hbs" + runtime='kitchensink', + pallets='pallet_balances', + output_path=manifest_dir + "/src/weights.rs", + header=header_path, + bench_flags='--flag1 --flag2', + template="substrate/.maintain/frame-weight-template.hbs" )), ] self.mock_system.assert_has_calls(expected_calls, any_order=True) @@ -257,10 +347,11 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( - 'asset-hub-westend', - 'pallet_assets', - './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', - header_path + runtime='asset-hub-westend', + pallets='pallet_assets', + output_path='./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', + header=header_path, + bench_flags='--flag7 --flag8' )), ] @@ -291,17 +382,19 @@ class TestCmd(unittest.TestCase): call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( - 'asset-hub-westend', - 'pallet_xcm_benchmarks::generic', - './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm', - header_path, - "cumulus/templates/xcm-bench-template.hbs" + runtime='asset-hub-westend', + pallets='pallet_xcm_benchmarks::generic', + output_path='./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm', + header=header_path, + bench_flags='--flag7 --flag8', + template="cumulus/templates/xcm-bench-template.hbs" )), call(get_mock_bench_output( - 'asset-hub-westend', - 'pallet_assets', - './cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', - header_path + runtime='asset-hub-westend', + pallets='pallet_assets', + output_path='./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights', + header=header_path, + bench_flags='--flag7 --flag8' )), ] diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index 9b01311aa69..935fc080c4e 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -16,14 +16,11 @@ env: ARTIFACTS_NAME: frame-omni-bencher-artifacts jobs: - preflight: - # TODO: remove once migration is complete or this workflow is fully stable - if: contains(github.event.label.name, 'GHA-migration') uses: ./.github/workflows/reusable-preflight.yml quick-benchmarks-omni: - runs-on: ${{ needs.preflight.outputs.RUNNER }} + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} needs: [preflight] if: ${{ needs.preflight.outputs.changes_rust }} env: @@ -31,6 +28,7 @@ jobs: RUST_BACKTRACE: "full" WASM_BUILD_NO_COLOR: 1 WASM_BUILD_RUSTFLAGS: "-C debug-assertions" + RUST_LOG: "frame_omni_bencher=info,polkadot_sdk_frame=info" timeout-minutes: 30 container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -42,33 +40,41 @@ jobs: forklift cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks forklift cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet + runtime-matrix: + runs-on: ubuntu-latest + needs: [preflight] + if: ${{ needs.preflight.outputs.changes_rust }} + timeout-minutes: 30 + outputs: + runtime: ${{ steps.runtime.outputs.runtime }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + name: Extract runtimes from matrix + steps: + - uses: actions/checkout@v4 + - id: runtime + run: | + RUNTIMES=$(jq '[.[] | select(.package != null)]' .github/workflows/runtimes-matrix.json) + + RUNTIMES=$(echo $RUNTIMES | jq -c .) + echo "runtime=$RUNTIMES" + echo "runtime=$RUNTIMES" >> $GITHUB_OUTPUT + run-frame-omni-bencher: - runs-on: ${{ needs.preflight.outputs.RUNNER }} - needs: [preflight] # , build-frame-omni-bencher ] + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} + needs: [preflight, runtime-matrix] if: ${{ needs.preflight.outputs.changes_rust }} timeout-minutes: 30 strategy: fail-fast: false # keep running other workflows even if one fails, to see the logs of all possible failures matrix: - runtime: - [ - westend-runtime, - rococo-runtime, - asset-hub-rococo-runtime, - asset-hub-westend-runtime, - bridge-hub-rococo-runtime, - bridge-hub-westend-runtime, - collectives-westend-runtime, - coretime-rococo-runtime, - coretime-westend-runtime, - people-rococo-runtime, - people-westend-runtime, - glutton-westend-runtime, - ] + runtime: ${{ fromJSON(needs.runtime-matrix.outputs.runtime) }} container: image: ${{ needs.preflight.outputs.IMAGE }} env: - PACKAGE_NAME: ${{ matrix.runtime }} + PACKAGE_NAME: ${{ matrix.runtime.package }} + FLAGS: ${{ matrix.runtime.bench_flags }} + RUST_LOG: "frame_omni_bencher=info,polkadot_sdk_frame=info" steps: - name: Checkout uses: actions/checkout@v4 @@ -77,10 +83,13 @@ jobs: run: | RUNTIME_BLOB_NAME=$(echo $PACKAGE_NAME | sed 's/-/_/g').compact.compressed.wasm RUNTIME_BLOB_PATH=./target/release/wbuild/$PACKAGE_NAME/$RUNTIME_BLOB_NAME - forklift cargo build --release --locked -p $PACKAGE_NAME -p frame-omni-bencher --features runtime-benchmarks + forklift cargo build --release --locked -p $PACKAGE_NAME -p frame-omni-bencher --features=${{ matrix.runtime.bench_features }} --quiet echo "Running short benchmarking for PACKAGE_NAME=$PACKAGE_NAME and RUNTIME_BLOB_PATH=$RUNTIME_BLOB_PATH" ls -lrt $RUNTIME_BLOB_PATH - ./target/release/frame-omni-bencher v1 benchmark pallet --runtime $RUNTIME_BLOB_PATH --all --steps 2 --repeat 1 + + cmd="./target/release/frame-omni-bencher v1 benchmark pallet --runtime $RUNTIME_BLOB_PATH --all --steps 2 --repeat 1 $FLAGS" + echo "Running command: $cmd" + eval "$cmd" confirm-frame-omni-benchers-passed: runs-on: ubuntu-latest name: All benchmarks passed diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index 50fa44b1b07..e4e3a2dbe6d 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -6,6 +6,7 @@ "header": "substrate/HEADER-APACHE2", "template": "substrate/.maintain/frame-weight-template.hbs", "bench_features": "runtime-benchmarks,riscv", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage", "uri": null, "is_relay": false }, @@ -15,8 +16,9 @@ "path": "polkadot/runtime/westend", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", - "uri": "wss://try-runtime-westend.polkadot.io:443", + "bench_flags": "", "bench_features": "runtime-benchmarks", + "uri": "wss://try-runtime-westend.polkadot.io:443", "is_relay": true }, { @@ -27,6 +29,7 @@ "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", "uri": "wss://try-runtime-rococo.polkadot.io:443", "bench_features": "runtime-benchmarks", + "bench_flags": "", "is_relay": true }, { @@ -36,6 +39,7 @@ "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks", + "bench_flags": "", "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -44,8 +48,9 @@ "package": "asset-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/assets/asset-hub-rococo", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "", "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -54,8 +59,9 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "", "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -64,8 +70,9 @@ "package": "bridge-hub-rococo-runtime", "path": "cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "", "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", "is_relay": false }, @@ -74,8 +81,9 @@ "package": "collectives-westend-runtime", "path": "cumulus/parachains/runtimes/collectives/collectives-westend", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "", "uri": "wss://westend-collectives-rpc.polkadot.io:443" }, { @@ -83,8 +91,9 @@ "package": "contracts-rococo-runtime", "path": "cumulus/parachains/runtimes/contracts/contracts-rococo", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm", "uri": "wss://rococo-contracts-rpc.polkadot.io:443", "is_relay": false }, @@ -93,8 +102,9 @@ "package": "coretime-rococo-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-rococo", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://rococo-coretime-rpc.polkadot.io:443", "is_relay": false }, @@ -103,8 +113,9 @@ "package": "coretime-westend-runtime", "path": "cumulus/parachains/runtimes/coretime/coretime-westend", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://westend-coretime-rpc.polkadot.io:443", "is_relay": false }, @@ -113,8 +124,9 @@ "package": "glutton-westend-runtime", "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none", "uri": null, "is_relay": false }, @@ -123,8 +135,9 @@ "package": "people-rococo-runtime", "path": "cumulus/parachains/runtimes/people/people-rococo", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://rococo-people-rpc.polkadot.io:443", "is_relay": false }, @@ -133,8 +146,9 @@ "package": "people-westend-runtime", "path": "cumulus/parachains/runtimes/people/people-westend", "header": "cumulus/file_header.txt", - "bench_features": "runtime-benchmarks", "template": "cumulus/templates/xcm-bench-template.hbs", + "bench_features": "runtime-benchmarks", + "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://westend-people-rpc.polkadot.io:443", "is_relay": false } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index e8cc9d02fb0..40801f66a47 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -72,7 +72,10 @@ impl Config for Runtime { type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; type MaxDelegateDependencies = ConstU32<32>; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Migrations = (pallet_contracts::migration::v16::Migration,); + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = (); + #[cfg(feature = "runtime-benchmarks")] + type Migrations = pallet_contracts::migration::codegen::BenchMigrations; type RuntimeHoldReason = RuntimeHoldReason; type Debug = (); type Environment = (); diff --git a/prdoc/pr_5872.prdoc b/prdoc/pr_5872.prdoc new file mode 100644 index 00000000000..cf4f0b24f8d --- /dev/null +++ b/prdoc/pr_5872.prdoc @@ -0,0 +1,13 @@ +title: '[omni-bencher] Make all runtimes work' +doc: +- audience: Runtime Dev + description: |- + Changes: + - Add `--exclude-pallets` to exclude some pallets from runtimes where we dont have genesis presets yet + - Make `--genesis-builder-policy=none` work with `--runtime` + - CI: Run the frame-omni-bencher for all runtimes +crates: +- name: frame-benchmarking-cli + bump: major +- name: contracts-rococo-runtime + bump: patch diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index f1499f41c2e..f3334819057 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -36,7 +36,7 @@ use sp_core::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, - traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt}, + traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt, WrappedRuntimeCode}, Hasher, }; use sp_externalities::Extensions; @@ -47,6 +47,7 @@ use sp_storage::{well_known_keys::CODE, Storage}; use sp_trie::{proof_size_extension::ProofSizeExt, recorder::Recorder}; use sp_wasm_interface::HostFunctions; use std::{ + borrow::Cow, collections::{BTreeMap, BTreeSet, HashMap}, fmt::Debug, fs, @@ -368,12 +369,12 @@ impl PalletCmd { "dispatch a benchmark", ) { Err(e) => { - log::error!("Error executing and verifying runtime benchmark: {}", e); + log::error!(target: LOG_TARGET, "Error executing and verifying runtime benchmark: {}", e); failed.push((pallet.clone(), extrinsic.clone())); continue 'outer }, Ok(Err(e)) => { - log::error!("Error executing and verifying runtime benchmark: {}", e); + log::error!(target: LOG_TARGET, "Error executing and verifying runtime benchmark: {}", e); failed.push((pallet.clone(), extrinsic.clone())); continue 'outer }, @@ -408,12 +409,12 @@ impl PalletCmd { "dispatch a benchmark", ) { Err(e) => { - log::error!("Error executing runtime benchmark: {}", e); + log::error!(target: LOG_TARGET, "Error executing runtime benchmark: {}", e); failed.push((pallet.clone(), extrinsic.clone())); continue 'outer }, Ok(Err(e)) => { - log::error!("Benchmark {pallet}::{extrinsic} failed: {e}",); + log::error!(target: LOG_TARGET, "Benchmark {pallet}::{extrinsic} failed: {e}",); failed.push((pallet.clone(), extrinsic.clone())); continue 'outer }, @@ -502,33 +503,28 @@ impl PalletCmd { } fn select_benchmarks_to_run(&self, list: Vec) -> Result> { - 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(); // Use the benchmark list and the user input to determine the set of benchmarks to run. let mut benchmarks_to_run = Vec::new(); - list.iter() - .filter(|item| pallet.is_empty() || pallet == &b"*"[..] || pallet == &item.pallet[..]) - .for_each(|item| { - for benchmark in &item.benchmarks { - let benchmark_name = &benchmark.name; - if extrinsic.is_empty() || - extrinsic.as_bytes() == &b"*"[..] || - extrinsics.contains(&&benchmark_name[..]) - { - benchmarks_to_run.push(( - item.pallet.clone(), - benchmark.name.clone(), - benchmark.components.clone(), - benchmark.pov_modes.clone(), - )) - } + list.iter().filter(|item| self.pallet_selected(&item.pallet)).for_each(|item| { + for benchmark in &item.benchmarks { + let benchmark_name = &benchmark.name; + if extrinsic.is_empty() || + extrinsic.as_bytes() == &b"*"[..] || + extrinsics.contains(&&benchmark_name[..]) + { + benchmarks_to_run.push(( + item.pallet.clone(), + benchmark.name.clone(), + benchmark.components.clone(), + benchmark.pov_modes.clone(), + )) } - }); + } + }); // Convert `Vec` to `String` for better readability. let benchmarks_to_run: Vec<_> = benchmarks_to_run .into_iter() @@ -558,6 +554,16 @@ impl PalletCmd { Ok(benchmarks_to_run) } + /// Whether this pallet should be run. + fn pallet_selected(&self, pallet: &Vec) -> bool { + let include = self.pallet.clone().unwrap_or_default(); + + let included = include.is_empty() || include == "*" || include.as_bytes() == pallet; + let excluded = self.exclude_pallets.iter().any(|p| p.as_bytes() == pallet); + + included && !excluded + } + /// Build the genesis storage by either the Genesis Builder API, chain spec or nothing. /// /// Behaviour can be controlled by the `--genesis-builder` flag. @@ -566,12 +572,11 @@ impl PalletCmd { chain_spec: &Option>, ) -> Result { Ok(match (self.genesis_builder, self.runtime.as_ref()) { - (Some(GenesisBuilderPolicy::None), Some(_)) => return Err("Cannot use `--genesis-builder=none` with `--runtime` since the runtime would be ignored.".into()), - (Some(GenesisBuilderPolicy::None), None) => Storage::default(), + (Some(GenesisBuilderPolicy::None), _) => Storage::default(), (Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::Spec), Some(_)) => return Err("Cannot use `--genesis-builder=spec-genesis` with `--runtime` since the runtime would be ignored.".into()), (Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::Spec), None) | (None, None) => { - log::warn!("{WARN_SPEC_GENESIS_CTOR}"); + log::warn!(target: LOG_TARGET, "{WARN_SPEC_GENESIS_CTOR}"); let Some(chain_spec) = chain_spec else { return Err("No chain spec specified to generate the genesis state".into()); }; @@ -593,7 +598,7 @@ impl PalletCmd { }, (Some(GenesisBuilderPolicy::Runtime), None) => return Err("Cannot use `--genesis-builder=runtime` without `--runtime`".into()), (Some(GenesisBuilderPolicy::Runtime), Some(runtime)) | (None, Some(runtime)) => { - log::info!("Loading WASM from {}", runtime.display()); + log::info!(target: LOG_TARGET, "Loading WASM from {}", runtime.display()); let code = fs::read(&runtime).map_err(|e| { format!( @@ -613,7 +618,7 @@ impl PalletCmd { &self, chain_spec: &dyn ChainSpec, ) -> Result { - log::info!("Building genesis state from chain spec runtime"); + log::info!(target: LOG_TARGET, "Building genesis state from chain spec runtime"); let storage = chain_spec .build_storage() .map_err(|e| format!("{ERROR_CANNOT_BUILD_GENESIS}\nError: {e}"))?; @@ -636,8 +641,9 @@ impl PalletCmd { genesis_config_caller.get_storage_for_named_preset(preset).inspect_err(|e| { let presets = genesis_config_caller.preset_names().unwrap_or_default(); log::error!( + target: LOG_TARGET, "Please pick one of the available presets with \ - `--genesis-builder-preset=`. Available presets ({}): {:?}. Error: {:?}", + `--genesis-builder-preset=` or use a different `--genesis-builder-policy`. Available presets ({}): {:?}. Error: {:?}", presets.len(), presets, e @@ -690,10 +696,25 @@ impl PalletCmd { &self, state: &'a BenchmarkingState, ) -> Result, H>> { - log::info!("Loading WASM from state"); - let state = sp_state_machine::backend::BackendRuntimeCode::new(state); + if let Some(runtime) = self.runtime.as_ref() { + log::info!(target: LOG_TARGET, "Loading WASM from file"); + let code = fs::read(runtime).map_err(|e| { + format!( + "Could not load runtime file from path: {}, error: {}", + runtime.display(), + e + ) + })?; + let hash = sp_core::blake2_256(&code).to_vec(); + let wrapped_code = WrappedRuntimeCode(Cow::Owned(code)); + + Ok(FetchedCode::FromFile { wrapped_code, heap_pages: self.heap_pages, hash }) + } else { + log::info!(target: LOG_TARGET, "Loading WASM from state"); + let state = sp_state_machine::backend::BackendRuntimeCode::new(state); - Ok(FetchedCode { state }) + Ok(FetchedCode::FromGenesis { state }) + } } /// Allocation strategy for pallet benchmarking. diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs index a507c1d1f54..412a1a86cb8 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -53,6 +53,10 @@ pub struct PalletCmd { #[arg(short, long, required_unless_present_any = ["list", "json_input", "all"], default_value_if("all", "true", Some("*".into())))] pub extrinsic: Option, + /// Comma separated list of pallets that should be excluded from the benchmark. + #[arg(long, value_parser, num_args = 1.., value_delimiter = ',')] + pub exclude_pallets: Vec, + /// Run benchmarks for all pallets and extrinsics. /// /// This is equivalent to running `--pallet * --extrinsic *`. diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs index ce37be455e8..a4799dc9236 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs @@ -18,7 +18,7 @@ //! Various types used by this crate. use sc_cli::Result; -use sp_core::traits::RuntimeCode; +use sp_core::traits::{RuntimeCode, WrappedRuntimeCode}; use sp_runtime::traits::Hash; /// How the genesis state for benchmarking should be build. @@ -43,8 +43,9 @@ pub enum GenesisBuilderPolicy { /// A runtime blob that was either fetched from genesis storage or loaded from a file. // NOTE: This enum is only needed for the annoying lifetime bounds on `RuntimeCode`. Otherwise we // could just directly return the blob. -pub struct FetchedCode<'a, B, H> { - pub state: sp_state_machine::backend::BackendRuntimeCode<'a, B, H>, +pub enum FetchedCode<'a, B, H> { + FromGenesis { state: sp_state_machine::backend::BackendRuntimeCode<'a, B, H> }, + FromFile { wrapped_code: WrappedRuntimeCode<'a>, heap_pages: Option, hash: Vec }, } impl<'a, B, H> FetchedCode<'a, B, H> @@ -54,7 +55,14 @@ where { /// The runtime blob. pub fn code(&'a self) -> Result> { - self.state.runtime_code().map_err(Into::into) + match self { + Self::FromGenesis { state } => state.runtime_code().map_err(Into::into), + Self::FromFile { wrapped_code, heap_pages, hash } => Ok(RuntimeCode { + code_fetcher: wrapped_code, + heap_pages: *heap_pages, + hash: hash.clone(), + }), + } } } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs index 34f31734da6..28918dd4e6a 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -484,9 +484,6 @@ pub(crate) fn write_results( benchmarks: results.clone(), }; - let file_path = fs::canonicalize(&file_path).map_err(|e| { - format!("Could not get absolute path for: {:?}. Error: {:?}", &file_path, e) - })?; let mut output_file = fs::File::create(&file_path).map_err(|e| { format!("Could not write weight file to: {:?}. Error: {:?}", &file_path, e) })?; -- GitLab From 1e5f5fe9086233a464ee249de74e4df959b8eb15 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:16:36 -0300 Subject: [PATCH 073/127] bump zombienet version and set request for k8s (#5968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump zombienet version, including fixes for `ci` and set _resources requests_ for the runner. Thx! Co-authored-by: Bastian Köcher --- .gitlab/pipeline/zombienet.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index b02d857f3f2..2a7e2e7559a 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,12 +1,14 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.113" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.114" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" RUST_LOG: "info,zombienet_orchestrator=debug" RUN_IN_CI: "1" + KUBERNETES_CPU_REQUEST: "512m" + KUBERNETES_MEMORY_REQUEST: "512M" timeout: 60m include: -- GitLab From 5d82bdc428ebc33e642212fc720fc21ff2119412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 8 Oct 2024 22:56:13 +0200 Subject: [PATCH 074/127] Bump some dependencies (#5886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bumps `ethbloom`, `ethereum-types`, `primitive-types` and `rlp` to their latest version. Fixes: https://github.com/paritytech/polkadot-sdk/issues/5870 --------- Co-authored-by: command-bot <> Co-authored-by: ggwpez Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: Dónal Murray --- Cargo.lock | 224 ++++++++++-------- Cargo.toml | 9 +- bridges/primitives/polkadot-core/Cargo.toml | 2 - .../polkadot-core/src/parachains.rs | 5 +- prdoc/pr_5886.prdoc | 18 ++ .../client/consensus/babe/src/authorship.rs | 3 +- substrate/frame/babe/src/mock.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 4 +- substrate/frame/sassafras/src/mock.rs | 2 +- substrate/primitives/runtime/src/testing.rs | 8 +- 10 files changed, 156 insertions(+), 121 deletions(-) create mode 100644 prdoc/pr_5886.prdoc diff --git a/Cargo.lock b/Cargo.lock index 4d5565e433b..34e30d79e98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,7 +900,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "polkadot-runtime-common", - "primitive-types", + "primitive-types 0.13.1", "rococo-runtime-constants", "scale-info", "serde_json", @@ -1033,7 +1033,7 @@ dependencies = [ "parity-scale-codec", "polkadot-parachain-primitives", "polkadot-runtime-common", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "serde_json", "snowbridge-router-primitives", @@ -2048,7 +2048,6 @@ dependencies = [ "frame-system", "hex", "parity-scale-codec", - "parity-util-mem", "scale-info", "serde", "sp-core 28.0.0", @@ -5756,9 +5755,9 @@ dependencies = [ [[package]] name = "ethabi-decode" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d398648d65820a727d6a81e58b962f874473396a047e4c30bafe3240953417" +checksum = "f9af52ec57c5147716872863c2567c886e7d62f539465b94352dbc0108fe5293" dependencies = [ "ethereum-types", "tiny-keccak", @@ -5766,33 +5765,33 @@ dependencies = [ [[package]] name = "ethbloom" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" dependencies = [ "crunchy", "fixed-hash", - "impl-codec", + "impl-codec 0.7.0", "impl-rlp", - "impl-serde", + "impl-serde 0.5.0", "scale-info", "tiny-keccak", ] [[package]] name = "ethereum-types" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" dependencies = [ "ethbloom", "fixed-hash", - "impl-codec", + "impl-codec 0.7.0", "impl-rlp", - "impl-serde", - "primitive-types", + "impl-serde 0.5.0", + "primitive-types 0.13.1", "scale-info", - "uint", + "uint 0.10.0", ] [[package]] @@ -7610,6 +7609,15 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "impl-num-traits" version = "0.1.2" @@ -7618,16 +7626,27 @@ checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" dependencies = [ "integer-sqrt", "num-traits", - "uint", + "uint 0.9.5", +] + +[[package]] +name = "impl-num-traits" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint 0.10.0", ] [[package]] name = "impl-rlp" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" dependencies = [ - "rlp", + "rlp 0.6.1", ] [[package]] @@ -7639,6 +7658,15 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -8366,7 +8394,7 @@ dependencies = [ "pallet-example-tasks", "parity-scale-codec", "polkadot-sdk", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "serde_json", "static_assertions", @@ -8779,7 +8807,7 @@ dependencies = [ "sha2 0.10.8", "smallvec", "thiserror", - "uint", + "uint 0.9.5", "unsigned-varint 0.7.2", "void", ] @@ -9242,7 +9270,7 @@ dependencies = [ "tokio-tungstenite", "tokio-util", "tracing", - "uint", + "uint 0.9.5", "unsigned-varint 0.8.0", "url", "x25519-dalek", @@ -9277,15 +9305,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "lru" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" -dependencies = [ - "hashbrown 0.12.3", -] - [[package]] name = "lru" version = "0.11.0" @@ -10628,7 +10647,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "parity-scale-codec", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "sp-api 26.0.0", "sp-arithmetic 23.0.0", @@ -10649,7 +10668,7 @@ dependencies = [ "pallet-assets", "pallet-balances", "parity-scale-codec", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -12313,7 +12332,7 @@ dependencies = [ "polkavm 0.12.0", "polkavm-common 0.12.0", "pretty_assertions", - "rlp", + "rlp 0.6.1", "scale-info", "serde", "sp-api 26.0.0", @@ -13223,7 +13242,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -13281,35 +13300,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "parity-util-mem" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" -dependencies = [ - "cfg-if", - "ethereum-types", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "lru 0.8.1", - "parity-util-mem-derive", - "parking_lot 0.12.3", - "primitive-types", - "smallvec", - "winapi", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2 1.0.86", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "parity-wasm" version = "0.45.0" @@ -13481,7 +13471,7 @@ dependencies = [ "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", - "primitive-types", + "primitive-types 0.12.2", "scale-info", "smallvec", "sp-api 26.0.0", @@ -16523,12 +16513,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec", - "impl-num-traits", + "impl-codec 0.6.0", + "impl-num-traits 0.1.2", + "impl-serde 0.4.0", + "scale-info", + "uint 0.9.5", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec 0.7.0", + "impl-num-traits 0.2.0", "impl-rlp", - "impl-serde", + "impl-serde 0.5.0", "scale-info", - "uint", + "uint 0.10.0", ] [[package]] @@ -17640,6 +17644,16 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.21.0" @@ -17950,10 +17964,10 @@ dependencies = [ "num-bigint", "num-traits", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "proptest", "rand", - "rlp", + "rlp 0.5.2", "ruint-macro", "serde", "valuable", @@ -19883,7 +19897,7 @@ checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ "derive_more", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-bits", "scale-decode-derive", "scale-type-resolver", @@ -19910,7 +19924,7 @@ checksum = "4ba0b9c48dc0eb20c60b083c29447c0c4617cb7c4a4c9fef72aa5c5bc539e15e" dependencies = [ "derive_more", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-bits", "scale-encode-derive", "scale-type-resolver", @@ -20872,7 +20886,7 @@ dependencies = [ "hex", "hex-literal", "parity-scale-codec", - "rlp", + "rlp 0.6.1", "scale-info", "serde", "snowbridge-ethereum", @@ -20920,7 +20934,7 @@ dependencies = [ "parity-bytes", "parity-scale-codec", "rand", - "rlp", + "rlp 0.6.1", "scale-info", "serde", "serde-big-array", @@ -21478,7 +21492,7 @@ dependencies = [ "integer-sqrt", "num-traits", "parity-scale-codec", - "primitive-types", + "primitive-types 0.13.1", "rand", "scale-info", "serde", @@ -21718,7 +21732,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.11.0", "k256", "libsecp256k1", @@ -21728,7 +21742,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.13.1", "rand", "regex", "scale-info", @@ -21767,7 +21781,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.10.5", "k256", "libsecp256k1", @@ -21777,7 +21791,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.12.2", "rand", "scale-info", "schnorrkel 0.11.4", @@ -21814,7 +21828,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.10.5", "k256", "libsecp256k1", @@ -21824,7 +21838,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.12.2", "rand", "scale-info", "schnorrkel 0.11.4", @@ -21861,7 +21875,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde", + "impl-serde 0.4.0", "itertools 0.11.0", "k256", "libsecp256k1", @@ -21871,7 +21885,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types", + "primitive-types 0.12.2", "rand", "scale-info", "schnorrkel 0.11.4", @@ -22516,7 +22530,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "sp-externalities 0.19.0", "sp-runtime-interface-proc-macro 11.0.0", "sp-std 8.0.0", @@ -22534,7 +22548,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive 0.9.1", - "primitive-types", + "primitive-types 0.13.1", "rustversion", "sp-core 28.0.0", "sp-externalities 0.25.0", @@ -22560,7 +22574,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive 0.8.0", - "primitive-types", + "primitive-types 0.12.2", "sp-externalities 0.27.0", "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -22580,7 +22594,7 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive 0.9.1", - "primitive-types", + "primitive-types 0.12.2", "sp-externalities 0.28.0", "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -22821,7 +22835,7 @@ name = "sp-storage" version = "13.0.0" source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -22833,7 +22847,7 @@ dependencies = [ name = "sp-storage" version = "19.0.0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -22846,7 +22860,7 @@ version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -22860,7 +22874,7 @@ version = "21.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99c82989b3a4979a7e1ad848aad9f5d0b4388f1f454cc131766526601ab9e8f8" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "ref-cast", "serde", @@ -23061,7 +23075,7 @@ dependencies = [ name = "sp-version" version = "29.0.0" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "parity-wasm", "scale-info", @@ -23079,7 +23093,7 @@ version = "35.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff74bf12b4f7d29387eb1caeec5553209a505f90a2511d2831143b970f89659" dependencies = [ - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", "parity-wasm", "scale-info", @@ -23413,7 +23427,7 @@ dependencies = [ "polkadot-primitives", "polkadot-runtime-parachains", "polkadot-test-runtime", - "primitive-types", + "primitive-types 0.13.1", "scale-info", "sp-arithmetic 23.0.0", "sp-core 28.0.0", @@ -24038,11 +24052,11 @@ dependencies = [ "frame-metadata 16.0.0", "futures", "hex", - "impl-serde", + "impl-serde 0.4.0", "instant", "jsonrpsee 0.22.5", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "reconnecting-jsonrpsee-ws-client", "scale-bits", "scale-decode", @@ -24095,9 +24109,9 @@ dependencies = [ "frame-metadata 16.0.0", "hashbrown 0.14.5", "hex", - "impl-serde", + "impl-serde 0.4.0", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "scale-bits", "scale-decode", "scale-encode", @@ -25448,6 +25462,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index bd37dee5146..a52cdb99b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -741,8 +741,8 @@ env_logger = { version = "0.11.2" } environmental = { version = "1.1.4", default-features = false } equivocation-detector = { path = "bridges/relays/equivocation" } ethabi = { version = "1.0.0", default-features = false, package = "ethabi-decode" } -ethbloom = { version = "0.13.0", default-features = false } -ethereum-types = { version = "0.14.1", default-features = false } +ethbloom = { version = "0.14.1", default-features = false } +ethereum-types = { version = "0.15.1", default-features = false } exit-future = { version = "0.2.0" } expander = { version = "2.0.0" } fatality = { version = "0.1.1" } @@ -1003,7 +1003,6 @@ parachains-relay = { path = "bridges/relays/parachains" } parachains-runtimes-test-utils = { path = "cumulus/parachains/runtimes/test-utils", default-features = false } parity-bytes = { version = "0.1.2", default-features = false } parity-db = { version = "0.4.12" } -parity-util-mem = { version = "0.12.0" } parity-wasm = { version = "0.45.0" } parking_lot = { version = "0.12.1", default-features = false } partial_sort = { version = "0.2.0" } @@ -1078,7 +1077,7 @@ polkavm-derive = "0.9.1" polkavm-linker = "0.9.2" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } -primitive-types = { version = "0.12.1", default-features = false } +primitive-types = { version = "0.13.1", default-features = false, features = ["num-traits"] } proc-macro-crate = { version = "3.0.0" } proc-macro-warning = { version = "1.0.0", default-features = false } proc-macro2 = { version = "1.0.86" } @@ -1106,7 +1105,7 @@ relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } reqwest = { version = "0.11", default-features = false } -rlp = { version = "0.5.2", default-features = false } +rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } rococo-parachain-runtime = { path = "cumulus/parachains/runtimes/testing/rococo-parachain" } rococo-runtime = { path = "polkadot/runtime/rococo" } diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index 366ee7aa948..295fb281e9b 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -12,7 +12,6 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -parity-util-mem = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = [ "derive", @@ -42,7 +41,6 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", - "parity-util-mem", "scale-info/std", "serde", "sp-core/std", diff --git a/bridges/primitives/polkadot-core/src/parachains.rs b/bridges/primitives/polkadot-core/src/parachains.rs index d54ee108386..a8b1cf6eebf 100644 --- a/bridges/primitives/polkadot-core/src/parachains.rs +++ b/bridges/primitives/polkadot-core/src/parachains.rs @@ -32,9 +32,6 @@ use sp_std::vec::Vec; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use parity_util_mem::MallocSizeOf; - /// Parachain id. /// /// This is an equivalent of the `polkadot_parachain_primitives::Id`, which is a compact-encoded @@ -71,7 +68,7 @@ impl From for ParaId { #[derive( PartialEq, Eq, Clone, PartialOrd, Ord, Encode, Decode, RuntimeDebug, TypeInfo, Default, )] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, MallocSizeOf))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash))] pub struct ParaHead(pub Vec); impl ParaHead { diff --git a/prdoc/pr_5886.prdoc b/prdoc/pr_5886.prdoc new file mode 100644 index 00000000000..f5e59728119 --- /dev/null +++ b/prdoc/pr_5886.prdoc @@ -0,0 +1,18 @@ +title: Bump some dependencies +doc: +- audience: Runtime Dev + description: |- + This bumps `ethbloom`, `ethereum-types`, `primitive-types` and `rlp` to their latest version. + + Fixes: https://github.com/paritytech/polkadot-sdk/issues/5870 +crates: +- name: sc-consensus-babe + bump: patch +- name: pallet-babe + bump: patch +- name: pallet-revive + bump: patch +- name: sp-runtime + bump: patch +- name: bp-polkadot-core + bump: major diff --git a/substrate/client/consensus/babe/src/authorship.rs b/substrate/client/consensus/babe/src/authorship.rs index 57ee706a04f..06394b84f47 100644 --- a/substrate/client/consensus/babe/src/authorship.rs +++ b/substrate/client/consensus/babe/src/authorship.rs @@ -108,7 +108,8 @@ pub(super) fn secondary_slot_author( return None } - let rand = U256::from((randomness, slot).using_encoded(sp_crypto_hashing::blake2_256)); + let rand = + U256::from_little_endian(&(randomness, slot).using_encoded(sp_crypto_hashing::blake2_256)); let authorities_len = U256::from(authorities.len()); let idx = rand % authorities_len; diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 4e4052b2b56..d416e31b25f 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -291,7 +291,7 @@ pub fn new_test_ext_with_pairs( authorities_len: usize, ) -> (Vec, sp_io::TestExternalities) { let pairs = (0..authorities_len) - .map(|i| AuthorityPair::from_seed(&U256::from(i).into())) + .map(|i| AuthorityPair::from_seed(&U256::from(i).to_little_endian())) .collect::>(); let public = pairs.iter().map(|p| p.public()).collect(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 485abbfda22..245c91278a7 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -46,9 +46,7 @@ const MAX_DECODE_NESTING: u32 = 256; /// Encode a `U256` into a 32 byte buffer. fn as_bytes(u: U256) -> [u8; 32] { - let mut bytes = [0u8; 32]; - u.to_little_endian(&mut bytes); - bytes + u.to_little_endian() } #[derive(Clone, Copy)] diff --git a/substrate/frame/sassafras/src/mock.rs b/substrate/frame/sassafras/src/mock.rs index f145bffa3a0..d2b329e8a2b 100644 --- a/substrate/frame/sassafras/src/mock.rs +++ b/substrate/frame/sassafras/src/mock.rs @@ -89,7 +89,7 @@ pub fn new_test_ext_with_pairs( with_ring_context: bool, ) -> (Vec, sp_io::TestExternalities) { let pairs = (0..authorities_len) - .map(|i| AuthorityPair::from_seed(&U256::from(i).into())) + .map(|i| AuthorityPair::from_seed(&U256::from(i).to_big_endian())) .collect::>(); let authorities: Vec<_> = pairs.iter().map(|p| p.public()).collect(); diff --git a/substrate/primitives/runtime/src/testing.rs b/substrate/primitives/runtime/src/testing.rs index b4aeda5a0e7..a4ce4b5fc1a 100644 --- a/substrate/primitives/runtime/src/testing.rs +++ b/substrate/primitives/runtime/src/testing.rs @@ -29,10 +29,7 @@ use crate::{ ApplyExtrinsicResultWithInfo, KeyTypeId, }; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; -use sp_core::{ - crypto::{key_types, ByteArray, CryptoType, Dummy}, - U256, -}; +use sp_core::crypto::{key_types, ByteArray, CryptoType, Dummy}; pub use sp_core::{sr25519, H256}; use std::{ cell::RefCell, @@ -79,7 +76,8 @@ impl From for u64 { impl UintAuthorityId { /// Convert this authority ID into a public key. pub fn to_public_key(&self) -> T { - let bytes: [u8; 32] = U256::from(self.0).into(); + let mut bytes = [0u8; 32]; + bytes[0..8].copy_from_slice(&self.0.to_le_bytes()); T::from_slice(&bytes).unwrap() } } -- GitLab From 6c2b46f9620fd147e63b151320cc145551fd2c18 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 9 Oct 2024 03:14:09 -0300 Subject: [PATCH 075/127] Disable flaky tests reported in 5972/5973/5974 (#5976) Disable flaky tests reported in: #5972 #5973 #5974 --- .gitlab/pipeline/zombienet/polkadot.yml | 6 +++--- .gitlab/pipeline/zombienet/substrate.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 0da95f224cf..33191d99941 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -94,7 +94,7 @@ zombienet-polkadot-functional-0002-parachains-disputes: --local-dir="${LOCAL_DIR}/functional" --test="0002-parachains-disputes.zndsl" -zombienet-polkadot-functional-0003-beefy-and-mmr: +.zombienet-polkadot-functional-0003-beefy-and-mmr: extends: - .zombienet-polkadot-common script: @@ -190,7 +190,7 @@ zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: --local-dir="${LOCAL_DIR}/elastic_scaling" --test="0002-elastic-scaling-doesnt-break-parachains.zndsl" -zombienet-polkadot-functional-0012-spam-statement-distribution-requests: +.zombienet-polkadot-functional-0012-spam-statement-distribution-requests: extends: - .zombienet-polkadot-common script: @@ -277,7 +277,7 @@ zombienet-polkadot-smoke-0002-parachains-parachains-upgrade-smoke: --local-dir="${LOCAL_DIR}/smoke" --test="0002-parachains-upgrade-smoke-test.zndsl" -zombienet-polkadot-smoke-0003-deregister-register-validator: +.zombienet-polkadot-smoke-0003-deregister-register-validator: extends: - .zombienet-polkadot-common script: diff --git a/.gitlab/pipeline/zombienet/substrate.yml b/.gitlab/pipeline/zombienet/substrate.yml index d4ffa34f339..030a0a3f50a 100644 --- a/.gitlab/pipeline/zombienet/substrate.yml +++ b/.gitlab/pipeline/zombienet/substrate.yml @@ -70,7 +70,7 @@ zombienet-substrate-0001-basic-warp-sync: --local-dir="${LOCAL_DIR}/0001-basic-warp-sync" --test="test-warp-sync.zndsl" -zombienet-substrate-0002-validators-warp-sync: +.zombienet-substrate-0002-validators-warp-sync: extends: - .zombienet-substrate-warp-sync-common before_script: -- GitLab From 6765bcd8a34bc47b4f8ad0c5c9f00574e3b1b9af Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:16:55 +0200 Subject: [PATCH 076/127] [ci] Remove short-benchmarks from Gitlab (#5988) PR removes short-benchmarks from GitLab, adds condition for cargo-check-rutimes --- .../workflows/check-cargo-check-runtimes.yml | 3 +- .gitlab-ci.yml | 7 - .gitlab/pipeline/build.yml | 102 +-------------- .gitlab/pipeline/short-benchmarks.yml | 122 +----------------- 4 files changed, 4 insertions(+), 230 deletions(-) diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index 7e9f0dc77b7..b49a2cbbfd3 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -7,11 +7,12 @@ concurrency: on: pull_request: types: [opened, synchronize, reopened, ready_for_review] + paths: + - "cumulus/parachains/runtimes/*" # Jobs in this workflow depend on each other, only for limiting peak amount of spawned workers jobs: - preflight: uses: ./.github/workflows/reusable-preflight.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dbc5dafeb0a..f508404f1ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -225,8 +225,6 @@ include: - .gitlab/pipeline/test.yml # build jobs - .gitlab/pipeline/build.yml - # short-benchmarks jobs - - .gitlab/pipeline/short-benchmarks.yml # publish jobs - .gitlab/pipeline/publish.yml # zombienet jobs @@ -284,8 +282,3 @@ cancel-pipeline-build-linux-substrate: extends: .cancel-pipeline-template needs: - job: build-linux-substrate - -cancel-pipeline-build-short-benchmark: - extends: .cancel-pipeline-template - needs: - - job: build-short-benchmark diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 0d509879b44..179eebf8b50 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -160,19 +160,6 @@ build-implementers-guide: - mkdir -p artifacts - mv polkadot/roadmap/implementers-guide/book artifacts/ -build-short-benchmark: - stage: build - extends: - - .docker-env - - .common-refs - - .run-immediately - - .collect-artifacts - script: - - cargo build --profile release --locked --features=runtime-benchmarks,on-chain-release-build --bin polkadot --workspace - - mkdir -p artifacts - - target/release/polkadot --version - - cp ./target/release/polkadot ./artifacts/ - build-polkadot-zombienet-tests: stage: build extends: @@ -191,7 +178,6 @@ build-polkadot-zombienet-tests: - mkdir -p artifacts - cp polkadot-zombienet-tests.tar.zst ./artifacts - # build jobs from cumulus build-linux-stable-cumulus: @@ -234,92 +220,6 @@ build-test-parachain: - mkdir -p ./artifacts/zombienet - mv ./target/release/wbuild/cumulus-test-runtime/wasm_binary_spec_version_incremented.rs.compact.compressed.wasm ./artifacts/zombienet/. -# build runtime only if files in $RUNTIME_PATH/$RUNTIME_NAME were changed -.build-runtime-template: &build-runtime-template - stage: build - extends: - - .docker-env - - .test-refs-no-trigger-prs-only - - .run-immediately - variables: - RUNTIME_PATH: "parachains/runtimes/assets" - script: - - cd ${RUNTIME_PATH} - - for directory in $(echo */); do - echo "_____Running cargo check for ${directory} ______"; - cd ${directory}; - pwd; - SKIP_WASM_BUILD=1 cargo check --locked; - cd ..; - done - -# DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-bridge-hubs -# DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-contracts -# DAG: build-runtime-assets -> build-runtime-coretime -# DAG: build-runtime-assets -> build-runtime-testing -build-runtime-assets: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/assets" - -build-runtime-collectives: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/collectives" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-assets - artifacts: false - -build-runtime-coretime: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/coretime" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-assets - artifacts: false - -build-runtime-bridge-hubs: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/bridge-hubs" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-collectives - artifacts: false - -build-runtime-contracts: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/contracts" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-collectives - artifacts: false - -build-runtime-testing: - <<: *build-runtime-template - variables: - RUNTIME_PATH: "cumulus/parachains/runtimes/testing" - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: build-runtime-assets - artifacts: false - -build-short-benchmark-cumulus: - stage: build - extends: - - .docker-env - - .common-refs - - .run-immediately - - .collect-artifacts - script: - - cargo build --profile release --locked --features=runtime-benchmarks,on-chain-release-build -p polkadot-parachain-bin --bin polkadot-parachain --workspace - - mkdir -p artifacts - - target/release/polkadot-parachain --version - - cp ./target/release/polkadot-parachain ./artifacts/ - # substrate build-linux-substrate: @@ -340,7 +240,7 @@ 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 ] + - !reference [.forklift-cache, before_script] script: - time WASM_BUILD_NO_COLOR=1 cargo build --locked --release -p staging-node-cli - mv $CARGO_TARGET_DIR/release/substrate-node ./artifacts/substrate/substrate diff --git a/.gitlab/pipeline/short-benchmarks.yml b/.gitlab/pipeline/short-benchmarks.yml index ad09f3f7cb0..ed97d539c09 100644 --- a/.gitlab/pipeline/short-benchmarks.yml +++ b/.gitlab/pipeline/short-benchmarks.yml @@ -1,121 +1 @@ -# This file is part of .gitlab-ci.yml -# Here are all jobs that are executed during "short-benchmarks" stage - -# Run all pallet benchmarks only once to check if there are any errors - -# run short-benchmarks for relay chain runtimes from polkadot - -short-benchmark-westend: &short-bench - stage: short-benchmarks - extends: - - .docker-env - - .common-refs - needs: - - job: build-short-benchmark - artifacts: true - variables: - RUNTIME: westend - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - tags: - - benchmark - script: - - ./artifacts/polkadot benchmark pallet --chain $RUNTIME-dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 - -short-benchmark-rococo: &short-bench - stage: short-benchmarks - extends: - - .docker-env - - .common-refs - needs: - - job: build-short-benchmark - artifacts: true - variables: - RUNTIME: rococo - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - tags: - - benchmark - script: - - ./artifacts/polkadot benchmark pallet --chain $RUNTIME-dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 - -# run short-benchmarks for system parachain runtimes from cumulus - -.short-benchmark-cumulus: &short-bench-cumulus - stage: short-benchmarks - extends: - - .common-refs - - .docker-env - needs: - - job: build-short-benchmark-cumulus - artifacts: true - variables: - RUNTIME_CHAIN: benchmarked-runtime-chain - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions -D warnings" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" - tags: - - benchmark - script: - - ./artifacts/polkadot-parachain benchmark pallet --chain $RUNTIME_CHAIN --pallet "*" --extrinsic "*" --steps 2 --repeat 1 - -short-benchmark-asset-hub-rococo: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: asset-hub-rococo-dev - -short-benchmark-asset-hub-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: asset-hub-westend-dev - -short-benchmark-bridge-hub-rococo: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: bridge-hub-rococo-dev - -short-benchmark-bridge-hub-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: bridge-hub-westend-dev - -short-benchmark-collectives-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: collectives-westend-dev - -short-benchmark-coretime-rococo: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: coretime-rococo-dev - -short-benchmark-coretime-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: coretime-westend-dev - -short-benchmark-people-rococo: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: people-rococo-dev - -short-benchmark-people-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: people-westend-dev - -short-benchmark-glutton-westend: - <<: *short-bench-cumulus - variables: - RUNTIME_CHAIN: glutton-westend-dev-1300 +--- -- GitLab From c477076202aa64ab5dbd4dc43cadbb3020d3753f Mon Sep 17 00:00:00 2001 From: Egor_P Date: Wed, 9 Oct 2024 15:55:53 +0200 Subject: [PATCH 077/127] [Release/CI] Github flow to build `polkadot`/`polkadot-parachain` rc binaries and deb package (#5963) This PR introduces a GitHub flow, that should replace a semi manual part of the release process to build rc binaries for `polkadot` and `polkadot-parachain` + the `polkadot` deb package. Right now, this part of the release is done on the `cleanroom` machine by the release engineers via triggering bash scripts directly on the server. These GitHub flows should replace it and move everything to the CI. The whole flow is meant to be run in the new [paritytech-release](https://github.com/paritytech-release) where the automated release is going to be moved. The flow includes the following steps: - Build `polkadot`, `polakdot-prepare-worker`, `polkadot-execute-worker` and `polkadopt-parachain` binaries - Sign those artefacts using `gpg` and generate a sha256 checksum - Build deb package for `polakdot` - Make a GitHub attestation - Upload artefacts to the S3 buckets Closes: https://github.com/paritytech/release-engineering/issues/223 --------- Co-authored-by: Oliver Tale-Yazdi --- .github/scripts/release/build-deb.sh | 17 ++ .../scripts/release/build-linux-release.sh | 34 ++++ .github/scripts/release/release_lib.sh | 21 ++ .github/workflows/release-build-rc.yml | 74 +++++++ .../workflows/release-reusable-rc-buid.yml | 191 ++++++++++++++++++ .../workflows/release-reusable-s3-upload.yml | 53 +++++ 6 files changed, 390 insertions(+) create mode 100755 .github/scripts/release/build-deb.sh create mode 100755 .github/scripts/release/build-linux-release.sh create mode 100644 .github/workflows/release-build-rc.yml create mode 100644 .github/workflows/release-reusable-rc-buid.yml create mode 100644 .github/workflows/release-reusable-s3-upload.yml diff --git a/.github/scripts/release/build-deb.sh b/.github/scripts/release/build-deb.sh new file mode 100755 index 00000000000..6cb833f98a4 --- /dev/null +++ b/.github/scripts/release/build-deb.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -e + +PRODUCT=$1 +VERSION=$2 +PROFILE=${PROFILE:-production} + +cargo install --version 2.7.0 cargo-deb --locked -q +echo "Using cargo-deb v$(cargo-deb --version)" +echo "Building a Debian package for '$PRODUCT' in '$PROFILE' profile" + +# we need to start the custom version with a didgit as requires it cargo-deb +cargo deb --profile $PROFILE --no-strip --no-build -p $PRODUCT --deb-version 1-$VERSION + +deb=target/debian/$PRODUCT_*_amd64.deb + +cp $deb target/production/ diff --git a/.github/scripts/release/build-linux-release.sh b/.github/scripts/release/build-linux-release.sh new file mode 100755 index 00000000000..a6bd658d292 --- /dev/null +++ b/.github/scripts/release/build-linux-release.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# This is used to build our binaries: +# - polkadot +# - polkadot-parachain +# set -e + +BIN=$1 +PACKAGE=${2:-$BIN} + +PROFILE=${PROFILE:-production} +ARTIFACTS=/artifacts/$BIN +VERSION=$(git tag -l --contains HEAD | grep -E "^v.*") + +echo "Artifacts will be copied into $ARTIFACTS" +mkdir -p "$ARTIFACTS" + +git log --pretty=oneline -n 1 +time cargo build --profile $PROFILE --locked --verbose --bin $BIN --package $PACKAGE + +echo "Artifact target: $ARTIFACTS" + +cp ./target/$PROFILE/$BIN "$ARTIFACTS" +pushd "$ARTIFACTS" > /dev/nul +sha256sum "$BIN" | tee "$BIN.sha256" + +EXTRATAG="$($ARTIFACTS/$BIN --version | + sed -n -r 's/^'$BIN' ([0-9.]+.*-[0-9a-f]{7,13})-.*$/\1/p')" + +EXTRATAG="${VERSION}-${EXTRATAG}-$(cut -c 1-8 $ARTIFACTS/$BIN.sha256)" + +echo "$BIN version = ${VERSION} (EXTRATAG = ${EXTRATAG})" +echo -n ${VERSION} > "$ARTIFACTS/VERSION" +echo -n ${EXTRATAG} > "$ARTIFACTS/EXTRATAG" diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh index 81a3c14edec..f5032073b61 100644 --- a/.github/scripts/release/release_lib.sh +++ b/.github/scripts/release/release_lib.sh @@ -116,3 +116,24 @@ set_polkadot_parachain_binary_version() { commit_with_message "$MESSAGE" git_show_log "$MESSAGE" } + + +upload_s3_release() { + alias aws='podman run --rm -it docker.io/paritytech/awscli -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_BUCKET aws' + + product=$1 + version=$2 + + echo "Working on product: $product " + echo "Working on version: $version " + + echo "Current content, should be empty on new uploads:" + aws s3 ls "s3://releases.parity.io/polkadot/${version}/" --recursive --human-readable --summarize || true + echo "Content to be uploaded:" + artifacts="artifacts/$product/" + ls "$artifacts" + aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/polkadot/${version}/" + echo "Uploaded files:" + aws s3 ls "s3://releases.parity.io/polkadot/${version}/" --recursive --human-readable --summarize + echo "✅ The release should be at https://releases.parity.io/polkadot/${version}" +} diff --git a/.github/workflows/release-build-rc.yml b/.github/workflows/release-build-rc.yml new file mode 100644 index 00000000000..5c25e3c749b --- /dev/null +++ b/.github/workflows/release-build-rc.yml @@ -0,0 +1,74 @@ +name: Release - Build node release candidate + +on: + workflow_dispatch: + inputs: + binary: + description: Binary to be build for the release + default: all + type: choice + options: + - polkadot + - polkadot-parachain + - all + + release_tag: + description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM + type: string + +jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [check-synchronization] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.release_tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + build-polkadot-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot", "polkadot-prepare-worker", "polkadot-execute-worker"]' + package: polkadot + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + build-polkadot-parachain-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot-parachain"]' + package: "polkadot-parachain-bin" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml new file mode 100644 index 00000000000..2aee9dc995b --- /dev/null +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -0,0 +1,191 @@ +name: RC Build + +on: + workflow_call: + inputs: + binary: + description: Binary to be build for the release + required: true + default: polkadot + type: string + + package: + description: Package to be built, for now is either polkadot or polkadot-parachain-bin + required: true + type: string + + release_tag: + description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM + required: true + type: string + + secrets: + PGP_KMS_KEY: + required: true + PGP_KMS_HASH: + required: true + AWS_ACCESS_KEY_ID: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + AWS_DEFAULT_REGION: + required: true + AWS_RELEASE_ACCESS_KEY_ID: + required: true + AWS_RELEASE_SECRET_ACCESS_KEY: + required: true + +permissions: + id-token: write + contents: read + attestations: write + +jobs: + + set-image: + # GitHub Actions allows using 'env' in a container context. + # However, env variables don't work for forks: https://github.com/orgs/community/discussions/44322 + # This workaround sets the container image for each job using 'set-image' job output. + runs-on: ubuntu-latest + outputs: + IMAGE: ${{ steps.set_image.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - id: set_image + run: cat .github/env >> $GITHUB_OUTPUT + + build-rc: + needs: [set-image] + runs-on: ubuntu-latest + environment: release + container: + image: ${{ needs.set-image.outputs.IMAGE }} + strategy: + matrix: + binaries: ${{ fromJSON(inputs.binary) }} + env: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + + steps: + - name: Install pgpkkms + run: | + # Install pgpkms that is used to sign built artifacts + python3 -m pip install "pgpkms @ git+https://github.com/paritytech-release/pgpkms.git@5a8f82fbb607ea102d8c178e761659de54c7af69" + which pgpkms + + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + ref: ${{ inputs.release_tag }} + fetch-depth: 0 + + - name: Import gpg keys + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + import_gpg_keys + + - name: Build binary + run: | + git config --global --add safe.directory "${GITHUB_WORKSPACE}" #avoid "detected dubious ownership" error + ./.github/scripts/release/build-linux-release.sh ${{ matrix.binaries }} ${{ inputs.package }} + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: /artifacts/${{ matrix.binaries }}/${{ matrix.binaries }} + + - name: Sign artifacts + working-directory: /artifacts/${{ matrix.binaries }} + run: | + python3 -m pgpkms sign --input ${{matrix.binaries }} -o ${{ matrix.binaries }}.asc + + - name: Check sha256 ${{ matrix.binaries }} + working-directory: /artifacts/${{ matrix.binaries }} + shell: bash + run: | + . "${GITHUB_WORKSPACE}"/.github/scripts/common/lib.sh + + echo "Checking binary ${{ matrix.binaries }}" + check_sha256 ${{ matrix.binaries }} + + - name: Check GPG ${{ matrix.binaries }} + working-directory: /artifacts/${{ matrix.binaries }} + shell: bash + run: | + . "${GITHUB_WORKSPACE}"/.github/scripts/common/lib.sh + + check_gpg ${{ matrix.binaries }} + + - name: Upload ${{ matrix.binaries }} artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: ${{ matrix.binaries }} + path: /artifacts/${{ matrix.binaries }} + + build-polkadot-deb-package: + if: ${{ inputs.package == 'polkadot' }} + needs: [build-rc] + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + ref: ${{ inputs.release_tag }} + fetch-depth: 0 + + - name: Download artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + path: target/production + merge-multiple: true + + - name: Build polkadot deb package + shell: bash + run: | + . "${GITHUB_WORKSPACE}"/.github/scripts/release/build-deb.sh ${{ inputs.package }} ${{ inputs.release_tag }} + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: target/production/*.deb + + - name: Upload ${{inputs.package }} artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: ${{ inputs.package }} + path: target/production + overwrite: true + + upload-polkadot-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot' }} + needs: [build-polkadot-deb-package] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + + upload-polkadot-parachain-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot-parachain-bin' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.binary }} + release_tag: ${{ inputs.release_tag }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-reusable-s3-upload.yml b/.github/workflows/release-reusable-s3-upload.yml new file mode 100644 index 00000000000..6776b78da8e --- /dev/null +++ b/.github/workflows/release-reusable-s3-upload.yml @@ -0,0 +1,53 @@ +name: Upload to s3 + +on: + workflow_call: + inputs: + package: + description: Package to be built, for now is either polkadot or polkadot-parachain-bin + required: true + type: string + + release_tag: + description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM-rcX + required: true + type: string + + secrets: + AWS_DEFAULT_REGION: + required: true + AWS_RELEASE_ACCESS_KEY_ID: + required: true + AWS_RELEASE_SECRET_ACCESS_KEY: + required: true + +jobs: + upload-artifacts-to-s3: + runs-on: ubuntu-latest + environment: release + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Download artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: ${{ inputs.package }} + path: artifacts/${{ inputs.package }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Upload ${{ inputs.package }} artifacts to s3 + run: | + . ./.github/scripts/release/release_lib.sh + upload_s3_release ${{ inputs.package }} ${{ inputs.release_tag }} -- GitLab From 3ad12919cbea732fbf66e4f7e8dad1132d51ba84 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Wed, 9 Oct 2024 16:11:12 +0200 Subject: [PATCH 078/127] Bump PoV request timeout (#5924) # Description We previously set the PoV request timeout to 1.2s based on synchronous backing, which allowed for 5 PoVs per relay block. With asynchronous backing, we no longer have a time budget and can increase the value to 2s. Fixes https://github.com/paritytech/polkadot-sdk/issues/5885 ## Integration This PR shouldn't affect downstream projects. ## Review Notes This PR can be followed by experiments with Gluttons on Kusama to confirm that the timeout is sufficient. --- .../network/protocol/src/request_response/mod.rs | 10 ++++++---- prdoc/pr_5924.prdoc | 13 +++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_5924.prdoc diff --git a/polkadot/node/network/protocol/src/request_response/mod.rs b/polkadot/node/network/protocol/src/request_response/mod.rs index b498de55dce..296c462b508 100644 --- a/polkadot/node/network/protocol/src/request_response/mod.rs +++ b/polkadot/node/network/protocol/src/request_response/mod.rs @@ -123,10 +123,12 @@ const DEFAULT_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_secs(1); /// Timeout for requesting availability chunks. pub const CHUNK_REQUEST_TIMEOUT: Duration = DEFAULT_REQUEST_TIMEOUT_CONNECTED; -/// This timeout is based on what seems sensible from a time budget perspective, considering 6 -/// second block time. This is going to be tough, if we have multiple forks and large PoVs, but we -/// only have so much time. -const POV_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_millis(1200); +/// This timeout is based on the following parameters, assuming we use asynchronous backing with no +/// time budget within a relay block: +/// - 500 Mbit/s networking speed +/// - 10 MB PoV +/// - 10 parallel executions +const POV_REQUEST_TIMEOUT_CONNECTED: Duration = Duration::from_millis(2000); /// We want timeout statement requests fast, so we don't waste time on slow nodes. Responders will /// try their best to either serve within that timeout or return an error immediately. (We need to diff --git a/prdoc/pr_5924.prdoc b/prdoc/pr_5924.prdoc new file mode 100644 index 00000000000..26bde8eec0d --- /dev/null +++ b/prdoc/pr_5924.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: Bump PoV request timeout + +doc: + - audience: Node Dev + description: | + With asynchronous backing and PoV size 10MB, we can increase the PoV request timeout from 1.2s to 2s. + +crates: + - name: polkadot-node-network-protocol + bump: patch -- GitLab From 48b56aa1780c23fa579904d01ee2e442c10863d2 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:37:44 +0200 Subject: [PATCH 079/127] [ci] Move test-linux-stable-no-try-runtime to GHA (#5979) PR moves `test-linux-stable-no-try-runtime` from gitlab to github. I disabled two tests because our current runners don't have necessary syscalls enabled. Will continue working on it in https://github.com/paritytech/ci_cd/issues/1056 Also PR remove `gh cli` installation since it's installed in the `ci-unified` image. close https://github.com/paritytech/ci_cd/issues/1023 --- .github/actions/set-up-gh/action.yml | 38 ++++++++++-------------- .github/workflows/check-semver.yml | 9 +++--- .github/workflows/tests-linux-stable.yml | 36 ++++++++++++++++++++++ .gitlab/pipeline/test.yml | 25 ---------------- 4 files changed, 55 insertions(+), 53 deletions(-) diff --git a/.github/actions/set-up-gh/action.yml b/.github/actions/set-up-gh/action.yml index fc16ce0b263..4dc3af4a19f 100644 --- a/.github/actions/set-up-gh/action.yml +++ b/.github/actions/set-up-gh/action.yml @@ -1,5 +1,5 @@ -name: 'install gh' -description: 'Install the gh cli in a debian based distro and switches to the PR branch.' +name: "install gh" +description: "Install the gh cli in a debian based distro and switches to the PR branch." inputs: pr-number: description: "Number of the PR" @@ -9,28 +9,20 @@ inputs: required: true outputs: branch: - description: 'Branch name for the PR' + description: "Branch name for the PR" value: ${{ steps.branch.outputs.branch }} runs: using: "composite" steps: - - name: Instal gh cli - shell: bash - # Here it would get the script from previous step - run: | - (type -p wget >/dev/null || (apt update && apt-get install wget -y)) - mkdir -p -m 755 /etc/apt/keyrings - wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null - apt update - apt install gh -y - git config --global --add safe.directory '*' - - run: gh pr checkout ${{ inputs.pr-number }} - shell: bash - env: - GITHUB_TOKEN: ${{ inputs.GH_TOKEN }} - - name: Export branch name - shell: bash - run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" - id: branch + - name: Set up git + shell: bash + # Here it would get the script from previous step + run: git config --global --add safe.directory '*' + - run: gh pr checkout ${{ inputs.pr-number }} + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.GH_TOKEN }} + - name: Export branch name + shell: bash + run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" + id: branch diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index b5866e0ce41..811ec4d5558 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -13,10 +13,13 @@ env: TOOLCHAIN: nightly-2024-06-01 jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml check-semver: runs-on: ubuntu-latest + needs: [preflight] container: - image: docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 + image: ${{ needs.preflight.outputs.IMAGE }} steps: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 with: @@ -36,10 +39,6 @@ jobs: run: | echo "This is a backport into stable." - wget -q https://github.com/cli/cli/releases/download/v2.51.0/gh_2.51.0_linux_amd64.tar.gz -O gh.tar.gz && \ - tar -xzf gh.tar.gz && mv gh_2.51.0_linux_amd64/bin/gh /usr/local/bin/gh && rm gh.tar.gz - chmod +x /usr/local/bin/gh - cat > msg.txt < - time cargo nextest run \ - --workspace \ - --locked \ - --release \ - --no-fail-fast \ - --cargo-quiet \ - --features experimental,riscv,ci-only-tests - test-doc: stage: test extends: -- GitLab From 292bfac4147f3065d85bd267a34a408461f6e357 Mon Sep 17 00:00:00 2001 From: ordian Date: Wed, 9 Oct 2024 17:30:15 +0200 Subject: [PATCH 080/127] Fix u256 conversion in BABE (#5994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/paritytech/polkadot-sdk/pull/5886#discussion_r1793423401 --------- Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- prdoc/pr_5994.prdoc | 3 +++ .../client/consensus/babe/src/authorship.rs | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5994.prdoc diff --git a/prdoc/pr_5994.prdoc b/prdoc/pr_5994.prdoc new file mode 100644 index 00000000000..425653e5264 --- /dev/null +++ b/prdoc/pr_5994.prdoc @@ -0,0 +1,3 @@ +crates: + - name: sc-consensus-babe + bump: none diff --git a/substrate/client/consensus/babe/src/authorship.rs b/substrate/client/consensus/babe/src/authorship.rs index 06394b84f47..aa54da2a443 100644 --- a/substrate/client/consensus/babe/src/authorship.rs +++ b/substrate/client/consensus/babe/src/authorship.rs @@ -109,7 +109,7 @@ pub(super) fn secondary_slot_author( } let rand = - U256::from_little_endian(&(randomness, slot).using_encoded(sp_crypto_hashing::blake2_256)); + U256::from_big_endian(&(randomness, slot).using_encoded(sp_crypto_hashing::blake2_256)); let authorities_len = U256::from(authorities.len()); let idx = rand % authorities_len; @@ -272,7 +272,9 @@ fn claim_primary_slot( #[cfg(test)] mod tests { use super::*; - use sp_consensus_babe::{AllowedSlots, AuthorityId, BabeEpochConfiguration, Epoch}; + use sp_consensus_babe::{ + AllowedSlots, AuthorityId, BabeEpochConfiguration, Epoch, RANDOMNESS_LENGTH, + }; use sp_core::{crypto::Pair as _, sr25519::Pair}; use sp_keystore::testing::MemoryKeystore; @@ -306,4 +308,18 @@ mod tests { epoch.authorities.push((valid_public_key.into(), 10)); assert_eq!(claim_slot(10.into(), &epoch, &keystore).unwrap().1, valid_public_key.into()); } + + #[test] + fn secondary_slot_author_selection_works() { + let authorities = (0..1000) + .map(|i| (AuthorityId::from(Pair::generate().0.public()), i)) + .collect::>(); + + let randomness = [3; RANDOMNESS_LENGTH]; + + assert_eq!( + *secondary_slot_author(100.into(), &authorities, randomness).unwrap(), + authorities[167].0 + ); + } } -- GitLab From 90ff47d989b0498e4e88366b432486e06b1398d8 Mon Sep 17 00:00:00 2001 From: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:54:50 +0200 Subject: [PATCH 081/127] Snowbridge V2 docs (#5902) Here are MD docs for V2 @acatangiu @franciscoaguirre . Let me know what you think. --------- Co-authored-by: Adrian Catangiu Co-authored-by: Francisco Aguirre Co-authored-by: Alistair Singh --- bridges/snowbridge/docs/v2.md | 356 ++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 bridges/snowbridge/docs/v2.md diff --git a/bridges/snowbridge/docs/v2.md b/bridges/snowbridge/docs/v2.md new file mode 100644 index 00000000000..8ec440c47ce --- /dev/null +++ b/bridges/snowbridge/docs/v2.md @@ -0,0 +1,356 @@ +# Snowbridge V2 + +This design lowers fees, improves UX, improves relayer decentralization and allows "transacting" over the bridge, making +it a general-purpose bridge rather than just a token bridge. + +We're grateful to Adrian Catangiu, Francisco Aguirre, and others from the Parity XCM/Bridges team for their help and +collaboration on this design. + +## Summary + +- Unordered messaging +- All messages routed through AH +- Off-chain fee estimation +- P→E Fee Asset: WETH +- E→P Fee Asset: ETH +- Relayer rewards for both directions paid out on AH in WETH + +## Polkadot→Ethereum + +Given source parachain $S$, with native token $S^{'}$ and the initial xcm $x_0$ to be executed on $S$. + +### Step 1: User agent constructs initial XCM + +The user agent constructs an initial XCM message $x_0$ that will be executed on S. + +The fee amounts in this message should be high enough to enable dry-running, after which they will be lowered. + +### Step 2: User agent estimates fees + +- Given source parachain $S$, with native token $S^{'}$ and the initial xcm $x_0$ to be executed on $S$. +- The native currency $P^{'}$ (DOT) of the Polkadot relay chain, and $E^{'}$ (ETH) of Ethereum. +- Suppose that the user agent chooses relayer reward $r$ in $E^{'}$. +- Suppose that the exchange rates are $K_{P^{'}/S^{'}}$ and $K_{E^{'}/S^{'}}$. The user agent chooses a multiplier to + $\beta$ to cover volatility in these rates. + +Apply the following sequence operations: + +1. Dry-run $x_0$ on $S$ to receive xcm $x_1$ and cost $a$ in $S^{'}$ +2. Dry-run $x_1$ on AH to receive xcm $x_2$ and cost $b$ in $P^{'}$ (DOT) +3. Dry-run $x_2$ on BH to receive command $m$ and cost $c$ in $P^{'}$ (DOT) +4. Dry-run $m$ on Ethereum to receive cost $d$ in $E^{'}$ (ETH) + +The final cost to the user in $S^{'}$ is given by + +$$ +\beta \left(a + \frac{b + c}{K_{P^{'}/S^{'}}} + \frac{d + r}{K_{E^{'}/S^{'}}}\right) +$$ + +The user agent should perform a final update to xcm $x_0$, substituting the calculated fee amounts. + +### Step 3: User agent initiates bridging operation + +The user agent calls `pallet_xcm::execute` with the initial xcm $x_0$ + +```text +WithdrawAsset (KLT, 100) +PayFees (KLT, 20) +InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(KLT, 20) dest=AH + ExchangeAsset give=(KLT, 20) want=(WETH, 1) + InitiateAssetsTransfer asset=(KLT, 40) remoteFee=(WETH, 1) dest=Ethereum + DepositAsset (KLT, 40) beneficiary=Bob +``` + +### Step 4: AH executes message x1 + +The message $x_1$ is application-specific: + +```text +ReserveAssetDeposited (KLT, 80) +PayFees (KLT, 20) +SetAssetClaimer Kilt/Alice +AliasOrigin Kilt/Alice +ExchangeAsset give=(KLT, 20) want=(WETH, 1) +InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(WETH, 1) dest=Ethereum + DepositAsset (KLT, 60) beneficiary=Bob +``` + +or + +```text +*ReserveAssetDeposited (KLT, 80) +*PayFees (KLT, 20) +*SetAssetClaimer Kilt/Alice +*AliasOrigin Kilt/Alice +ExchangeAsset give=(KLT, 20) want=(WETH, 1) +InitiateAssetsTransfer asset=(KLT, 60) remoteFee=(WETH, 1) dest=Ethereum + DepositAsset (KLT, 60) beneficiary=Bob + Transact Bob.hello() +``` + +Note that the `SetAssetClaimer` instruction is placed before `AliasOrigin` in case AH fails to interpret the latter +instruction. + +In all cases, $x_1$ should contain the necessary instructions to: + +1. Pay fees for local execution using `PaysFees` +2. Obtain WETH for remote delivery fees. + +The XCM bridge-router on AH will charge a small fee to prevent spamming BH with bridge messages. This is necessary since +the `ExportMessage` instruction in message $x_2$ will have no execution fee on BH. For a similar reason, we should also +impose a minimum relayer reward of at least the existential deposit 0.1 DOT, which acts as a deposit to stop spamming +messages with 0 rewards. + +### Step 5: BH executes message x2 + +Message $x_2$ is parsed by the `SnowbridgeMessageExporter` in block $n$ with the following effects: + +- A bridge command $m$ is committed to binary merkle tree $M_n$. + - The transferred asset is parsed from `ReserveAssetDeposited` , `WithdrawAsset` or `TeleportedAssetReceived` + instructions for the local, destination and teleport asset transfer types respectively. + - The original origin is preserved through the `AliasOrigin` instruction. This will allow us to resolve agents for the + case of `Transact`. + - The message exporter must be able to support multiple assets and reserve types in the same message and potentially + multiple `Transacts`. + - The Message Exporter must be able to support multiple Deposited Assets. + - The Message Exporter must be able to parse `SetAssetClaimer` and allow the provided location to claim the assets on + BH in case of errors. +- Given relayer reward $r$ in WETH, set storage $P(\mathrm{hash}(m)) = r$. This is parsed from the `WithdrawAsset` and + `PayFees` instruction within `ExportMessage`. + +Note that WETH on AH & BH is a wrapped derivative of the +[WETH](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) ERC20 contract on Ethereum, which is +itself a wrapper over ETH, the native currency of Ethereum. For the purposes of this document you can consider them all +to be of equivalent value. + +```text +!WithdrawAsset(DOT, 10) +!PayFees (DOT, 10) +!ExportMessage dest=Ethereum + *ReserveAssetDeposited (KLT, 60) + *WithdrawAsset (WETH, 1) + *PayFees (WETH, 1) + *SetAssetClaimer Kilt/Alice + *AliasOrigin Kilt/Alice + DepositAsset (KLT, 60) beneficiary=Bob +``` + +or + +```text +!WithdrawAsset(DOT, 10) +!PayFees (DOT, 10) +!ExportMessage dest=Ethereum + *ReserveAssetDeposited (KLT, 80) + *PayFees (KLT, 20) + *SetAssetClaimer Kilt/Alice + *AliasOrigin Kilt/Alice + DepositAsset (KLT, 60) beneficiary=Bob + Transact Bob.hello() +``` + +### Step 6: Relayer relays message to Gateway + +1. A relayer _Charlie_ inspects storage $P$ to look for new messages to relay. Suppose it finds $\mathrm{hash}(m)$ + giving reward $r$. +2. The relayer queries $m$ from $M$ and constructs the necessary proofs. +3. The relayer dry-runs m on Ethereum to decide whether the message is profitable to deliver. +4. The relayer finally delivers the message together with a relayer-controlled address $u$ on AH where the relayer can + claim their reward after proof of delivery. + +### Step 7: Relayer delivers proof of delivery to BH + +The proof of delivery is essentially a merkle proof for the `InboundMessageAccepted` event log. + +When BH processes the proof of delivery: + +1. The command $m$ is removed from storage items $M$ and $P$. +2. The relayer reward is tracked in storage $R$, where $R(u)$ is the accumulated rewards that can be claimed by account + $u$. + +## Ethereum→Polkadot + +### Step 1: Submit send on Gateway + +The interface that the Gateway will use to initiate transfers will be similar to the interface from +`transfer_assets_using_type_and_then` extrinsic that we currently use to initiate transfers from the Polkadot to +Ethereum direction. + +1. It must allow multiple assets to be transferred and specify the transfer type: Local, Destination or Teleport asset + transfer types. It is the job of the User Agent/UX layer to fill in this information correctly. +2. It must allow specifying a destination which is `Address32`, `Address20` or a custom scale-encoded XCM payload that + is executed on the destination. This is how we will support `Transact` , the User Agent/UX layer can build a + scale-encoded payload with an encoded transact call. +3. The same interface is used for both PNA (Polkadot Assets) and ERC20 tokens. Internally we will still look up whether + the token is registered as a PNA or ERC20 for the purpose of minting/locking burning/unlocking logic. The asset + transfer type chosen by the UX layer will inform the XCM that is built from the message on BH. + +```solidity +enum Kind { + Index, + Address32, + Address20, + XCMPayload, +} + +struct Beneficiary { + Kind kind; + bytes data; +} + +enum AssetTransferType { + ReserveDeposit, ReserveWithdraw, Teleport +} + +struct Token { + AssetTransferType type; + address token; + uint128 amount; +} + +function send( + ParaID destinationChain, + Beneficiary calldata beneficiary, + Token[] tokens, + uint128 reward +) external payable; +``` + +Message enqueued $m_0$: + +```solidity +send( + 3022, // KILT Para Id + Address32(0x0000....), + [(ReserveWithdraw, KLT, 100)], + 10, // WETH +) +``` + +```solidity +send { value: 3 }( // Send 3 Eth for fees and reward + 3022, // KILT Para Id + XCMPayload( + DepositAsset (KLT, 100) dest=Bob + Transact Bob.hello() + ), + [(ReserveWithdraw, KLT, 100)], + 1, // 1 ETH of 3 needs to be for the reward, the rest is for fees +) +``` + +The User Agent/UX layer will need to estimate the fee required to be passed into the `send` method. This may be an issue +as we cannot Dry-Run something on Polkadot that has not even been submitted on Ethereum yet. We may need to make RPC API +to DryRun and get back the xcm that would be submitted to asset hub. + +### Step 2: Relayer relays message to Bridge Hub + +On-chain exchange rate is eliminated. Users pay remote delivery costs in ETH, and this amount is sent with the message +as WETH. The delivery fee can be claimed by the relayer on BH. + +The user agent applies a similar dry-running process as with +[Step 2: User agent estimates fees](https://www.notion.so/Step-2-User-agent-estimates-fees-113296aaabef8159bcd0e6dd2e64c3d0?pvs=21). + +The message is converted from $m_0$ to $x_0$ during message submission on BH. Dry-running submission will return $x_0$ +to the relayer so that it can verify it is profitable. + +### Step 3: AH receives $x_0$ from BH + +Submitting the message $m_0$ will cause the following XCM, $x_0$, to be built on BH and dispatched to AH. + +```text +WithdrawAsset (KLT, 100) +ReserveAssetDeposited(WETH, 2) +PayFees (WETH, 1) +SetAssetClaimer Kilt/Bob // derived from beneficiary on final destination +AliasOrigin Ethereum/Alice // derived from msg.sender +InitiateAssetsTransfer asset=(KLT, 100) remoteFee=(WETH, 1) dest=KLT + DepositAsset (KLT, 100) beneficiary=Bob +``` + +```text +WithdrawAsset (KLT, 100) +ReserveAssetDeposited(WETH, 2) +PayFees (WETH, 1) +SetAssetClaimer Kilt/Bob // derived from beneficiary on final destination +AliasOrigin Ethereum/Alice // derived from msg.sender +InitiateAssetsTransfer asset=(KLT, 100) remoteFee=(WETH, 1) dest=KLT + DepositAsset (KLT, 100) beneficiary=Bob + Transact Bob.hello() +``` + +### Step 4: KILT Receives XCM from AH + +The following XCM $x_1$ is received from AH on KILT. + +```text +*WithdrawAsset (KLT, 100) +*ReserveAssetDeposited (WETH, 1) +*PayFees (WETH, 1) +*SetAssetClaimer Ethereum/Alice +*AliasOrigin Ethereum/Alice // origin preserved from AH +SetAssetClaimer Bob +DepositAsset (KLT, 100) beneficiary=Bob +``` + +```text +*WithdrawAsset (KLT, 100) +*ReserveAssetDeposited (WETH, 1) +*PayFees (WETH, 1) +*SetAssetClaimer Ethereum/Alice +*AliasOrigin Ethereum/Alice // origin preserved from AH +SetAssetClaimer Bob +DepositAsset (KLT, 100) beneficiary=Bob +Transact Bob.hello() // executes with the origin from AH +``` + +## Relayer Rewards + +The tracking and disbursement of relayer rewards for both directions has been unified. Rewards are accumulated on BH in +WETH and must be manually claimed. As part of the claims flow, an XCM instruction is sent to AH to mint the WETH into +the deposit account chosen by the relayer. + +To claim, call following extrinsic, where $o$ is rewards account (origin), and $w$ is account on AH where the WETH will +be minted. + +$$ +\mathrm{claim}(o,w) +$$ + +For tax accounting purposes it might be desirable that $o \neq w$. + +## Top-Up + +Top-up of the relayer reward is viable to implement for either direction as extrinsics on Bridge Hub and Ethereum +respectively. + +## Origin Preservation + +Origins for transact will be preserved by use of the `AliasOrigin` instruction. This instruction will have the following +rules that parachain runtimes will need to allow: + +1. `AliasOrigin` can behave like `DescendOrigin`. This is safe because it respects the hierarchy of multi-locations and + does not allow jumping up. Meaning no escalation of privileges. + 1. Example location `Ethereum` can alias into `Ethereum/Alice` because we are descending in origin and this + essentially is how the `DescendOrigin` instruction works. +2. `AliasOrigin` must allow AH to alias into bridged locations such as + `{ parents: 2, interior: GlobalConsensus(Ethereum) }` and all of its internal locations so that AH can act as a proxy + for the bridge on parachains. + +`AliasOrigin` will be inserted by every `InitiateAssetTransfer` instruction on the source parachain, populated with the +contents of the origin register, essentially forwarding the origin of the source to the destination. + +RFCS: + +[https://github.com/polkadot-fellows/RFCs/pull/122](https://github.com/polkadot-fellows/RFCs/pull/122) + +[https://github.com/polkadot-fellows/RFCs/blob/main/text/0100-xcm-multi-type-asset-transfer.md](https://github.com/polkadot-fellows/RFCs/blob/main/text/0100-xcm-multi-type-asset-transfer.md) + +## Parachain Requirements + +1. Pallet-xcm.execute enabled. +2. XCM payment and dry run apis implemented. +3. Must accept WETH needed for fees. Though in future user agents can inject `ExchangeAsset` instructions to obtain + WETH. +4. Trust AH as a reserve for bridged assets. +5. Origin Preservation rules configured which allow asset hub to impersonate bridged addresses. -- GitLab From e294d6287743f036e19af1c3e6383f114d5172a9 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Wed, 9 Oct 2024 19:07:49 +0200 Subject: [PATCH 082/127] Add PVF execution priority (#4837) Resolves https://github.com/paritytech/polkadot-sdk/issues/4632 The new logic optimizes the distribution of execution jobs for disputes, approvals, and backings. Testing shows improved finality lag and candidate checking times, especially under heavy network load. ### Approach This update adds prioritization to the PVF execution queue. The logic partially implements the suggestions from https://github.com/paritytech/polkadot-sdk/issues/4632#issuecomment-2209188695. We use thresholds to determine how much a current priority can "steal" from lower ones: - Disputes: 70% - Approvals: 80% - Backing System Parachains: 100% - Backing: 100% A threshold indicates the portion of the current priority that can be allocated from lower priorities. For example: - Disputes take 70%, leaving 30% for approvals and all backings. - 80% of the remaining goes to approvals, which is 30% * 80% = 24% of the original 100%. - If we used parts of the original 100%, approvals couldn't take more than 24%, even if there are no disputes. Assuming a maximum of 12 executions per block, with a 6-second window, 2 CPU cores, and a 2-second run time, we get these distributions: - With disputes: 8 disputes, 3 approvals, 1 backing - Without disputes: 9 approvals, 3 backings It's worth noting that when there are no disputes, if there's only one backing job, we continue processing approvals regardless of their fulfillment status. ### Versi Testing 40/20 Testing showed a slight difference in finality lag and candidate checking time between this pull request and its base on the master branch. The more loaded the network, the greater the observed difference. Testing Parameters: - 40 validators (4 malicious) - 20 gluttons with 2 seconds of PVF execution time - 6 VRF modulo samples - 12 required approvals ![Pasted Graphic 3](https://github.com/user-attachments/assets/8b6163a4-a1c9-44c2-bdba-ce1ef4b1eba7) ![Pasted Graphic 4](https://github.com/user-attachments/assets/9f016647-7727-42e8-afe9-04f303e6c862) ### Versi Testing 80/40 For this test, we compared the master branch with the branch from https://github.com/paritytech/polkadot-sdk/pull/5616. The second branch is based on the current one but removes backing jobs that have exceeded their time limits. We excluded malicious nodes to reduce noise from disputing and banning validators. The results show that, under the same load, nodes experience less finality lag and reduced recovery and check time. Even parachains are functioning with a shorter block time, although it remains over 6 seconds. Testing Parameters: - 80 validators (0 malicious) - 40 gluttons with 2 seconds of PVF execution time - 6 VRF modulo samples - 30 required approvals ![image](https://github.com/user-attachments/assets/42bcc845-9115-4ae3-9910-286b77a60bbf) --------- Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- Cargo.lock | 3 + polkadot/node/core/approval-voting/src/lib.rs | 6 +- polkadot/node/core/backing/Cargo.toml | 1 + polkadot/node/core/backing/src/lib.rs | 17 +- polkadot/node/core/backing/src/tests/mod.rs | 22 +- .../src/tests/prospective_parachains.rs | 2 +- .../node/core/candidate-validation/src/lib.rs | 41 ++- .../core/candidate-validation/src/tests.rs | 4 + .../src/participation/mod.rs | 8 +- .../src/participation/tests.rs | 10 +- polkadot/node/core/pvf/Cargo.toml | 1 + polkadot/node/core/pvf/src/execute/queue.rs | 325 +++++++++++++++++- polkadot/node/core/pvf/src/host.rs | 43 ++- polkadot/node/core/pvf/src/metrics.rs | 19 + polkadot/node/core/pvf/src/priority.rs | 13 + polkadot/node/core/pvf/tests/it/main.rs | 10 +- polkadot/node/malus/src/variants/common.rs | 4 +- .../node/overseer/examples/minimal-example.rs | 4 +- polkadot/node/overseer/src/tests.rs | 4 +- polkadot/node/subsystem-types/Cargo.toml | 1 + polkadot/node/subsystem-types/src/messages.rs | 47 ++- prdoc/pr_4837.prdoc | 26 ++ 22 files changed, 539 insertions(+), 72 deletions(-) create mode 100644 prdoc/pr_4837.prdoc diff --git a/Cargo.lock b/Cargo.lock index 34e30d79e98..f26252520ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14312,6 +14312,7 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-util", + "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-statement-table", @@ -14532,6 +14533,7 @@ dependencies = [ "slotmap", "sp-core 28.0.0", "sp-maybe-compressed-blob 11.0.0", + "strum 0.26.2", "tempfile", "test-parachain-adder", "test-parachain-halt", @@ -14784,6 +14786,7 @@ dependencies = [ "sp-blockchain", "sp-consensus-babe", "sp-runtime 31.0.1", + "strum 0.26.2", "substrate-prometheus-endpoint", "thiserror", ] diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 83509829305..0cb977c5802 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -38,7 +38,7 @@ use polkadot_node_subsystem::{ ApprovalVotingMessage, AssignmentCheckError, AssignmentCheckResult, AvailabilityRecoveryMessage, BlockDescription, CandidateValidationMessage, ChainApiMessage, ChainSelectionMessage, CheckedIndirectAssignment, CheckedIndirectSignedApprovalVote, - DisputeCoordinatorMessage, HighestApprovedAncestorBlock, RuntimeApiMessage, + DisputeCoordinatorMessage, HighestApprovedAncestorBlock, PvfExecKind, RuntimeApiMessage, RuntimeApiRequest, }, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult, @@ -53,8 +53,8 @@ use polkadot_node_subsystem_util::{ }; use polkadot_primitives::{ ApprovalVoteMultipleCandidates, ApprovalVotingParams, BlockNumber, CandidateHash, - CandidateIndex, CandidateReceipt, CoreIndex, ExecutorParams, GroupIndex, Hash, PvfExecKind, - SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, + CandidateIndex, CandidateReceipt, CoreIndex, ExecutorParams, GroupIndex, Hash, SessionIndex, + SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, }; use sc_keystore::LocalKeystore; use sp_application_crypto::Pair; diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index 1b52afc309b..bd56a3ad693 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -14,6 +14,7 @@ futures = { workspace = true } sp-keystore = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index f276321c87e..4463fb34b51 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -89,8 +89,9 @@ use polkadot_node_subsystem::{ AvailabilityDistributionMessage, AvailabilityStoreMessage, CanSecondRequest, CandidateBackingMessage, CandidateValidationMessage, CollatorProtocolMessage, HypotheticalCandidate, HypotheticalMembershipRequest, IntroduceSecondedCandidateRequest, - ProspectiveParachainsMessage, ProvisionableData, ProvisionerMessage, RuntimeApiMessage, - RuntimeApiRequest, StatementDistributionMessage, StoreAvailableDataError, + ProspectiveParachainsMessage, ProvisionableData, ProvisionerMessage, PvfExecKind, + RuntimeApiMessage, RuntimeApiRequest, StatementDistributionMessage, + StoreAvailableDataError, }, overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, }; @@ -105,12 +106,13 @@ use polkadot_node_subsystem_util::{ }, Validator, }; +use polkadot_parachain_primitives::primitives::IsSystem; use polkadot_primitives::{ node_features::FeatureIndex, BackedCandidate, CandidateCommitments, CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, - PvfExecKind, SessionIndex, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, - ValidatorSignature, ValidityAttestation, + SessionIndex, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, + ValidityAttestation, }; use polkadot_statement_table::{ generic::AttestedCandidate as TableAttestedCandidate, @@ -625,6 +627,7 @@ async fn request_candidate_validation( executor_params: ExecutorParams, ) -> Result { let (tx, rx) = oneshot::channel(); + let is_system = candidate_receipt.descriptor.para_id.is_system(); sender .send_message(CandidateValidationMessage::ValidateFromExhaustive { @@ -633,7 +636,11 @@ async fn request_candidate_validation( candidate_receipt, pov, executor_params, - exec_kind: PvfExecKind::Backing, + exec_kind: if is_system { + PvfExecKind::BackingSystemParas + } else { + PvfExecKind::Backing + }, response_sender: tx, }) .await; diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 10eb45b82d1..d9c1fc9499e 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -30,7 +30,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ node_features, CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, - PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, + ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; use polkadot_primitives_test_helpers::{ dummy_candidate_receipt_bad_sig, dummy_collator, dummy_collator_signature, @@ -434,7 +434,7 @@ async fn assert_validate_from_exhaustive( ) if validation_data == *assert_pvd && validation_code == *assert_validation_code && *pov == *assert_pov && &candidate_receipt.descriptor == assert_candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_receipt.commitments_hash == assert_candidate.commitments.hash() => { response_sender.send(Ok(ValidationResult::Valid( @@ -651,7 +651,7 @@ fn backing_works(#[case] elastic_scaling_mvp: bool) { ) if validation_data == pvd_ab && validation_code == validation_code_ab && *pov == pov_ab && &candidate_receipt.descriptor == candidate_a.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_receipt.commitments_hash == candidate_a_commitments_hash => { response_sender.send(Ok( @@ -1287,7 +1287,7 @@ fn backing_works_while_validation_ongoing() { ) if validation_data == pvd_abc && validation_code == validation_code_abc && *pov == pov_abc && &candidate_receipt.descriptor == candidate_a.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { // we never validate the candidate. our local node @@ -1454,7 +1454,7 @@ fn backing_misbehavior_works() { ) if validation_data == pvd_a && validation_code == validation_code_a && *pov == pov_a && &candidate_receipt.descriptor == candidate_a.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { response_sender.send(Ok( @@ -1621,7 +1621,7 @@ fn backing_dont_second_invalid() { ) if validation_data == pvd_a && validation_code == validation_code_a && *pov == pov_block_a && &candidate_receipt.descriptor == candidate_a.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_a.commitments.hash() == candidate_receipt.commitments_hash => { response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); @@ -1661,7 +1661,7 @@ fn backing_dont_second_invalid() { ) if validation_data == pvd_b && validation_code == validation_code_b && *pov == pov_block_b && &candidate_receipt.descriptor == candidate_b.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_b.commitments.hash() == candidate_receipt.commitments_hash => { response_sender.send(Ok( @@ -1788,7 +1788,7 @@ fn backing_second_after_first_fails_works() { ) if validation_data == pvd_a && validation_code == validation_code_a && *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::BadReturn))).unwrap(); @@ -1932,7 +1932,7 @@ fn backing_works_after_failed_validation() { ) if validation_data == pvd_a && validation_code == validation_code_a && *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { response_sender.send(Err(ValidationFailed("Internal test error".into()))).unwrap(); @@ -2211,7 +2211,7 @@ fn retry_works() { ) if validation_data == pvd_a && validation_code == validation_code_a && *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash ); virtual_overseer @@ -2753,7 +2753,7 @@ fn validator_ignores_statements_from_disabled_validators() { ) if validation_data == pvd && validation_code == expected_validation_code && *pov == expected_pov && &candidate_receipt.descriptor == candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate_commitments_hash == candidate_receipt.commitments_hash => { response_sender.send(Ok( diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index 15bc0b4a113..57b2fabd43b 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -276,7 +276,7 @@ async fn assert_validate_seconded_candidate( &validation_code == assert_validation_code && &*pov == assert_pov && &candidate_receipt.descriptor == candidate.descriptor() && - exec_kind == PvfExecKind::Backing && + exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { response_sender.send(Ok(ValidationResult::Valid( diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 50505d73391..e875be9b5df 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -31,8 +31,8 @@ use polkadot_node_primitives::{InvalidCandidate, PoV, ValidationResult}; use polkadot_node_subsystem::{ errors::RuntimeApiError, messages::{ - CandidateValidationMessage, PreCheckOutcome, RuntimeApiMessage, RuntimeApiRequest, - ValidationFailed, + CandidateValidationMessage, PreCheckOutcome, PvfExecKind, RuntimeApiMessage, + RuntimeApiRequest, ValidationFailed, }, overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult, SubsystemSender, @@ -46,8 +46,9 @@ use polkadot_primitives::{ DEFAULT_LENIENT_PREPARATION_TIMEOUT, DEFAULT_PRECHECK_PREPARATION_TIMEOUT, }, AuthorityDiscoveryId, CandidateCommitments, CandidateDescriptor, CandidateEvent, - CandidateReceipt, ExecutorParams, Hash, PersistedValidationData, PvfExecKind, PvfPrepKind, - SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, + CandidateReceipt, ExecutorParams, Hash, PersistedValidationData, + PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, + ValidationCodeHash, ValidatorId, }; use sp_application_crypto::{AppCrypto, ByteArray}; use sp_keystore::KeystorePtr; @@ -667,9 +668,9 @@ async fn validate_candidate_exhaustive( let result = match exec_kind { // Retry is disabled to reduce the chance of nondeterministic blocks getting backed and // honest backers getting slashed. - PvfExecKind::Backing => { + PvfExecKind::Backing | PvfExecKind::BackingSystemParas => { let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Prepare); - let exec_timeout = pvf_exec_timeout(&executor_params, exec_kind); + let exec_timeout = pvf_exec_timeout(&executor_params, exec_kind.into()); let pvf = PvfPrepData::from_code( validation_code.0, executor_params, @@ -683,20 +684,22 @@ async fn validate_candidate_exhaustive( exec_timeout, persisted_validation_data.clone(), pov, - polkadot_node_core_pvf::Priority::Normal, + exec_kind.into(), + exec_kind, ) .await }, - PvfExecKind::Approval => + PvfExecKind::Approval | PvfExecKind::Dispute => validation_backend .validate_candidate_with_retry( validation_code.0, - pvf_exec_timeout(&executor_params, exec_kind), + pvf_exec_timeout(&executor_params, exec_kind.into()), persisted_validation_data.clone(), pov, executor_params, PVF_APPROVAL_EXECUTION_RETRY_DELAY, - polkadot_node_core_pvf::Priority::Critical, + exec_kind.into(), + exec_kind, ) .await, }; @@ -784,6 +787,8 @@ trait ValidationBackend { pov: Arc, // The priority for the preparation job. prepare_priority: polkadot_node_core_pvf::Priority, + // The kind for the execution job. + exec_kind: PvfExecKind, ) -> Result; /// Tries executing a PVF. Will retry once if an error is encountered that may have @@ -804,6 +809,8 @@ trait ValidationBackend { retry_delay: Duration, // The priority for the preparation job. prepare_priority: polkadot_node_core_pvf::Priority, + // The kind for the execution job. + exec_kind: PvfExecKind, ) -> Result { let prep_timeout = pvf_prep_timeout(&executor_params, PvfPrepKind::Prepare); // Construct the PVF a single time, since it is an expensive operation. Cloning it is cheap. @@ -825,6 +832,7 @@ trait ValidationBackend { pvd.clone(), pov.clone(), prepare_priority, + exec_kind, ) .await; if validation_result.is_ok() { @@ -905,6 +913,7 @@ trait ValidationBackend { pvd.clone(), pov.clone(), prepare_priority, + exec_kind, ) .await; } @@ -929,9 +938,13 @@ impl ValidationBackend for ValidationHost { pov: Arc, // The priority for the preparation job. prepare_priority: polkadot_node_core_pvf::Priority, + // The kind for the execution job. + exec_kind: PvfExecKind, ) -> Result { let (tx, rx) = oneshot::channel(); - if let Err(err) = self.execute_pvf(pvf, exec_timeout, pvd, pov, prepare_priority, tx).await + if let Err(err) = self + .execute_pvf(pvf, exec_timeout, pvd, pov, prepare_priority, exec_kind, tx) + .await { return Err(InternalValidationError::HostCommunication(format!( "cannot send pvf to the validation host, it might have shut down: {:?}", @@ -1023,12 +1036,12 @@ fn pvf_prep_timeout(executor_params: &ExecutorParams, kind: PvfPrepKind) -> Dura /// This should be much longer than the backing execution timeout to ensure that in the /// absence of extremely large disparities between hardware, blocks that pass backing are /// considered executable by approval checkers or dispute participants. -fn pvf_exec_timeout(executor_params: &ExecutorParams, kind: PvfExecKind) -> Duration { +fn pvf_exec_timeout(executor_params: &ExecutorParams, kind: RuntimePvfExecKind) -> Duration { if let Some(timeout) = executor_params.pvf_exec_timeout(kind) { return timeout } match kind { - PvfExecKind::Backing => DEFAULT_BACKING_EXECUTION_TIMEOUT, - PvfExecKind::Approval => DEFAULT_APPROVAL_EXECUTION_TIMEOUT, + RuntimePvfExecKind::Backing => DEFAULT_BACKING_EXECUTION_TIMEOUT, + RuntimePvfExecKind::Approval => DEFAULT_APPROVAL_EXECUTION_TIMEOUT, } } diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 46de55e4836..2f7baf4abb6 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -17,6 +17,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use super::*; +use crate::PvfExecKind; use assert_matches::assert_matches; use futures::executor; use polkadot_node_core_pvf::PrepareError; @@ -441,6 +442,7 @@ impl ValidationBackend for MockValidateCandidateBackend { _pvd: Arc, _pov: Arc, _prepare_priority: polkadot_node_core_pvf::Priority, + _exec_kind: PvfExecKind, ) -> Result { // This is expected to panic if called more times than expected, indicating an error in the // test. @@ -1023,6 +1025,7 @@ impl ValidationBackend for MockPreCheckBackend { _pvd: Arc, _pov: Arc, _prepare_priority: polkadot_node_core_pvf::Priority, + _exec_kind: PvfExecKind, ) -> Result { unreachable!() } @@ -1177,6 +1180,7 @@ impl ValidationBackend for MockHeadsUp { _pvd: Arc, _pov: Arc, _prepare_priority: polkadot_node_core_pvf::Priority, + _exec_kind: PvfExecKind, ) -> Result { unreachable!() } diff --git a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs index b58ce570f8f..2220f65e20a 100644 --- a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs +++ b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs @@ -27,13 +27,11 @@ use futures_timer::Delay; use polkadot_node_primitives::ValidationResult; use polkadot_node_subsystem::{ - messages::{AvailabilityRecoveryMessage, CandidateValidationMessage}, + messages::{AvailabilityRecoveryMessage, CandidateValidationMessage, PvfExecKind}, overseer, ActiveLeavesUpdate, RecoveryError, }; use polkadot_node_subsystem_util::runtime::get_validation_code_by_hash; -use polkadot_primitives::{ - BlockNumber, CandidateHash, CandidateReceipt, Hash, PvfExecKind, SessionIndex, -}; +use polkadot_primitives::{BlockNumber, CandidateHash, CandidateReceipt, Hash, SessionIndex}; use crate::LOG_TARGET; @@ -387,7 +385,7 @@ async fn participate( candidate_receipt: req.candidate_receipt().clone(), pov: available_data.pov, executor_params: req.executor_params(), - exec_kind: PvfExecKind::Approval, + exec_kind: PvfExecKind::Dispute, response_sender: validation_tx, }) .await; diff --git a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs index a80553828ac..a6ab6f16df0 100644 --- a/polkadot/node/core/dispute-coordinator/src/participation/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/participation/tests.rs @@ -26,7 +26,7 @@ use codec::Encode; use polkadot_node_primitives::{AvailableData, BlockData, InvalidCandidate, PoV}; use polkadot_node_subsystem::{ messages::{ - AllMessages, ChainApiMessage, DisputeCoordinatorMessage, RuntimeApiMessage, + AllMessages, ChainApiMessage, DisputeCoordinatorMessage, PvfExecKind, RuntimeApiMessage, RuntimeApiRequest, }, ActiveLeavesUpdate, SpawnGlue, @@ -116,7 +116,7 @@ pub async fn participation_full_happy_path( ctx_handle.recv().await, AllMessages::CandidateValidation( CandidateValidationMessage::ValidateFromExhaustive { candidate_receipt, exec_kind, response_sender, .. } - ) if exec_kind == PvfExecKind::Approval => { + ) if exec_kind == PvfExecKind::Dispute => { if expected_commitments_hash != candidate_receipt.commitments_hash { response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); } else { @@ -450,7 +450,7 @@ fn cast_invalid_vote_if_validation_fails_or_is_invalid() { ctx_handle.recv().await, AllMessages::CandidateValidation( CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } - ) if exec_kind == PvfExecKind::Approval => { + ) if exec_kind == PvfExecKind::Dispute => { response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::Timeout))).unwrap(); }, "overseer did not receive candidate validation message", @@ -487,7 +487,7 @@ fn cast_invalid_vote_if_commitments_dont_match() { ctx_handle.recv().await, AllMessages::CandidateValidation( CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } - ) if exec_kind == PvfExecKind::Approval => { + ) if exec_kind == PvfExecKind::Dispute => { response_sender.send(Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch))).unwrap(); }, "overseer did not receive candidate validation message", @@ -524,7 +524,7 @@ fn cast_valid_vote_if_validation_passes() { ctx_handle.recv().await, AllMessages::CandidateValidation( CandidateValidationMessage::ValidateFromExhaustive { exec_kind, response_sender, .. } - ) if exec_kind == PvfExecKind::Approval => { + ) if exec_kind == PvfExecKind::Dispute => { response_sender.send(Ok(ValidationResult::Valid(dummy_candidate_commitments(None), PersistedValidationData::default()))).unwrap(); }, "overseer did not receive candidate validation message", diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index d603af04bf0..13fcdc69a99 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -24,6 +24,7 @@ slotmap = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { features = ["fs", "process"], workspace = true, default-features = true } +strum = { features = ["derive"], workspace = true, default-features = true } codec = { features = [ "derive", diff --git a/polkadot/node/core/pvf/src/execute/queue.rs b/polkadot/node/core/pvf/src/execute/queue.rs index 11031bf1074..096cec3501b 100644 --- a/polkadot/node/core/pvf/src/execute/queue.rs +++ b/polkadot/node/core/pvf/src/execute/queue.rs @@ -35,15 +35,17 @@ use polkadot_node_core_pvf_common::{ SecurityStatus, }; use polkadot_node_primitives::PoV; +use polkadot_node_subsystem::messages::PvfExecKind; use polkadot_primitives::{ExecutorParams, ExecutorParamsHash, PersistedValidationData}; use slotmap::HopSlotMap; use std::{ - collections::VecDeque, + collections::{HashMap, VecDeque}, fmt, path::PathBuf, sync::Arc, time::{Duration, Instant}, }; +use strum::IntoEnumIterator; /// The amount of time a job for which the queue does not have a compatible worker may wait in the /// queue. After that time passes, the queue will kill the first worker which becomes idle to @@ -74,6 +76,7 @@ pub struct PendingExecutionRequest { pub pov: Arc, pub executor_params: ExecutorParams, pub result_tx: ResultSender, + pub exec_kind: PvfExecKind, } struct ExecuteJob { @@ -166,7 +169,7 @@ struct Queue { security_status: SecurityStatus, /// The queue of jobs that are waiting for a worker to pick up. - queue: VecDeque, + unscheduled: Unscheduled, workers: Workers, mux: Mux, } @@ -192,7 +195,7 @@ impl Queue { security_status, to_queue_rx, from_queue_tx, - queue: VecDeque::new(), + unscheduled: Unscheduled::new(), mux: Mux::new(), workers: Workers { running: HopSlotMap::with_capacity_and_key(10), @@ -226,9 +229,13 @@ impl Queue { /// If all the workers are busy or the queue is empty, it does nothing. /// Should be called every time a new job arrives to the queue or a job finishes. fn try_assign_next_job(&mut self, finished_worker: Option) { - // New jobs are always pushed to the tail of the queue; the one at its head is always - // the eldest one. - let eldest = if let Some(eldest) = self.queue.get(0) { eldest } else { return }; + // We always work at the same priority level + let priority = self.unscheduled.select_next_priority(); + let Some(queue) = self.unscheduled.get_mut(priority) else { return }; + + // New jobs are always pushed to the tail of the queue based on their priority; + // the one at its head of each queue is always the eldest one. + let eldest = if let Some(eldest) = queue.get(0) { eldest } else { return }; // By default, we're going to execute the eldest job on any worker slot available, even if // we have to kill and re-spawn a worker @@ -240,7 +247,7 @@ impl Queue { if eldest.waiting_since.elapsed() < MAX_KEEP_WAITING { if let Some(finished_worker) = finished_worker { if let Some(worker_data) = self.workers.running.get(finished_worker) { - for (i, job) in self.queue.iter().enumerate() { + for (i, job) in queue.iter().enumerate() { if worker_data.executor_params_hash == job.executor_params.hash() { (worker, job_index) = (Some(finished_worker), i); break @@ -252,7 +259,7 @@ impl Queue { if worker.is_none() { // Try to obtain a worker for the job - worker = self.workers.find_available(self.queue[job_index].executor_params.hash()); + worker = self.workers.find_available(queue[job_index].executor_params.hash()); } if worker.is_none() { @@ -270,13 +277,15 @@ impl Queue { return } - let job = self.queue.remove(job_index).expect("Job is just checked to be in queue; qed"); + let job = queue.remove(job_index).expect("Job is just checked to be in queue; qed"); if let Some(worker) = worker { assign(self, worker, job); } else { spawn_extra_worker(self, job); } + self.metrics.on_execute_kind(priority); + self.unscheduled.mark_scheduled(priority); } } @@ -297,7 +306,7 @@ async fn purge_dead(metrics: &Metrics, workers: &mut Workers) { fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) { let ToQueue::Enqueue { artifact, pending_execution_request } = to_queue; - let PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx } = + let PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx, exec_kind } = pending_execution_request; gum::debug!( target: LOG_TARGET, @@ -315,7 +324,7 @@ fn handle_to_queue(queue: &mut Queue, to_queue: ToQueue) { result_tx, waiting_since: Instant::now(), }; - queue.queue.push_back(job); + queue.unscheduled.add(job, exec_kind); queue.try_assign_next_job(None); } @@ -638,3 +647,297 @@ pub fn start( .run(); (to_queue_tx, from_queue_rx, run) } + +struct Unscheduled { + unscheduled: HashMap>, + counter: HashMap, +} + +impl Unscheduled { + /// We keep track of every scheduled job in the `counter`, but reset it if the total number of + /// counted jobs reaches the threshold. This number is set as the maximum amount of jobs per + /// relay chain block possible with 4 CPU cores and 2 seconds of execution time. Under normal + /// conditions, the maximum expected queue size is at least vrf_module_samples(6) + 1 for + /// backing a parachain candidate. A buffer is added to cover situations where more work + /// arrives in the queue. + const SCHEDULING_WINDOW_SIZE: usize = 12; + + /// A threshold in percentages indicates how much time a current priority can "steal" from lower + /// priorities. Given the `SCHEDULING_WINDOW_SIZE` is 12 and all job priorities are present: + /// - Disputes consume 70% or 8 jobs in a row. + /// - The remaining 30% of original 100% is allocated for approval and all backing jobs. + /// - 80% or 3 jobs of the remaining goes to approvals. + /// - The remaining 6% of original 100% is allocated for all backing jobs. + /// - 100% or 1 job of the remaining goes to backing system parachains. + /// - Nothing is left for backing. + /// - The counter is restarted and the distribution starts from the beginning. + /// + /// This system might seem complex, but we operate with the remaining percentages because: + /// - Not all job types are present in each block. If we used parts of the original 100%, + /// approvals could not exceed 24%, even if there are no disputes. + /// - We cannot fully prioritize backing system parachains over backing other parachains based + /// on the distribution of the original 100%. + const PRIORITY_ALLOCATION_THRESHOLDS: &'static [(PvfExecKind, usize)] = &[ + (PvfExecKind::Dispute, 70), + (PvfExecKind::Approval, 80), + (PvfExecKind::BackingSystemParas, 100), + (PvfExecKind::Backing, 100), + ]; + + fn new() -> Self { + Self { + unscheduled: PvfExecKind::iter().map(|priority| (priority, VecDeque::new())).collect(), + counter: PvfExecKind::iter().map(|priority| (priority, 0)).collect(), + } + } + + fn select_next_priority(&self) -> PvfExecKind { + gum::debug!( + target: LOG_TARGET, + unscheduled = ?self.unscheduled.iter().map(|(p, q)| (*p, q.len())).collect::>(), + counter = ?self.counter, + "Selecting next execution priority...", + ); + + let priority = PvfExecKind::iter() + .find(|priority| self.has_pending(priority) && !self.has_reached_threshold(priority)) + .unwrap_or_else(|| { + PvfExecKind::iter() + .find(|priority| self.has_pending(priority)) + .unwrap_or(PvfExecKind::Backing) + }); + + gum::debug!( + target: LOG_TARGET, + ?priority, + "Selected next execution priority", + ); + + priority + } + + fn get_mut(&mut self, priority: PvfExecKind) -> Option<&mut VecDeque> { + self.unscheduled.get_mut(&priority) + } + + fn add(&mut self, job: ExecuteJob, priority: PvfExecKind) { + self.unscheduled.entry(priority).or_default().push_back(job); + } + + fn has_pending(&self, priority: &PvfExecKind) -> bool { + !self.unscheduled.get(priority).unwrap_or(&VecDeque::new()).is_empty() + } + + fn priority_allocation_threshold(priority: &PvfExecKind) -> Option { + Self::PRIORITY_ALLOCATION_THRESHOLDS.iter().find_map(|&(p, value)| { + if p == *priority { + Some(value) + } else { + None + } + }) + } + + /// Checks if a given priority has reached its allocated threshold + /// The thresholds are defined in `PRIORITY_ALLOCATION_THRESHOLDS`. + fn has_reached_threshold(&self, priority: &PvfExecKind) -> bool { + let Some(threshold) = Self::priority_allocation_threshold(priority) else { return false }; + let Some(count) = self.counter.get(&priority) else { return false }; + // Every time we iterate by lower level priorities + let total_scheduled_at_priority_or_lower: usize = self + .counter + .iter() + .filter_map(|(p, c)| if *p >= *priority { Some(c) } else { None }) + .sum(); + if total_scheduled_at_priority_or_lower == 0 { + return false + } + + let has_reached_threshold = count * 100 / total_scheduled_at_priority_or_lower >= threshold; + + gum::debug!( + target: LOG_TARGET, + ?priority, + ?count, + ?total_scheduled_at_priority_or_lower, + "Execution priority has {}reached threshold: {}/{}%", + if has_reached_threshold {""} else {"not "}, + count * 100 / total_scheduled_at_priority_or_lower, + threshold + ); + + has_reached_threshold + } + + fn mark_scheduled(&mut self, priority: PvfExecKind) { + *self.counter.entry(priority).or_default() += 1; + + if self.counter.values().sum::() >= Self::SCHEDULING_WINDOW_SIZE { + self.reset_counter(); + } + } + + fn reset_counter(&mut self) { + self.counter = PvfExecKind::iter().map(|kind| (kind, 0)).collect(); + } +} + +#[cfg(test)] +mod tests { + use polkadot_node_primitives::BlockData; + use sp_core::H256; + + use super::*; + use crate::testing::artifact_id; + use std::time::Duration; + + fn create_execution_job() -> ExecuteJob { + let (result_tx, _result_rx) = oneshot::channel(); + let pvd = Arc::new(PersistedValidationData { + parent_head: Default::default(), + relay_parent_number: 1u32, + relay_parent_storage_root: H256::default(), + max_pov_size: 4096 * 1024, + }); + let pov = Arc::new(PoV { block_data: BlockData(b"pov".to_vec()) }); + ExecuteJob { + artifact: ArtifactPathId { id: artifact_id(0), path: PathBuf::new() }, + exec_timeout: Duration::from_secs(10), + pvd, + pov, + executor_params: ExecutorParams::default(), + result_tx, + waiting_since: Instant::now(), + } + } + + #[test] + fn test_unscheduled_add() { + let mut unscheduled = Unscheduled::new(); + + PvfExecKind::iter().for_each(|priority| { + unscheduled.add(create_execution_job(), priority); + }); + + PvfExecKind::iter().for_each(|priority| { + let queue = unscheduled.unscheduled.get(&priority).unwrap(); + assert_eq!(queue.len(), 1); + }); + } + + #[test] + fn test_unscheduled_priority_distribution() { + use PvfExecKind::*; + + let mut priorities = vec![]; + + let mut unscheduled = Unscheduled::new(); + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + unscheduled.add(create_execution_job(), Dispute); + unscheduled.add(create_execution_job(), Approval); + unscheduled.add(create_execution_job(), BackingSystemParas); + unscheduled.add(create_execution_job(), Backing); + } + + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + let priority = unscheduled.select_next_priority(); + priorities.push(priority); + unscheduled.mark_scheduled(priority); + } + + assert_eq!(priorities.iter().filter(|v| **v == Dispute).count(), 8); + assert_eq!(priorities.iter().filter(|v| **v == Approval).count(), 3); + assert_eq!(priorities.iter().filter(|v| **v == BackingSystemParas).count(), 1); + } + + #[test] + fn test_unscheduled_priority_distribution_without_backing_system_paras() { + use PvfExecKind::*; + + let mut priorities = vec![]; + + let mut unscheduled = Unscheduled::new(); + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + unscheduled.add(create_execution_job(), Dispute); + unscheduled.add(create_execution_job(), Approval); + unscheduled.add(create_execution_job(), Backing); + } + + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + let priority = unscheduled.select_next_priority(); + priorities.push(priority); + unscheduled.mark_scheduled(priority); + } + + assert_eq!(priorities.iter().filter(|v| **v == Dispute).count(), 8); + assert_eq!(priorities.iter().filter(|v| **v == Approval).count(), 3); + assert_eq!(priorities.iter().filter(|v| **v == Backing).count(), 1); + } + + #[test] + fn test_unscheduled_priority_distribution_without_disputes() { + use PvfExecKind::*; + + let mut priorities = vec![]; + + let mut unscheduled = Unscheduled::new(); + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + unscheduled.add(create_execution_job(), Approval); + unscheduled.add(create_execution_job(), BackingSystemParas); + unscheduled.add(create_execution_job(), Backing); + } + + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + let priority = unscheduled.select_next_priority(); + priorities.push(priority); + unscheduled.mark_scheduled(priority); + } + + assert_eq!(priorities.iter().filter(|v| **v == Approval).count(), 9); + assert_eq!(priorities.iter().filter(|v| **v == BackingSystemParas).count(), 2); + assert_eq!(priorities.iter().filter(|v| **v == Backing).count(), 1); + } + + #[test] + fn test_unscheduled_priority_distribution_without_disputes_and_only_one_backing() { + use PvfExecKind::*; + + let mut priorities = vec![]; + + let mut unscheduled = Unscheduled::new(); + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + unscheduled.add(create_execution_job(), Approval); + } + unscheduled.add(create_execution_job(), Backing); + + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + let priority = unscheduled.select_next_priority(); + priorities.push(priority); + unscheduled.mark_scheduled(priority); + } + + assert_eq!(priorities.iter().filter(|v| **v == Approval).count(), 11); + assert_eq!(priorities.iter().filter(|v| **v == Backing).count(), 1); + } + + #[test] + fn test_unscheduled_does_not_postpone_backing() { + use PvfExecKind::*; + + let mut priorities = vec![]; + + let mut unscheduled = Unscheduled::new(); + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + unscheduled.add(create_execution_job(), Approval); + } + unscheduled.add(create_execution_job(), Backing); + + for _ in 0..Unscheduled::SCHEDULING_WINDOW_SIZE { + let priority = unscheduled.select_next_priority(); + priorities.push(priority); + unscheduled.mark_scheduled(priority); + } + + assert_eq!(&priorities[..4], &[Approval, Backing, Approval, Approval]); + } +} diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index 22943a06c43..37cd6fcbf74 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -37,7 +37,7 @@ use polkadot_node_core_pvf_common::{ pvf::PvfPrepData, }; use polkadot_node_primitives::PoV; -use polkadot_node_subsystem::{SubsystemError, SubsystemResult}; +use polkadot_node_subsystem::{messages::PvfExecKind, SubsystemError, SubsystemResult}; use polkadot_parachain_primitives::primitives::ValidationResult; use polkadot_primitives::PersistedValidationData; use std::{ @@ -114,6 +114,7 @@ impl ValidationHost { pvd: Arc, pov: Arc, priority: Priority, + exec_kind: PvfExecKind, result_tx: ResultSender, ) -> Result<(), String> { self.to_host_tx @@ -123,6 +124,7 @@ impl ValidationHost { pvd, pov, priority, + exec_kind, result_tx, })) .await @@ -155,6 +157,7 @@ struct ExecutePvfInputs { pvd: Arc, pov: Arc, priority: Priority, + exec_kind: PvfExecKind, result_tx: ResultSender, } @@ -545,7 +548,7 @@ async fn handle_execute_pvf( awaiting_prepare: &mut AwaitingPrepare, inputs: ExecutePvfInputs, ) -> Result<(), Fatal> { - let ExecutePvfInputs { pvf, exec_timeout, pvd, pov, priority, result_tx } = inputs; + let ExecutePvfInputs { pvf, exec_timeout, pvd, pov, priority, exec_kind, result_tx } = inputs; let artifact_id = ArtifactId::from_pvf_prep_data(&pvf); let executor_params = (*pvf.executor_params()).clone(); @@ -567,6 +570,7 @@ async fn handle_execute_pvf( pvd, pov, executor_params, + exec_kind, result_tx, }, }, @@ -597,6 +601,7 @@ async fn handle_execute_pvf( pvd, pov, executor_params, + exec_kind, result_tx, }, ) @@ -606,7 +611,14 @@ async fn handle_execute_pvf( ArtifactState::Preparing { .. } => { awaiting_prepare.add( artifact_id, - PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx }, + PendingExecutionRequest { + exec_timeout, + pvd, + pov, + executor_params, + result_tx, + exec_kind, + }, ); }, ArtifactState::FailedToProcess { last_time_failed, num_failures, error } => { @@ -638,6 +650,7 @@ async fn handle_execute_pvf( pvd, pov, executor_params, + exec_kind, result_tx, }, ) @@ -657,7 +670,14 @@ async fn handle_execute_pvf( pvf, priority, artifact_id, - PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx }, + PendingExecutionRequest { + exec_timeout, + pvd, + pov, + executor_params, + result_tx, + exec_kind, + }, ) .await?; } @@ -779,7 +799,7 @@ async fn handle_prepare_done( // It's finally time to dispatch all the execution requests that were waiting for this artifact // to be prepared. let pending_requests = awaiting_prepare.take(&artifact_id); - for PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx } in + for PendingExecutionRequest { exec_timeout, pvd, pov, executor_params, result_tx, exec_kind } in pending_requests { if result_tx.is_canceled() { @@ -805,6 +825,7 @@ async fn handle_prepare_done( pvd, pov, executor_params, + exec_kind, result_tx, }, }, @@ -1234,6 +1255,7 @@ pub(crate) mod tests { pvd.clone(), pov1.clone(), Priority::Normal, + PvfExecKind::Backing, result_tx, ) .await @@ -1246,6 +1268,7 @@ pub(crate) mod tests { pvd.clone(), pov1, Priority::Critical, + PvfExecKind::Backing, result_tx, ) .await @@ -1258,6 +1281,7 @@ pub(crate) mod tests { pvd, pov2, Priority::Normal, + PvfExecKind::Backing, result_tx, ) .await @@ -1407,6 +1431,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx, ) .await @@ -1455,6 +1480,7 @@ pub(crate) mod tests { pvd, pov, Priority::Critical, + PvfExecKind::Backing, result_tx, ) .await @@ -1565,6 +1591,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx, ) .await @@ -1596,6 +1623,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx_2, ) .await @@ -1619,6 +1647,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx_3, ) .await @@ -1677,6 +1706,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx, ) .await @@ -1708,6 +1738,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx_2, ) .await @@ -1731,6 +1762,7 @@ pub(crate) mod tests { pvd.clone(), pov.clone(), Priority::Critical, + PvfExecKind::Backing, result_tx_3, ) .await @@ -1805,6 +1837,7 @@ pub(crate) mod tests { pvd, pov, Priority::Normal, + PvfExecKind::Backing, result_tx, ) .await diff --git a/polkadot/node/core/pvf/src/metrics.rs b/polkadot/node/core/pvf/src/metrics.rs index c59cab46418..745f2de99e5 100644 --- a/polkadot/node/core/pvf/src/metrics.rs +++ b/polkadot/node/core/pvf/src/metrics.rs @@ -18,6 +18,7 @@ use polkadot_node_core_pvf_common::prepare::MemoryStats; use polkadot_node_metrics::metrics::{self, prometheus}; +use polkadot_node_subsystem::messages::PvfExecKind; /// Validation host metrics. #[derive(Default, Clone)] @@ -120,6 +121,13 @@ impl Metrics { .observe(pov_size as f64); } } + + /// When preparation pipeline concluded working on an item. + pub(crate) fn on_execute_kind(&self, kind: PvfExecKind) { + if let Some(metrics) = &self.0 { + metrics.exec_kind_selected.with_label_values(&[kind.as_str()]).inc(); + } + } } #[derive(Clone)] @@ -146,6 +154,7 @@ struct MetricsInner { preparation_peak_tracked_allocation: prometheus::Histogram, pov_size: prometheus::HistogramVec, code_size: prometheus::Histogram, + exec_kind_selected: prometheus::CounterVec, } impl metrics::Metrics for Metrics { @@ -369,6 +378,16 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + exec_kind_selected: prometheus::register( + prometheus::CounterVec::new( + prometheus::Opts::new( + "polkadot_pvf_exec_kind_selected", + "The total number of selected execute kinds", + ), + &["priority"], + )?, + registry, + )?, }; Ok(Metrics(Some(inner))) } diff --git a/polkadot/node/core/pvf/src/priority.rs b/polkadot/node/core/pvf/src/priority.rs index 0d18d4b484c..7aaeacf3622 100644 --- a/polkadot/node/core/pvf/src/priority.rs +++ b/polkadot/node/core/pvf/src/priority.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use polkadot_node_subsystem::messages::PvfExecKind; + /// A priority assigned to preparation of a PVF. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Priority { @@ -35,3 +37,14 @@ impl Priority { self == Priority::Critical } } + +impl From for Priority { + fn from(priority: PvfExecKind) -> Self { + match priority { + PvfExecKind::Dispute => Priority::Critical, + PvfExecKind::Approval => Priority::Critical, + PvfExecKind::BackingSystemParas => Priority::Normal, + PvfExecKind::Backing => Priority::Normal, + } + } +} diff --git a/polkadot/node/core/pvf/tests/it/main.rs b/polkadot/node/core/pvf/tests/it/main.rs index a4a08531895..4cbc6fb04a8 100644 --- a/polkadot/node/core/pvf/tests/it/main.rs +++ b/polkadot/node/core/pvf/tests/it/main.rs @@ -25,9 +25,11 @@ use polkadot_node_core_pvf::{ ValidationHost, JOB_TIMEOUT_WALL_CLOCK_FACTOR, }; use polkadot_node_primitives::{PoV, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT}; +use polkadot_node_subsystem::messages::PvfExecKind; use polkadot_parachain_primitives::primitives::{BlockData, ValidationResult}; use polkadot_primitives::{ - ExecutorParam, ExecutorParams, PersistedValidationData, PvfExecKind, PvfPrepKind, + ExecutorParam, ExecutorParams, PersistedValidationData, PvfExecKind as RuntimePvfExecKind, + PvfPrepKind, }; use sp_core::H256; @@ -123,6 +125,7 @@ impl TestHost { Arc::new(pvd), Arc::new(pov), polkadot_node_core_pvf::Priority::Normal, + PvfExecKind::Backing, result_tx, ) .await @@ -580,8 +583,9 @@ async fn artifact_does_not_reprepare_on_non_meaningful_exec_parameter_change() { let cache_dir = host.cache_dir.path(); let set1 = ExecutorParams::default(); - let set2 = - ExecutorParams::from(&[ExecutorParam::PvfExecTimeout(PvfExecKind::Backing, 2500)][..]); + let set2 = ExecutorParams::from( + &[ExecutorParam::PvfExecTimeout(RuntimePvfExecKind::Backing, 2500)][..], + ); let _stats = host .precheck_pvf(test_parachain_halt::wasm_binary_unwrap(), set1) diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs index 50a5af63db4..66926f48c5e 100644 --- a/polkadot/node/malus/src/variants/common.rs +++ b/polkadot/node/malus/src/variants/common.rs @@ -241,7 +241,7 @@ where }, } => { match self.fake_validation { - x if x.misbehaves_valid() && x.should_misbehave(exec_kind) => { + x if x.misbehaves_valid() && x.should_misbehave(exec_kind.into()) => { // Behave normally if the `PoV` is not known to be malicious. if pov.block_data.0.as_slice() != MALICIOUS_POV { return Some(FromOrchestra::Communication { @@ -296,7 +296,7 @@ where }, } }, - x if x.misbehaves_invalid() && x.should_misbehave(exec_kind) => { + x if x.misbehaves_invalid() && x.should_misbehave(exec_kind.into()) => { // Set the validation result to invalid with probability `p` and trigger a // dispute let behave_maliciously = self.distribution.sample(&mut rand::thread_rng()); diff --git a/polkadot/node/overseer/examples/minimal-example.rs b/polkadot/node/overseer/examples/minimal-example.rs index c2cb1817312..807e7405ff1 100644 --- a/polkadot/node/overseer/examples/minimal-example.rs +++ b/polkadot/node/overseer/examples/minimal-example.rs @@ -24,14 +24,14 @@ use orchestra::async_trait; use std::time::Duration; use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem_types::messages::CandidateValidationMessage; +use polkadot_node_subsystem_types::messages::{CandidateValidationMessage, PvfExecKind}; use polkadot_overseer::{ self as overseer, dummy::dummy_overseer_builder, gen::{FromOrchestra, SpawnedSubsystem}, HeadSupportsParachains, SubsystemError, }; -use polkadot_primitives::{CandidateReceipt, Hash, PersistedValidationData, PvfExecKind}; +use polkadot_primitives::{CandidateReceipt, Hash, PersistedValidationData}; use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_hash, dummy_validation_code, }; diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index 47c26186b51..46864a482e2 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -25,11 +25,11 @@ use polkadot_node_primitives::{ }; use polkadot_node_subsystem_test_helpers::mock::{dummy_unpin_handle, new_leaf}; use polkadot_node_subsystem_types::messages::{ - NetworkBridgeEvent, ReportPeerMessage, RuntimeApiRequest, + NetworkBridgeEvent, PvfExecKind, ReportPeerMessage, RuntimeApiRequest, }; use polkadot_primitives::{ CandidateHash, CandidateReceipt, CollatorPair, Id as ParaId, InvalidDisputeStatementKind, - PersistedValidationData, PvfExecKind, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, + PersistedValidationData, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, }; use polkadot_primitives_test_helpers::{ dummy_candidate_descriptor, dummy_candidate_receipt, dummy_hash, dummy_validation_code, diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index b5686ec96be..b8bad8f8a29 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -32,3 +32,4 @@ prometheus-endpoint = { workspace = true, default-features = true } thiserror = { workspace = true } async-trait = { workspace = true } bitvec = { features = ["alloc"], workspace = true } +strum = { features = ["derive"], workspace = true, default-features = true } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index a520e85f52a..0017adb4556 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -24,6 +24,7 @@ use futures::channel::oneshot; use sc_network::{Multiaddr, ReputationChange}; +use strum::EnumIter; use thiserror::Error; pub use sc_network::IfDisconnected; @@ -47,9 +48,10 @@ use polkadot_primitives::{ CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreIndex, CoreState, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, - NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, PvfExecKind, - SessionIndex, SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, + NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, + PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield, + SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + ValidatorSignature, }; use polkadot_statement_table::v2::Misbehavior; use std::{ @@ -182,6 +184,45 @@ pub enum CandidateValidationMessage { }, } +/// Extends primitives::PvfExecKind, which is a runtime parameter we don't want to change, +/// to separate and prioritize execution jobs by request type. +/// The order is important, because we iterate through the values and assume it is going from higher +/// to lowest priority. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumIter)] +pub enum PvfExecKind { + /// For dispute requests + Dispute, + /// For approval requests + Approval, + /// For backing requests from system parachains. + BackingSystemParas, + /// For backing requests. + Backing, +} + +impl PvfExecKind { + /// Converts priority level to &str + pub fn as_str(&self) -> &str { + match *self { + Self::Dispute => "dispute", + Self::Approval => "approval", + Self::BackingSystemParas => "backing_system_paras", + Self::Backing => "backing", + } + } +} + +impl From for RuntimePvfExecKind { + fn from(exec: PvfExecKind) -> Self { + match exec { + PvfExecKind::Dispute => RuntimePvfExecKind::Approval, + PvfExecKind::Approval => RuntimePvfExecKind::Approval, + PvfExecKind::BackingSystemParas => RuntimePvfExecKind::Backing, + PvfExecKind::Backing => RuntimePvfExecKind::Backing, + } + } +} + /// Messages received by the Collator Protocol subsystem. #[derive(Debug, derive_more::From)] pub enum CollatorProtocolMessage { diff --git a/prdoc/pr_4837.prdoc b/prdoc/pr_4837.prdoc new file mode 100644 index 00000000000..55c12cc92a1 --- /dev/null +++ b/prdoc/pr_4837.prdoc @@ -0,0 +1,26 @@ +# 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 PVF execution priority + +doc: + - audience: Node Dev + description: | + The new logic optimizes the distribution of execution jobs for disputes, approvals, and backings. + The main goal is to create back pressure for backing in the presence of disputes or numerous approval jobs. + +crates: + - name: polkadot-node-core-pvf + bump: major + - name: polkadot-overseer + bump: patch + - name: polkadot-node-subsystem-types + bump: patch + - name: polkadot-node-core-approval-voting + bump: patch + - name: polkadot-node-core-backing + bump: patch + - name: polkadot-node-core-candidate-validation + bump: patch + - name: polkadot-node-core-dispute-coordinator + bump: patch -- GitLab From e0062af9c8bdbd15a5b387912f4ba2913d82b75b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:41:20 +0000 Subject: [PATCH 083/127] Bump strum from 0.26.2 to 0.26.3 (#5943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [strum](https://github.com/Peternator7/strum) from 0.26.2 to 0.26.3.
Release notes

Sourced from strum's releases.

v0.26.3

What's Changed

New Contributors

Full Changelog: https://github.com/Peternator7/strum/compare/v0.26.2...v0.26.3

Changelog

Sourced from strum's changelog.

0.26.3 (strum_macros)

  • #344: Hide EnumTable because it's going to be deprecated in the next version.
  • #357: Fixes an incompatiblity with itertools by using the fully qualified name rather than the inherent method.
  • #345: Allows unnamed tuple like variants to use their variants in string interpolation. #[strum(to_string = "Field 0: {0}, Field 1: {1})")] will now work for tuple variants
Commits
  • c89286f Update changelog & strum_macros version
  • 0c85c16 Use associated function syntax for calling get on an EnumIter, eliminatin...
  • 9689d7b add hyperlinks to documentation and clarify derive macros (#355)
  • 186d29f Interpolate unnamed enum variant fields in to_string attribute (#345)
  • 410062e Fix broken links (#350)
  • 1e46337 Update heck requirement (#346)
  • f5fce03 Fix typos & misspellings in docs (#347)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=strum&package-manager=cargo&previous-version=0.26.2&new-version=0.26.3)](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> --- Cargo.lock | 32 ++++++++++++++++---------------- Cargo.toml | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f26252520ff..fa8f486e01f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11466,7 +11466,7 @@ dependencies = [ "sp-npos-elections", "sp-runtime 31.0.1", "sp-tracing 16.0.0", - "strum 0.26.2", + "strum 0.26.3", ] [[package]] @@ -13242,7 +13242,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core 0.5.1", + "rand_core 0.6.4", "serde", "unicode-normalization", ] @@ -14533,7 +14533,7 @@ dependencies = [ "slotmap", "sp-core 28.0.0", "sp-maybe-compressed-blob 11.0.0", - "strum 0.26.2", + "strum 0.26.3", "tempfile", "test-parachain-adder", "test-parachain-halt", @@ -14702,7 +14702,7 @@ dependencies = [ "sc-network", "sc-network-types", "sp-runtime 31.0.1", - "strum 0.26.2", + "strum 0.26.3", "thiserror", "tracing-gum", ] @@ -14786,7 +14786,7 @@ dependencies = [ "sp-blockchain", "sp-consensus-babe", "sp-runtime 31.0.1", - "strum 0.26.2", + "strum 0.26.3", "substrate-prometheus-endpoint", "thiserror", ] @@ -15921,7 +15921,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-timestamp", "sp-tracing 16.0.0", - "strum 0.26.2", + "strum 0.26.3", "substrate-prometheus-endpoint", "tikv-jemallocator", "tokio", @@ -21665,7 +21665,7 @@ dependencies = [ "sp-mmr-primitives", "sp-runtime 31.0.1", "sp-weights 27.0.0", - "strum 0.26.2", + "strum 0.26.3", "w3f-bls", ] @@ -22237,7 +22237,7 @@ version = "31.0.0" dependencies = [ "sp-core 28.0.0", "sp-runtime 31.0.1", - "strum 0.26.2", + "strum 0.26.3", ] [[package]] @@ -23564,11 +23564,11 @@ checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.2", + "strum_macros 0.26.4", ] [[package]] @@ -23599,11 +23599,11 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", "rustversion", @@ -23799,7 +23799,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-trie 29.0.0", "structopt", - "strum 0.26.2", + "strum 0.26.3", "thiserror", ] @@ -23992,7 +23992,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0", "sp-tracing 16.0.0", "sp-version 29.0.0", - "strum 0.26.2", + "strum 0.26.3", "tempfile", "toml 0.8.12", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index a52cdb99b0e..fde05e90ca6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1282,7 +1282,7 @@ ssz_rs_derive = { version = "0.9.0", default-features = false } static_assertions = { version = "1.1.0", default-features = false } static_init = { version = "1.0.3" } structopt = { version = "0.3" } -strum = { version = "0.26.2", default-features = false } +strum = { version = "0.26.3", default-features = false } subkey = { path = "substrate/bin/utils/subkey", default-features = false } substrate-bip39 = { path = "substrate/utils/substrate-bip39", default-features = false } substrate-build-script-utils = { path = "substrate/utils/build-script-utils", default-features = false } -- GitLab From cb1f19c51c62a79dc623e75a82300f51ff5fc1b3 Mon Sep 17 00:00:00 2001 From: RadiumBlock Date: Thu, 10 Oct 2024 02:38:29 -0500 Subject: [PATCH 084/127] Add RadiumBlock bootnodes to Coretime Polkadot Chain spec (#5967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✄ ----------------------------------------------------------------------------- Thank you for your Pull Request! 🙏 Please make sure it follows the contribution guidelines outlined in [this document](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and fill out the sections below. Once you're ready to submit your PR for review, please delete this section and leave only the text under the "Description" heading. # Description Please consider adding RadiumBlock bootnodes to Coretime Polkadot Chain spec ## Integration *In depth notes about how this PR should be integrated by downstream projects. This part is mandatory, and should be reviewed by reviewers, if the PR does NOT have the `R0-Silent` label. In case of a `R0-Silent`, it can be ignored.* ## Review Notes *In depth notes about the **implementation** details of your PR. This should be the main guide for reviewers to understand your approach and effectively review it. If too long, use [`
`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)*. *Imagine that someone who is depending on the old code wants to integrate your new code and the only information that they get is this section. It helps to include example usage and default value here, with a `diff` code-block to show possibly integration.* *Include your leftover TODOs, if any, here.* # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) You can remove the "Checklist" section once all have been checked. Thank you for your contribution! ✄ ----------------------------------------------------------------------------- Co-authored-by: Veena Co-authored-by: Dónal Murray --- cumulus/parachains/chain-specs/coretime-polkadot.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cumulus/parachains/chain-specs/coretime-polkadot.json b/cumulus/parachains/chain-specs/coretime-polkadot.json index 806231db764..e4f947d2afc 100644 --- a/cumulus/parachains/chain-specs/coretime-polkadot.json +++ b/cumulus/parachains/chain-specs/coretime-polkadot.json @@ -10,7 +10,9 @@ "/dns4/coretime-polkadot.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWFJ2yBTKFKYwgKUjfY3F7XfaxHV8hY6fbJu5oMkpP7wZ9", "/dns4/coretime-polkadot.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWCy5pToLafcQzPHn5kadxAftmF6Eh8ZJGPXhSeXSUDfjv", "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", - "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9" + "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", + "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU", + "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU" ], "telemetryEndpoints": null, "protocolId": null, @@ -91,4 +93,4 @@ "childrenDefault": {} } } -} \ No newline at end of file +} -- GitLab From 4a70b2cffb23db148a9af50cfbf13c4655dbaf7b Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 10 Oct 2024 10:46:49 +0200 Subject: [PATCH 085/127] Remove redundant XCMs from dry run's forwarded xcms (#5913) # Description This PR addresses https://github.com/paritytech/polkadot-sdk/issues/5878. After dry running an xcm on asset hub, we had redundant xcms showing up in the `forwarded_xcms` field of the dry run effects returned. These were caused by two things: - The `UpwardMessageSender` router always added an element even if there were no messages. - The two routers on asset hub westend related to bridging (to rococo and sepolia) getting the message from their queues when their queues is actually the same xcmp queue that was already contemplated. In order to fix this, we check for no messages in UMP and clear the implementation of `InspectMessageQueues` for these bridging routers. Keep in mind that the bridged message is still sent, as normal via the xcmp-queue to Bridge Hub. To keep on dry-running the journey of the message, the next hop to dry-run is Bridge Hub. That'll be tackled in a different PR. Added a test in `bridge-hub-westend-integration-tests` and `bridge-hub-rococo-integration-tests` that show that dry-running a transfer across the bridge from asset hub results in one and only one message sent to bridge hub. ## TODO - [x] Functionality - [x] Test --------- Co-authored-by: command-bot <> --- Cargo.lock | 2 + .../modules/xcm-bridge-hub-router/src/lib.rs | 35 +++----------- cumulus/pallets/parachain-system/src/lib.rs | 6 ++- .../emulated/common/src/macros.rs | 48 +++++++++++++++++++ .../bridges/bridge-hub-rococo/Cargo.toml | 1 + .../bridges/bridge-hub-rococo/src/lib.rs | 4 +- .../src/tests/asset_transfers.rs | 9 ++++ .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../bridges/bridge-hub-westend/src/lib.rs | 4 +- .../src/tests/asset_transfers.rs | 9 ++++ .../xcm/xcm-builder/src/universal_exports.rs | 10 ++-- prdoc/pr_5913.prdoc | 20 ++++++++ 12 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 prdoc/pr_5913.prdoc diff --git a/Cargo.lock b/Cargo.lock index fa8f486e01f..ca71985b69f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2228,6 +2228,7 @@ dependencies = [ "staging-xcm", "staging-xcm-executor", "testnet-parachains-constants", + "xcm-runtime-apis", ] [[package]] @@ -2421,6 +2422,7 @@ dependencies = [ "staging-xcm", "staging-xcm-executor", "testnet-parachains-constants", + "xcm-runtime-apis", ] [[package]] diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index 7ba524e95b1..fe8f5a2efdf 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -99,7 +99,7 @@ pub mod pallet { type DestinationVersion: GetVersion; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. - type ToBridgeHubSender: SendXcm + InspectMessageQueues; + type ToBridgeHubSender: SendXcm; /// Local XCM channel manager. type LocalXcmChannelManager: XcmChannelStatusProvider; @@ -408,12 +408,12 @@ impl, I: 'static> SendXcm for Pallet { } impl, I: 'static> InspectMessageQueues for Pallet { - fn clear_messages() { - ViaBridgeHubExporter::::clear_messages() - } + fn clear_messages() {} + /// This router needs to implement `InspectMessageQueues` but doesn't have to + /// return any messages, since it just reuses the `XcmpQueue` router. fn get_messages() -> Vec<(VersionedLocation, Vec>)> { - ViaBridgeHubExporter::::get_messages() + Vec::new() } } @@ -646,34 +646,13 @@ mod tests { } #[test] - fn get_messages_works() { + fn get_messages_does_not_return_anything() { run_test(|| { assert_ok!(send_xcm::( (Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)).into(), vec![ClearOrigin].into() )); - assert_eq!( - XcmBridgeHubRouter::get_messages(), - vec![( - VersionedLocation::V4((Parent, Parachain(1002)).into()), - vec![VersionedXcm::V4( - Xcm::builder() - .withdraw_asset((Parent, 1_002_000)) - .buy_execution((Parent, 1_002_000), Unlimited) - .set_appendix( - Xcm::builder_unsafe() - .deposit_asset(AllCounted(1), (Parent, Parachain(1000))) - .build() - ) - .export_message( - Kusama, - Parachain(1000), - Xcm::builder_unsafe().clear_origin().build() - ) - .build() - )], - ),], - ); + assert_eq!(XcmBridgeHubRouter::get_messages(), vec![]); }); } } diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index a4b505b98e5..98989a852b8 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1627,7 +1627,11 @@ impl InspectMessageQueues for Pallet { .map(|encoded_message| VersionedXcm::<()>::decode(&mut &encoded_message[..]).unwrap()) .collect(); - vec![(VersionedLocation::V4(Parent.into()), messages)] + if messages.is_empty() { + vec![] + } else { + vec![(VersionedLocation::from(Location::parent()), messages)] + } } } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index 578bca84ce5..68926b04bfe 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -403,3 +403,51 @@ macro_rules! test_chain_can_claim_assets { } }; } + +#[macro_export] +macro_rules! test_dry_run_transfer_across_pk_bridge { + ( $sender_asset_hub:ty, $sender_bridge_hub:ty, $destination:expr ) => { + $crate::macros::paste::paste! { + use frame_support::{dispatch::RawOrigin, traits::fungible}; + use sp_runtime::AccountId32; + use xcm::prelude::*; + use xcm_runtime_apis::dry_run::runtime_decl_for_dry_run_api::DryRunApiV1; + + let who = AccountId32::new([1u8; 32]); + let transfer_amount = 10_000_000_000_000u128; + let initial_balance = transfer_amount * 10; + + // Bridge setup. + $sender_asset_hub::force_xcm_version($destination, XCM_VERSION); + open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); + + <$sender_asset_hub as TestExt>::execute_with(|| { + type Runtime = <$sender_asset_hub as Chain>::Runtime; + type RuntimeCall = <$sender_asset_hub as Chain>::RuntimeCall; + type OriginCaller = <$sender_asset_hub as Chain>::OriginCaller; + type Balances = <$sender_asset_hub as [<$sender_asset_hub Pallet>]>::Balances; + + // Give some initial funds. + >::set_balance(&who, initial_balance); + + let call = RuntimeCall::PolkadotXcm(pallet_xcm::Call::transfer_assets { + dest: Box::new(VersionedLocation::from($destination)), + beneficiary: Box::new(VersionedLocation::from(Junction::AccountId32 { + id: who.clone().into(), + network: None, + })), + assets: Box::new(VersionedAssets::from(vec![ + (Parent, transfer_amount).into(), + ])), + fee_asset_item: 0, + weight_limit: Unlimited, + }); + let result = Runtime::dry_run_call(OriginCaller::system(RawOrigin::Signed(who)), call).unwrap(); + // We assert the dry run succeeds and sends only one message to the local bridge hub. + assert!(result.execution_result.is_ok()); + assert_eq!(result.forwarded_xcms.len(), 1); + assert_eq!(result.forwarded_xcms[0].0, VersionedLocation::from(Location::new(1, [Parachain($sender_bridge_hub::para_id().into())]))); + }); + } + }; +} 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 86ace7d564e..9f6fe78a33e 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 @@ -28,6 +28,7 @@ sp-runtime = { workspace = true } xcm = { workspace = true } pallet-xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true } # Bridges pallet-bridge-messages = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index e83b2807678..a542de16de5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -33,8 +33,8 @@ mod imports { pub use emulated_integration_tests_common::{ accounts::ALICE, impls::Inspect, - test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, - test_relay_is_trusted_teleporter, + test_dry_run_transfer_across_pk_bridge, test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, 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 11930798da4..f1e2a6dcca6 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 @@ -534,3 +534,12 @@ fn send_back_wnds_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_weste assert!(receiver_wnds_after > receiver_wnds_before); assert!(receiver_wnds_after <= receiver_wnds_before + amount); } + +#[test] +fn dry_run_transfer_to_westend_sends_xcm_to_bridge_hub() { + test_dry_run_transfer_across_pk_bridge!( + AssetHubRococo, + BridgeHubRococo, + asset_hub_westend_location() + ); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index 44121cbfdaf..b87f25ac0f0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -29,6 +29,7 @@ sp-runtime = { workspace = true } xcm = { workspace = true } pallet-xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true } # Bridges pallet-bridge-messages = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index d9b92cb11e9..9228700c019 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -32,8 +32,8 @@ mod imports { pub use emulated_integration_tests_common::{ accounts::ALICE, impls::Inspect, - test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, - test_relay_is_trusted_teleporter, + test_dry_run_transfer_across_pk_bridge, test_parachain_is_trusted_teleporter, + test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, 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 6492c520234..7def637a66e 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 @@ -554,3 +554,12 @@ fn send_back_rocs_from_penpal_westend_through_asset_hub_westend_to_asset_hub_roc assert!(receiver_rocs_after > receiver_rocs_before); assert!(receiver_rocs_after <= receiver_rocs_before + amount); } + +#[test] +fn dry_run_transfer_to_rococo_sends_xcm_to_bridge_hub() { + test_dry_run_transfer_across_pk_bridge!( + AssetHubWestend, + BridgeHubWestend, + asset_hub_rococo_location() + ); +} diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 30e0b7c72b0..5c754f01ec0 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -337,15 +337,15 @@ impl InspectMessageQueues +impl InspectMessageQueues for SovereignPaidRemoteExporter { - fn clear_messages() { - Router::clear_messages() - } + fn clear_messages() {} + /// This router needs to implement `InspectMessageQueues` but doesn't have to + /// return any messages, since it just reuses the `XcmpQueue` router. fn get_messages() -> Vec<(VersionedLocation, Vec>)> { - Router::get_messages() + Vec::new() } } diff --git a/prdoc/pr_5913.prdoc b/prdoc/pr_5913.prdoc new file mode 100644 index 00000000000..f50cd722c71 --- /dev/null +++ b/prdoc/pr_5913.prdoc @@ -0,0 +1,20 @@ +# 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 redundant XCMs from dry run's forwarded xcms + +doc: + - audience: Runtime User + description: | + The DryRunApi was returning the same message repeated multiple times in the + `forwarded_xcms` field. This is no longer the case. + +crates: + - name: pallet-xcm-bridge-hub-router + bump: patch + - name: cumulus-pallet-parachain-system + bump: patch + - name: staging-xcm-builder + bump: patch + - name: emulated-integration-tests-common + bump: minor -- GitLab From cba7d13bc7d132fe758b859393bca7ddd45c690f Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Thu, 10 Oct 2024 12:47:29 +0300 Subject: [PATCH 086/127] Fix `0003-beefy-and-mmr` test (#6003) Resolves https://github.com/paritytech/polkadot-sdk/issues/5972 Only needed to increase some timeouts --- .gitlab/pipeline/zombienet/polkadot.yml | 12 ++++++------ .../functional/0003-beefy-and-mmr.zndsl | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 33191d99941..d1738083994 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -94,7 +94,7 @@ zombienet-polkadot-functional-0002-parachains-disputes: --local-dir="${LOCAL_DIR}/functional" --test="0002-parachains-disputes.zndsl" -.zombienet-polkadot-functional-0003-beefy-and-mmr: +zombienet-polkadot-functional-0003-beefy-and-mmr: extends: - .zombienet-polkadot-common script: @@ -172,7 +172,7 @@ zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: variables: FORCED_INFRA_INSTANCE: "spot-iops" before_script: - - !reference [.zombienet-polkadot-common, before_script] + - !reference [ .zombienet-polkadot-common, before_script ] - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh @@ -183,7 +183,7 @@ zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: extends: - .zombienet-polkadot-common before_script: - - !reference [.zombienet-polkadot-common, before_script] + - !reference [ .zombienet-polkadot-common, before_script ] - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh @@ -218,7 +218,7 @@ zombienet-polkadot-functional-0015-coretime-shared-core: extends: - .zombienet-polkadot-common before_script: - - !reference [.zombienet-polkadot-common, before_script] + - !reference [ .zombienet-polkadot-common, before_script ] - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional script: - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh @@ -360,10 +360,10 @@ zombienet-polkadot-malus-0001-dispute-valid: - job: build-polkadot-zombienet-tests artifacts: true before_script: - - !reference [".zombienet-polkadot-common", "before_script"] + - !reference [ ".zombienet-polkadot-common", "before_script" ] - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" script: - # we want to use `--no-capture` in zombienet tests. + # we want to use `--no-capture` in zombienet tests. - unset NEXTEST_FAILURE_OUTPUT - unset NEXTEST_SUCCESS_OUTPUT - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- smoke::coretime_revenue::coretime_revenue_test diff --git a/polkadot/zombienet_tests/functional/0003-beefy-and-mmr.zndsl b/polkadot/zombienet_tests/functional/0003-beefy-and-mmr.zndsl index 8300ef051f0..4fc066a13b0 100644 --- a/polkadot/zombienet_tests/functional/0003-beefy-and-mmr.zndsl +++ b/polkadot/zombienet_tests/functional/0003-beefy-and-mmr.zndsl @@ -18,22 +18,22 @@ validator-unstable: reports substrate_beefy_best_block is at least 1 within 60 s validator-unstable: pause # Verify validator sets get changed on new sessions. -validator: reports substrate_beefy_validator_set_id is at least 1 within 70 seconds +validator: reports substrate_beefy_validator_set_id is at least 1 within 180 seconds # Check next session too. -validator: reports substrate_beefy_validator_set_id is at least 2 within 130 seconds +validator: reports substrate_beefy_validator_set_id is at least 2 within 180 seconds # Verify voting happens and blocks are being finalized for new sessions too: # since we verified we're at least in the 3rd session, verify BEEFY finalized mandatory #21. -validator: reports substrate_beefy_best_block is at least 21 within 130 seconds +validator: reports substrate_beefy_best_block is at least 21 within 180 seconds # Custom JS to test BEEFY RPCs. -validator-0: js-script ./0003-beefy-finalized-heads.js with "validator-0,validator-1,validator-2" return is 1 within 5 seconds +validator-0: js-script ./0003-beefy-finalized-heads.js with "validator-0,validator-1,validator-2" return is 1 within 60 seconds # Custom JS to test MMR RPCs. -validator: js-script ./0003-mmr-leaves.js with "21" return is 1 within 5 seconds -validator: js-script ./0003-mmr-generate-and-verify-proof.js with "validator-0,validator-1,validator-2" return is 1 within 5 seconds +validator: js-script ./0003-mmr-leaves.js with "21" return is 1 within 60 seconds +validator: js-script ./0003-mmr-generate-and-verify-proof.js with "validator-0,validator-1,validator-2" return is 1 within 60 seconds # Resume validator-unstable and verify it imports all BEEFY justification and catches up. validator-unstable: resume -validator-unstable: reports substrate_beefy_validator_set_id is at least 2 within 30 seconds -validator-unstable: reports substrate_beefy_best_block is at least 21 within 30 seconds +validator-unstable: reports substrate_beefy_validator_set_id is at least 2 within 60 seconds +validator-unstable: reports substrate_beefy_best_block is at least 21 within 60 seconds -- GitLab From 439b31ef9bbdf0e0709bbab4804a9590b48ba8f0 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:57:39 +0100 Subject: [PATCH 087/127] Set larger timeout for cmd.yml (#6006) 1800 min --- .github/workflows/cmd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index f8bc7cb5b60..cbe39bc4e3e 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -283,6 +283,7 @@ jobs: env: JOB_NAME: "cmd" runs-on: ${{ needs.set-image.outputs.RUNNER }} + timeout-minutes: 1800 # 30 hours as it could take a long time to run all the runtimes/pallets container: image: ${{ needs.set-image.outputs.IMAGE }} steps: -- GitLab From c16ac9250c0f41287a8bae1b47469b85b67e7a80 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:53:29 +0200 Subject: [PATCH 088/127] [ci] Remove quick-benchmarks-omni from GitLab (#6014) The `quick-benchmarks-omni` job was moved to GHA (can be found [here](https://github.com/paritytech/polkadot-sdk/blob/439b31ef9bbdf0e0709bbab4804a9590b48ba8f0/.github/workflows/check-frame-omni-bencher.yml#L22)) but hasn't been removed from GitLab . PR fixes it and makes the check required. --- .github/workflows/check-frame-omni-bencher.yml | 4 ++-- .gitlab/pipeline/test.yml | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index 935fc080c4e..924a8b7f712 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -86,14 +86,14 @@ jobs: forklift cargo build --release --locked -p $PACKAGE_NAME -p frame-omni-bencher --features=${{ matrix.runtime.bench_features }} --quiet echo "Running short benchmarking for PACKAGE_NAME=$PACKAGE_NAME and RUNTIME_BLOB_PATH=$RUNTIME_BLOB_PATH" ls -lrt $RUNTIME_BLOB_PATH - + cmd="./target/release/frame-omni-bencher v1 benchmark pallet --runtime $RUNTIME_BLOB_PATH --all --steps 2 --repeat 1 $FLAGS" echo "Running command: $cmd" eval "$cmd" confirm-frame-omni-benchers-passed: runs-on: ubuntu-latest name: All benchmarks passed - needs: run-frame-omni-bencher + needs: [quick-benchmarks-omni, run-frame-omni-bencher] if: always() && !cancelled() steps: - run: | diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 00a0aa2c977..81e9ad12751 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -136,20 +136,3 @@ test-rustdoc: SKIP_WASM_BUILD: 1 script: - time cargo doc --workspace --all-features --no-deps - -quick-benchmarks-omni: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-C debug-assertions" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-C debug-assertions" - script: - - time cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks - - time cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet -- GitLab From e5ccc008c71e2208191559e9e8a3a8f4a85485f3 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Fri, 11 Oct 2024 13:51:35 +0200 Subject: [PATCH 089/127] Rename QueueEvent::StartWork (#6015) # Description When we send `QueueEvent::StartWork`, we have already completed the execution. This may be a leftover of a previous logic change. Currently, the name is misleading, so it would be better to rename it to `FinishWork`. https://github.com/paritytech/polkadot-sdk/blob/c52675efdc05e181ddcec72d3bd425dc0a89d622/polkadot/node/core/pvf/src/execute/queue.rs#L632-L646 https://github.com/paritytech/polkadot-sdk/blob/c52675efdc05e181ddcec72d3bd425dc0a89d622/polkadot/node/core/pvf/src/execute/queue.rs#L361-L363 Fixes https://github.com/paritytech/polkadot-sdk/issues/5910 ## Integration Shouldn't affect downstream projects. --------- Co-authored-by: GitHub Action --- polkadot/node/core/pvf/src/execute/queue.rs | 6 +++--- prdoc/pr_6015.prdoc | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6015.prdoc diff --git a/polkadot/node/core/pvf/src/execute/queue.rs b/polkadot/node/core/pvf/src/execute/queue.rs index 096cec3501b..2ac5116912e 100644 --- a/polkadot/node/core/pvf/src/execute/queue.rs +++ b/polkadot/node/core/pvf/src/execute/queue.rs @@ -143,7 +143,7 @@ impl Workers { enum QueueEvent { Spawn(IdleWorker, WorkerHandle, ExecuteJob), - StartWork( + FinishWork( Worker, Result, ArtifactId, @@ -333,7 +333,7 @@ async fn handle_mux(queue: &mut Queue, event: QueueEvent) { QueueEvent::Spawn(idle, handle, job) => { handle_worker_spawned(queue, idle, handle, job); }, - QueueEvent::StartWork(worker, outcome, artifact_id, result_tx) => { + QueueEvent::FinishWork(worker, outcome, artifact_id, result_tx) => { handle_job_finish(queue, worker, outcome, artifact_id, result_tx).await; }, } @@ -615,7 +615,7 @@ fn assign(queue: &mut Queue, worker: Worker, job: ExecuteJob) { job.pov, ) .await; - QueueEvent::StartWork(worker, result, job.artifact.id, job.result_tx) + QueueEvent::FinishWork(worker, result, job.artifact.id, job.result_tx) } .boxed(), ); diff --git a/prdoc/pr_6015.prdoc b/prdoc/pr_6015.prdoc new file mode 100644 index 00000000000..d5a7d1e18d5 --- /dev/null +++ b/prdoc/pr_6015.prdoc @@ -0,0 +1,9 @@ +title: Rename QueueEvent::StartWork +doc: +- audience: Node Dev + description: |- + When we send `QueueEvent::StartWork`, we have already completed the execution. Therefore, `QueueEvent::FinishWork` is a better match. + +crates: +- name: polkadot-node-core-pvf + bump: patch -- GitLab From b45f89c51fbd58e984e5e013992dd26715cb8bdc Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:55:00 +0200 Subject: [PATCH 090/127] `substrate-node`: removed excessive polkadot-sdk features (#5925) Some of the features enabled for `polkadot-sdk` umbrella crate were not necessary for substrate node (e.g. all `cumulus-*` or `polkadot-*` features) resulting in much longer compilation time. This PR fixes that. --- substrate/bin/node/cli/Cargo.toml | 86 ++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 6e734a723cd..04dcb909b8c 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -48,7 +48,91 @@ rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } # The Polkadot-SDK: -polkadot-sdk = { features = ["node"], workspace = true, default-features = true } +polkadot-sdk = { features = [ + "fork-tree", + "frame-benchmarking-cli", + "frame-remote-externalities", + "frame-support-procedural-tools", + "generate-bags", + "mmr-gadget", + "mmr-rpc", + "pallet-contracts-mock-network", + "pallet-revive-mock-network", + "pallet-transaction-payment-rpc", + "sc-allocator", + "sc-authority-discovery", + "sc-basic-authorship", + "sc-block-builder", + "sc-chain-spec", + "sc-cli", + "sc-client-api", + "sc-client-db", + "sc-consensus", + "sc-consensus-aura", + "sc-consensus-babe", + "sc-consensus-babe-rpc", + "sc-consensus-beefy", + "sc-consensus-beefy-rpc", + "sc-consensus-epochs", + "sc-consensus-grandpa", + "sc-consensus-grandpa-rpc", + "sc-consensus-manual-seal", + "sc-consensus-pow", + "sc-consensus-slots", + "sc-executor", + "sc-executor-common", + "sc-executor-polkavm", + "sc-executor-wasmtime", + "sc-informant", + "sc-keystore", + "sc-mixnet", + "sc-network", + "sc-network-common", + "sc-network-gossip", + "sc-network-light", + "sc-network-statement", + "sc-network-sync", + "sc-network-transactions", + "sc-network-types", + "sc-offchain", + "sc-proposer-metrics", + "sc-rpc", + "sc-rpc-api", + "sc-rpc-server", + "sc-rpc-spec-v2", + "sc-service", + "sc-state-db", + "sc-statement-store", + "sc-storage-monitor", + "sc-sync-state-rpc", + "sc-sysinfo", + "sc-telemetry", + "sc-tracing", + "sc-transaction-pool", + "sc-transaction-pool-api", + "sc-utils", + "sp-blockchain", + "sp-consensus", + "sp-core-hashing", + "sp-core-hashing-proc-macro", + "sp-database", + "sp-maybe-compressed-blob", + "sp-panic-handler", + "sp-rpc", + "staging-chain-spec-builder", + "staging-node-inspect", + "staging-tracking-allocator", + "std", + "subkey", + "substrate-build-script-utils", + "substrate-frame-rpc-support", + "substrate-frame-rpc-system", + "substrate-prometheus-endpoint", + "substrate-rpc-client", + "substrate-state-trie-migration-rpc", + "substrate-wasm-builder", + "tracing-gum", +], workspace = true, default-features = true } # Shared code between the staging node and kitchensink runtime: kitchensink-runtime = { workspace = true } -- GitLab From c0b734336a68b6f48ac70a9b9507d8ddb9fed57e Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:20:57 +0100 Subject: [PATCH 091/127] /cmd: Improved devx of benching many pallets simultaneously (#6007) ### Improved devx of running many pallets simultaneously Changes to /cmd: - Replace (flip) `--continue-on-fail` with `--fail-fast`, but only for `bench`. This makes all pallets/runtimes run non-stop by default, as it was primary use-case during tests - The list of successful/failed pallets was hard to find within tons of logs, so decided to write only needed logs in a file, and output as a summary in the workflow and in the comment - Side fix: updated `tasks_example` to `pallet_example_tasks` to make compliant with standard naming image + added command results to workflow summary: image --------- Co-authored-by: GitHub Action --- .github/scripts/cmd/cmd.py | 47 ++++++++++++++-------- .github/scripts/cmd/test_cmd.py | 20 ++++----- .github/workflows/cmd.yml | 38 +++++++++++++++-- README.md | 1 - docs/contributor/commands-readme.md | 5 --- docs/contributor/weight-generation.md | 34 ++++++++-------- substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/examples/tasks/src/mock.rs | 6 +-- 8 files changed, 98 insertions(+), 55 deletions(-) diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index 01f36ea375c..6a624bf4237 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -15,12 +15,21 @@ runtimesMatrix = json.load(f) runtimeNames = list(map(lambda x: x['name'], runtimesMatrix)) common_args = { - '--continue-on-fail': {"action": "store_true", "help": "Won't exit(1) on failed command and continue with next steps. "}, '--quiet': {"action": "store_true", "help": "Won't print start/end/failed messages in PR"}, '--clean': {"action": "store_true", "help": "Clean up the previous bot's & author's comments in PR"}, '--image': {"help": "Override docker image '--image docker.io/paritytech/ci-unified:latest'"}, } +def print_and_log(message, output_file='/tmp/cmd/command_output.log'): + print(message) + with open(output_file, 'a') as f: + f.write(message + '\n') + +def setup_logging(): + if not os.path.exists('/tmp/cmd'): + os.makedirs('/tmp/cmd') + open('/tmp/cmd/command_output.log', 'w') + parser = argparse.ArgumentParser(prog="/cmd ", description='A command runner for polkadot-sdk repo', add_help=False) parser.add_argument('--help', action=_HelpAction, help='help for help if you need some help') # help for help for arg, config in common_args.items(): @@ -28,6 +37,8 @@ for arg, config in common_args.items(): subparsers = parser.add_subparsers(help='a command to run', dest='command') +setup_logging() + """ BENCH """ @@ -39,8 +50,8 @@ bench_example = '''**Examples**: Runs benchmarks for pallet_balances and pallet_multisig for all runtimes which have these pallets. **--quiet** makes it to output nothing to PR but reactions %(prog)s --pallet pallet_balances pallet_xcm_benchmarks::generic --quiet - Runs bench for all pallets for westend runtime and continues even if some benchmarks fail - %(prog)s --runtime westend --continue-on-fail + Runs bench for all pallets for westend runtime and fails fast on first failed benchmark + %(prog)s --runtime westend --fail-fast Does not output anything and cleans up the previous bot's & author command triggering comments in PR %(prog)s --runtime westend rococo --pallet pallet_balances pallet_multisig --quiet --clean @@ -53,6 +64,7 @@ for arg, config in common_args.items(): parser_bench.add_argument('--runtime', help='Runtime(s) space separated', choices=runtimeNames, nargs='*', default=runtimeNames) parser_bench.add_argument('--pallet', help='Pallet(s) space separated', nargs='*', default=[]) +parser_bench.add_argument('--fail-fast', help='Fail fast on first failed benchmark', action='store_true') """ FMT @@ -156,7 +168,9 @@ def main(): manifest_path = os.popen(search_manifest_path).read() if not manifest_path: print(f'-- pallet {pallet} not found in dev runtime') - exit(1) + if args.fail_fast: + print_and_log(f'Error: {pallet} not found in dev runtime') + sys.exit(1) package_dir = os.path.dirname(manifest_path) print(f'-- package_dir: {package_dir}') print(f'-- manifest_path: {manifest_path}') @@ -186,8 +200,9 @@ def main(): f"{config['bench_flags']}" print(f'-- Running: {cmd} \n') status = os.system(cmd) - if status != 0 and not args.continue_on_fail: - print(f'Failed to benchmark {pallet} in {runtime}') + + if status != 0 and args.fail_fast: + print_and_log(f'❌ Failed to benchmark {pallet} in {runtime}') sys.exit(1) # Otherwise collect failed benchmarks and print them at the end @@ -198,14 +213,14 @@ def main(): successful_benchmarks[f'{runtime}'] = successful_benchmarks.get(f'{runtime}', []) + [pallet] if failed_benchmarks: - print('❌ Failed benchmarks of runtimes/pallets:') + print_and_log('❌ Failed benchmarks of runtimes/pallets:') for runtime, pallets in failed_benchmarks.items(): - print(f'-- {runtime}: {pallets}') + print_and_log(f'-- {runtime}: {pallets}') if successful_benchmarks: - print('✅ Successful benchmarks of runtimes/pallets:') + print_and_log('✅ Successful benchmarks of runtimes/pallets:') for runtime, pallets in successful_benchmarks.items(): - print(f'-- {runtime}: {pallets}') + print_and_log(f'-- {runtime}: {pallets}') elif args.command == 'fmt': command = f"cargo +nightly fmt" @@ -213,8 +228,8 @@ def main(): nightly_status = os.system(f'{command}') taplo_status = os.system('taplo format --config .config/taplo.toml') - if (nightly_status != 0 or taplo_status != 0) and not args.continue_on_fail: - print('❌ Failed to format code') + if (nightly_status != 0 or taplo_status != 0): + print_and_log('❌ Failed to format code') sys.exit(1) elif args.command == 'update-ui': @@ -222,15 +237,15 @@ def main(): print(f'Updating ui with `{command}`') status = os.system(f'{command}') - if status != 0 and not args.continue_on_fail: - print('❌ Failed to format code') + if status != 0: + print_and_log('❌ Failed to update ui') sys.exit(1) elif args.command == 'prdoc': # Call the main function from ./github/scripts/generate-prdoc.py module exit_code = generate_prdoc.main(args) - if exit_code != 0 and not args.continue_on_fail: - print('❌ Failed to generate prdoc') + if exit_code != 0: + print_and_log('❌ Failed to generate prdoc') sys.exit(exit_code) print('🚀 Done') diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index 0316c7ff1bb..faad3f261b9 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -96,7 +96,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=list(map(lambda x: x['name'], mock_runtimes_matrix)), pallet=['pallet_balances'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -153,7 +153,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['westend'], pallet=['pallet_balances', 'pallet_staking'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -196,7 +196,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['westend'], pallet=['pallet_xcm_benchmarks::generic'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -232,7 +232,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['westend', 'rococo'], pallet=['pallet_balances', 'pallet_staking'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -290,7 +290,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['dev'], pallet=['pallet_balances'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -327,7 +327,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['asset-hub-westend'], pallet=['pallet_assets'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -362,7 +362,7 @@ class TestCmd(unittest.TestCase): command='bench', runtime=['asset-hub-westend'], pallet=['pallet_xcm_benchmarks::generic', 'pallet_assets'], - continue_on_fail=False, + fail_fast=True, quiet=False, clean=False, image=None @@ -400,7 +400,7 @@ class TestCmd(unittest.TestCase): self.mock_system.assert_has_calls(expected_calls, any_order=True) - @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='fmt', continue_on_fail=False), [])) + @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='fmt'), [])) @patch('os.system', return_value=0) def test_fmt_command(self, mock_system, mock_parse_args): with patch('sys.exit') as mock_exit: @@ -410,7 +410,7 @@ class TestCmd(unittest.TestCase): mock_system.assert_any_call('cargo +nightly fmt') mock_system.assert_any_call('taplo format --config .config/taplo.toml') - @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='update-ui', continue_on_fail=False), [])) + @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='update-ui'), [])) @patch('os.system', return_value=0) def test_update_ui_command(self, mock_system, mock_parse_args): with patch('sys.exit') as mock_exit: @@ -419,7 +419,7 @@ class TestCmd(unittest.TestCase): mock_exit.assert_not_called() mock_system.assert_called_with('sh ./scripts/update-ui-tests.sh') - @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='prdoc', continue_on_fail=False), [])) + @patch('argparse.ArgumentParser.parse_known_args', return_value=(argparse.Namespace(command='prdoc'), [])) @patch('os.system', return_value=0) def test_prdoc_command(self, mock_system, mock_parse_args): with patch('sys.exit') as mock_exit: diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index cbe39bc4e3e..1818cdc11bb 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -369,6 +369,23 @@ jobs: git status git diff + if [ -f /tmp/cmd/command_output.log ]; then + CMD_OUTPUT=$(cat /tmp/cmd/command_output.log) + # export to summary to display in the PR + echo "$CMD_OUTPUT" >> $GITHUB_STEP_SUMMARY + # should be multiline, otherwise it captures the first line only + echo 'cmd_output<> $GITHUB_OUTPUT + echo "$CMD_OUTPUT" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + fi + + - name: Upload command output + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: command-output + path: /tmp/cmd/command_output.log + - name: Commit changes run: | if [ -n "$(git status --porcelain)" ]; then @@ -414,35 +431,50 @@ jobs: uses: actions/github-script@v7 env: SUBWEIGHT: "${{ steps.subweight.outputs.result }}" + CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | let runUrl = ${{ steps.build-link.outputs.run_url }} let subweight = process.env.SUBWEIGHT; + let cmdOutput = process.env.CMD_OUTPUT; + console.log(cmdOutput); - let subweightCollapsed = subweight + let subweightCollapsed = subweight.trim() !== '' ? `
\n\nSubweight results:\n\n${subweight}\n\n
` : ''; + let cmdOutputCollapsed = cmdOutput.trim() !== '' + ? `
\n\nCommand output:\n\n${cmdOutput}\n\n
` + : ''; + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}` + body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` }) - name: Comment PR (Failure) if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }} uses: actions/github-script@v7 + env: + CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | let jobUrl = ${{ steps.build-link.outputs.job_url }} + let cmdOutput = process.env.CMD_OUTPUT; + + let cmdOutputCollapsed = cmdOutput.trim() !== '' + ? `
\n\nCommand output:\n\n${cmdOutput}\n\n
` + : ''; + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})` + body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}` }) - name: Add 😕 reaction on failure diff --git a/README.md b/README.md index b8ddf8427c9..8016b6b3730 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -
![SDK Logo](./docs/images/Polkadot_Logo_Horizontal_Pink_White.png#gh-dark-mode-only) diff --git a/docs/contributor/commands-readme.md b/docs/contributor/commands-readme.md index 3a0fadc3bb2..52c554cc709 100644 --- a/docs/contributor/commands-readme.md +++ b/docs/contributor/commands-readme.md @@ -24,11 +24,6 @@ By default, the Start and End/Failure of the command will be commented with the If you want to avoid, use this flag. Go to [Action Tab](https://github.com/paritytech/polkadot-sdk/actions/workflows/cmd.yml) to see the pipeline status. -2.`--continue-on-fail` to continue running the command even if something inside a command -(like specific pallet weight generation) are failed. -Basically avoids interruption in the middle with `exit 1` -The pipeline logs will include what is failed (like which runtimes/pallets), then you can re-run them separately or not. - 3.`--clean` to clean up all yours and bot's comments in PR relevant to `/cmd` commands. If you run too many commands, or they keep failing, and you're rerunning them again, it's handy to add this flag to keep a PR clean. diff --git a/docs/contributor/weight-generation.md b/docs/contributor/weight-generation.md index 77a570c9453..a22a55404a4 100644 --- a/docs/contributor/weight-generation.md +++ b/docs/contributor/weight-generation.md @@ -19,51 +19,53 @@ In a PR run the actions through comment: To regenerate all weights (however it will take long, so don't do it unless you really need it), run the following command: + ```sh /cmd bench ``` To generate weights for all pallets in a particular runtime(s), run the following command: + ```sh /cmd bench --runtime kusama polkadot ``` For Substrate pallets (supports sub-modules too): + ```sh /cmd bench --runtime dev --pallet pallet_asset_conversion_ops ``` > **📝 Note**: The action is not being run right-away, it will be queued and run in the next available runner. -So might be quick, but might also take up to 10 mins (That's in control of Github). -Once the action is run, you'll see reaction 👀 on original comment, and if you didn't pass `--quiet` - -it will also send a link to a pipeline when started, and link to whole workflow when finished. +> So might be quick, but might also take up to 10 mins (That's in control of Github). +> Once the action is run, you'll see reaction 👀 on original comment, and if you didn't pass `--quiet` - +> it will also send a link to a pipeline when started, and link to whole workflow when finished. +> +> **📝 Note**: It will try keep benchmarking even if some pallets failed, with the result of failed/successful pallets. +> +> If you want to fail fast on first failed benchmark, add `--fail-fast` flag to the command. --- -> **💡Hint #1** : if you run all runtimes or all pallets, it might be that some pallet in the middle is failed -to generate weights, thus it stops (fails) the whole pipeline. -> If you want, you can make it to continue running, even if some pallets are failed, add `--continue-on-fail` -flag to the command. The report will include which runtimes/pallets have failed, then you can re-run -them separately after all is done. - This way it runs all possible runtimes for the specified pallets, if it finds them in the runtime + ```sh /cmd bench --pallet pallet_balances pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible ``` If you want to run all specific pallet(s) for specific runtime(s), you can do it like this: + ```sh /cmd bench --runtime bridge-hub-polkadot --pallet pallet_xcm_benchmarks::generic pallet_xcm_benchmarks::fungible ``` - -> **💡Hint #2** : Sometimes when you run too many commands, or they keep failing and you're rerunning them again, -it's handy to add `--clean` flag to the command. This will clean up all yours and bot's comments in PR relevant to -/cmd commands. +> **💡Hint #1** : Sometimes when you run too many commands, or they keep failing and you're rerunning them again, +> it's handy to add `--clean` flag to the command. This will clean up all yours and bot's comments in PR relevant to +> /cmd commands. ```sh -/cmd bench --runtime kusama polkadot --pallet=pallet_balances --clean --continue-on-fail +/cmd bench --runtime kusama polkadot --pallet=pallet_balances --clean ``` -> **💡Hint #3** : If you have questions or need help, feel free to tag @paritytech/opstooling (in github comments) -or ping in [matrix](https://matrix.to/#/#command-bot:parity.io) channel. +> **💡Hint #2** : If you have questions or need help, feel free to tag @paritytech/opstooling (in github comments) +> or ping in [matrix](https://matrix.to/#/#command-bot:parity.io) channel. diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index c81921f844b..ececf0d87b2 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2643,7 +2643,7 @@ mod benches { [pallet_contracts, Contracts] [pallet_revive, Revive] [pallet_core_fellowship, CoreFellowship] - [tasks_example, TasksExample] + [pallet_example_tasks, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] diff --git a/substrate/frame/examples/tasks/src/mock.rs b/substrate/frame/examples/tasks/src/mock.rs index 33912bb5269..9a1112946f6 100644 --- a/substrate/frame/examples/tasks/src/mock.rs +++ b/substrate/frame/examples/tasks/src/mock.rs @@ -18,7 +18,7 @@ //! Mock runtime for `tasks-example` tests. #![cfg(test)] -use crate::{self as tasks_example}; +use crate::{self as pallet_example_tasks}; use frame_support::derive_impl; use sp_runtime::testing::TestXt; @@ -29,7 +29,7 @@ type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Runtime { System: frame_system, - TasksExample: tasks_example, + TasksExample: pallet_example_tasks, } ); @@ -48,7 +48,7 @@ where type Extrinsic = Extrinsic; } -impl tasks_example::Config for Runtime { +impl pallet_example_tasks::Config for Runtime { type RuntimeTask = RuntimeTask; type WeightInfo = (); } -- GitLab From d1c115b6197bf6c45d5640594f0432e6c2781a4f Mon Sep 17 00:00:00 2001 From: Julian Eager Date: Sun, 13 Oct 2024 05:20:04 +0800 Subject: [PATCH 092/127] Fix storage expansion in pallet section (#6023) fixes #5320 @sam0x17 @gupnik # Description The issue could be confirmed with the added example. The cause is for macro hygiene, `entries` in the `#( #entries_builder )*` expansion won't be able to reference the `entries` defined outside. The solution here is to allow the reference to be passed into the expansion with closure. Or we could just switch to the unhygienic span with `quote::quote!` instead such that `entries` will resolve to the "outer" definition. --- prdoc/pr_6023.prdoc | 11 +++++ .../procedural/src/pallet/expand/storage.rs | 22 +++++---- .../test/tests/split_ui/pass/split_storage.rs | 49 +++++++++++++++++++ .../test/tests/split_ui/pass/storage/mod.rs | 27 ++++++++++ 4 files changed, 99 insertions(+), 10 deletions(-) create mode 100644 prdoc/pr_6023.prdoc create mode 100644 substrate/frame/support/test/tests/split_ui/pass/split_storage.rs create mode 100644 substrate/frame/support/test/tests/split_ui/pass/storage/mod.rs diff --git a/prdoc/pr_6023.prdoc b/prdoc/pr_6023.prdoc new file mode 100644 index 00000000000..3b3b5a4cb5f --- /dev/null +++ b/prdoc/pr_6023.prdoc @@ -0,0 +1,11 @@ +title: Fix storage in pallet section + +doc: + - audience: Runtime Dev + description: | + Fix compilation of `pallet::storage` in a pallet section: a local binding definition was not + correctly referenced due to macro hygiene. + +crates: + - name: frame-support-procedural + bump: patch diff --git a/substrate/frame/support/procedural/src/pallet/expand/storage.rs b/substrate/frame/support/procedural/src/pallet/expand/storage.rs index e5bfa2793cb..10e674c3cb1 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/storage.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/storage.rs @@ -427,15 +427,17 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { }; entries_builder.push(quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* - { - <#full_ident as #frame_support::storage::StorageEntryMetadataBuilder>::build_metadata( - #deprecation, - #frame_support::__private::vec![ - #( #docs, )* - ], - &mut entries, - ); - } + (|entries: &mut #frame_support::__private::Vec<_>| { + { + <#full_ident as #frame_support::storage::StorageEntryMetadataBuilder>::build_metadata( + #deprecation, + #frame_support::__private::vec![ + #( #docs, )* + ], + entries, + ); + } + }) )) } @@ -911,7 +913,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { entries: { #[allow(unused_mut)] let mut entries = #frame_support::__private::vec![]; - #( #entries_builder )* + #( #entries_builder(&mut entries); )* entries }, } diff --git a/substrate/frame/support/test/tests/split_ui/pass/split_storage.rs b/substrate/frame/support/test/tests/split_ui/pass/split_storage.rs new file mode 100644 index 00000000000..e8601587fac --- /dev/null +++ b/substrate/frame/support/test/tests/split_ui/pass/split_storage.rs @@ -0,0 +1,49 @@ +// 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. + +use frame_support::pallet_macros::import_section; + +mod storage; + +#[import_section(storage::storage)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + pub fn increment_value(_origin: OriginFor) -> DispatchResult { + Value::::mutate(|v| { + v.saturating_add(1) + }); + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/split_ui/pass/storage/mod.rs b/substrate/frame/support/test/tests/split_ui/pass/storage/mod.rs new file mode 100644 index 00000000000..26974a750dc --- /dev/null +++ b/substrate/frame/support/test/tests/split_ui/pass/storage/mod.rs @@ -0,0 +1,27 @@ +// 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. + +use frame_support::pallet_macros::pallet_section; + +#[pallet_section] +mod storage { + #[pallet::storage] + pub type Value = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type Map = StorageMap<_, _, u32, u32, ValueQuery>; +} -- GitLab From ff87db8c42e55ac386a7431ebc3a5ce3ecdddcd3 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:46:40 +0100 Subject: [PATCH 093/127] update cmd timeout (#6038) 30 hrs -> 72 hours as it has stopped by timeout here https://github.com/paritytech/polkadot-sdk/actions/runs/11299872333/job/31431758932 --- .github/workflows/cmd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 1818cdc11bb..525ab0c0fc2 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -283,7 +283,7 @@ jobs: env: JOB_NAME: "cmd" runs-on: ${{ needs.set-image.outputs.RUNNER }} - timeout-minutes: 1800 # 30 hours as it could take a long time to run all the runtimes/pallets + timeout-minutes: 4320 # 72 hours -> 3 days; as it could take a long time to run all the runtimes/pallets container: image: ${{ needs.set-image.outputs.IMAGE }} steps: -- GitLab From d7f01a1795f2ca6aed8fc0abaa51bda78f5382b9 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Mon, 14 Oct 2024 17:36:51 +0300 Subject: [PATCH 094/127] Use the umbrella crate for the parachain template (#5991) Use the umbrella crate for the parachain template This covers almost all the dependencies. There are just a few exceptions for which I created a separate issue: https://github.com/paritytech/polkadot-sdk/issues/5993 Also related to: https://github.com/paritytech/polkadot-sdk/issues/4782 --- Cargo.lock | 96 +--------- substrate/frame/support/src/lib.rs | 5 +- templates/parachain/node/Cargo.toml | 68 +------ templates/parachain/node/build.rs | 2 +- templates/parachain/node/src/chain_spec.rs | 2 + templates/parachain/node/src/cli.rs | 1 + templates/parachain/node/src/command.rs | 2 + templates/parachain/node/src/main.rs | 2 + templates/parachain/node/src/rpc.rs | 2 + templates/parachain/node/src/service.rs | 4 +- .../parachain/pallets/template/Cargo.toml | 47 +---- .../pallets/template/src/benchmarking.rs | 2 +- .../parachain/pallets/template/src/lib.rs | 8 +- .../parachain/pallets/template/src/mock.rs | 13 +- .../parachain/pallets/template/src/tests.rs | 2 +- .../parachain/pallets/template/src/weights.rs | 2 +- templates/parachain/runtime/Cargo.toml | 166 ++++-------------- templates/parachain/runtime/src/apis.rs | 9 +- templates/parachain/runtime/src/benchmarks.rs | 2 +- .../parachain/runtime/src/configs/mod.rs | 4 + .../runtime/src/configs/xcm_config.rs | 5 + .../runtime/src/genesis_config_presets.rs | 7 +- templates/parachain/runtime/src/lib.rs | 11 +- .../runtime/src/weights/block_weights.rs | 4 + .../runtime/src/weights/extrinsic_weights.rs | 4 + .../runtime/src/weights/paritydb_weights.rs | 4 + .../runtime/src/weights/rocksdb_weights.rs | 4 + 27 files changed, 123 insertions(+), 355 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca71985b69f..131966b9a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12178,14 +12178,9 @@ dependencies = [ name = "pallet-parachain-template" version = "0.0.0" dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", "parity-scale-codec", + "polkadot-sdk-frame", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] @@ -13047,57 +13042,16 @@ version = "0.0.0" dependencies = [ "clap 4.5.13", "color-print", - "cumulus-client-cli", - "cumulus-client-collator", - "cumulus-client-consensus-aura", - "cumulus-client-consensus-common", - "cumulus-client-consensus-proposer", - "cumulus-client-service", - "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", - "cumulus-relay-chain-interface", "docify", - "frame-benchmarking", - "frame-benchmarking-cli", "futures", "jsonrpsee 0.24.3", "log", - "pallet-transaction-payment-rpc", "parachain-template-runtime", "parity-scale-codec", - "polkadot-cli", - "polkadot-primitives", - "sc-basic-authorship", - "sc-chain-spec", - "sc-cli", - "sc-client-api", - "sc-consensus", - "sc-executor 0.32.0", - "sc-network", - "sc-network-sync", - "sc-offchain", - "sc-rpc", - "sc-service", - "sc-sysinfo", - "sc-telemetry", + "polkadot-sdk", "sc-tracing", - "sc-transaction-pool", - "sc-transaction-pool-api", "serde", "serde_json", - "sp-api 26.0.0", - "sp-block-builder", - "sp-blockchain", - "sp-consensus-aura", - "sp-core 28.0.0", - "sp-genesis-builder", - "sp-io 30.0.0", - "sp-keystore 0.34.0", - "sp-runtime 31.0.1", - "sp-timestamp", - "staging-xcm", - "substrate-build-script-utils", - "substrate-frame-rpc-system", "substrate-prometheus-endpoint", ] @@ -13105,60 +13059,16 @@ dependencies = [ name = "parachain-template-runtime" version = "0.0.0" dependencies = [ - "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", - "cumulus-pallet-session-benchmarking", - "cumulus-pallet-xcm", - "cumulus-pallet-xcmp-queue", - "cumulus-primitives-aura", - "cumulus-primitives-core", - "cumulus-primitives-storage-weight-reclaim", - "cumulus-primitives-utility", "docify", - "frame-benchmarking", - "frame-executive", - "frame-metadata-hash-extension", - "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "frame-try-runtime", "hex-literal", "log", - "pallet-aura", - "pallet-authorship", - "pallet-balances", - "pallet-collator-selection", - "pallet-message-queue", "pallet-parachain-template", - "pallet-session", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-xcm", - "parachains-common", "parity-scale-codec", - "polkadot-parachain-primitives", - "polkadot-runtime-common", + "polkadot-sdk", "scale-info", "serde_json", "smallvec", - "sp-api 26.0.0", - "sp-block-builder", - "sp-consensus-aura", - "sp-core 28.0.0", - "sp-genesis-builder", - "sp-inherents", - "sp-offchain", - "sp-runtime 31.0.1", - "sp-session", - "sp-transaction-pool", - "sp-version 29.0.0", - "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", "substrate-wasm-builder", ] diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 269867e38c5..d76073a97a3 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -915,7 +915,10 @@ pub mod pallet_prelude { pub use scale_info::TypeInfo; pub use sp_inherents::MakeFatalError; pub use sp_runtime::{ - traits::{MaybeSerializeDeserialize, Member, ValidateUnsigned}, + traits::{ + CheckedAdd, CheckedConversion, CheckedDiv, CheckedMul, CheckedShl, CheckedShr, + CheckedSub, MaybeSerializeDeserialize, Member, One, ValidateUnsigned, Zero, + }, transaction_validity::{ InvalidTransaction, TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction, diff --git a/templates/parachain/node/Cargo.toml b/templates/parachain/node/Cargo.toml index c0c81d8222a..ba5f1212b79 100644 --- a/templates/parachain/node/Cargo.toml +++ b/templates/parachain/node/Cargo.toml @@ -10,9 +10,6 @@ edition.workspace = true publish = false build = "build.rs" -# [[bin]] -# name = "parachain-template-node" - [dependencies] clap = { features = ["derive"], workspace = true } log = { workspace = true, default-features = true } @@ -22,82 +19,31 @@ jsonrpsee = { features = ["server"], workspace = true } futures = { workspace = true } serde_json = { workspace = true, default-features = true } docify = { workspace = true } +color-print = { workspace = true } + +polkadot-sdk = { workspace = true, features = ["node"] } -# Local parachain-template-runtime = { workspace = true } # Substrate -frame-benchmarking = { workspace = true, default-features = true } -frame-benchmarking-cli = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-sync = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -substrate-frame-rpc-system = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -# Polkadot -polkadot-cli = { features = ["rococo-native"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -xcm = { workspace = true } - -# Cumulus -cumulus-client-cli = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } -cumulus-client-consensus-aura = { workspace = true, default-features = true } -cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-client-consensus-proposer = { workspace = true, default-features = true } -cumulus-client-service = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } -color-print = { workspace = true } - [build-dependencies] -substrate-build-script-utils = { workspace = true, default-features = true } +polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } [features] default = ["std"] std = [ "log/std", "parachain-template-runtime/std", - "xcm/std", + "polkadot-sdk/std", ] runtime-benchmarks = [ - "cumulus-primitives-core/runtime-benchmarks", - "frame-benchmarking-cli/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", "parachain-template-runtime/runtime-benchmarks", - "polkadot-cli/runtime-benchmarks", - "polkadot-primitives/runtime-benchmarks", - "sc-service/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "polkadot-sdk/runtime-benchmarks", ] try-runtime = [ "parachain-template-runtime/try-runtime", - "polkadot-cli/try-runtime", - "sp-runtime/try-runtime", + "polkadot-sdk/try-runtime", ] diff --git a/templates/parachain/node/build.rs b/templates/parachain/node/build.rs index e3bfe3116bf..8ee8f23d854 100644 --- a/templates/parachain/node/build.rs +++ b/templates/parachain/node/build.rs @@ -1,4 +1,4 @@ -use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; +use polkadot_sdk::substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { generate_cargo_keys(); diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index af82607bc8e..55a099dd022 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -1,3 +1,5 @@ +use polkadot_sdk::*; + use parachain_template_runtime as runtime; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; diff --git a/templates/parachain/node/src/cli.rs b/templates/parachain/node/src/cli.rs index f008e856d99..c8bdbc10d75 100644 --- a/templates/parachain/node/src/cli.rs +++ b/templates/parachain/node/src/cli.rs @@ -1,3 +1,4 @@ +use polkadot_sdk::*; use std::path::PathBuf; /// Sub-commands supported by the collator. diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs index 6b9deade127..938bda837e0 100644 --- a/templates/parachain/node/src/command.rs +++ b/templates/parachain/node/src/command.rs @@ -1,3 +1,5 @@ +use polkadot_sdk::*; + use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; diff --git a/templates/parachain/node/src/main.rs b/templates/parachain/node/src/main.rs index 12738a6793c..46ebcfd266d 100644 --- a/templates/parachain/node/src/main.rs +++ b/templates/parachain/node/src/main.rs @@ -2,6 +2,8 @@ #![warn(missing_docs)] +use polkadot_sdk::*; + mod chain_spec; mod cli; mod command; diff --git a/templates/parachain/node/src/rpc.rs b/templates/parachain/node/src/rpc.rs index 4937469e11e..7549a5d090d 100644 --- a/templates/parachain/node/src/rpc.rs +++ b/templates/parachain/node/src/rpc.rs @@ -9,6 +9,8 @@ use std::sync::Arc; use parachain_template_runtime::{opaque::Block, AccountId, Balance, Nonce}; +use polkadot_sdk::*; + use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; diff --git a/templates/parachain/node/src/service.rs b/templates/parachain/node/src/service.rs index 04e20be2bd4..6729b9d7ef4 100644 --- a/templates/parachain/node/src/service.rs +++ b/templates/parachain/node/src/service.rs @@ -3,14 +3,16 @@ // std use std::{sync::Arc, time::Duration}; -use cumulus_client_cli::CollatorOptions; // Local Runtime Types use parachain_template_runtime::{ apis::RuntimeApi, opaque::{Block, Hash}, }; +use polkadot_sdk::*; + // Cumulus Imports +use cumulus_client_cli::CollatorOptions; use cumulus_client_collator::service::CollatorService; #[docify::export(lookahead_collator)] use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams}; diff --git a/templates/parachain/pallets/template/Cargo.toml b/templates/parachain/pallets/template/Cargo.toml index dde86310137..dc1088cb33f 100644 --- a/templates/parachain/pallets/template/Cargo.toml +++ b/templates/parachain/pallets/template/Cargo.toml @@ -13,45 +13,16 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = [ - "derive", -], workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } -# frame deps -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } - -# primitive deps -sp-runtime = { workspace = true } - -[dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } +frame = { workspace = true, default-features = false, features = [ + "experimental", + "runtime", +] } [features] default = ["std"] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -std = [ - "codec/std", - "scale-info/std", - - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - - "sp-runtime/std", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", -] +runtime-benchmarks = ["frame/runtime-benchmarks"] +std = ["codec/std", "frame/std", "scale-info/std"] +try-runtime = ["frame/try-runtime"] diff --git a/templates/parachain/pallets/template/src/benchmarking.rs b/templates/parachain/pallets/template/src/benchmarking.rs index 29572c3ff60..9f2d09904f5 100644 --- a/templates/parachain/pallets/template/src/benchmarking.rs +++ b/templates/parachain/pallets/template/src/benchmarking.rs @@ -1,7 +1,7 @@ //! Benchmarking setup for pallet-template use super::*; -use frame_benchmarking::v2::*; +use frame::{deps::frame_benchmarking::v2::*, prelude::*}; #[benchmarks] mod benchmarks { diff --git a/templates/parachain/pallets/template/src/lib.rs b/templates/parachain/pallets/template/src/lib.rs index 6bfb98972ae..211bef51aa8 100644 --- a/templates/parachain/pallets/template/src/lib.rs +++ b/templates/parachain/pallets/template/src/lib.rs @@ -66,17 +66,13 @@ mod benchmarking; // To see a full list of `pallet` macros and their use cases, see: // // -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, DefaultNoBound}; - use frame_system::pallet_prelude::*; - use sp_runtime::traits::{CheckedAdd, One}; + use frame::prelude::*; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. - /// type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// A type representing the weights required by the dispatchables of this pallet. diff --git a/templates/parachain/pallets/template/src/mock.rs b/templates/parachain/pallets/template/src/mock.rs index b8c730b6eb5..b924428d414 100644 --- a/templates/parachain/pallets/template/src/mock.rs +++ b/templates/parachain/pallets/template/src/mock.rs @@ -1,9 +1,12 @@ -use frame_support::{derive_impl, weights::constants::RocksDbWeight}; -use frame_system::{mocking::MockBlock, GenesisConfig}; -use sp_runtime::{traits::ConstU64, BuildStorage}; +use frame::{ + deps::{frame_support::weights::constants::RocksDbWeight, frame_system::GenesisConfig}, + prelude::*, + runtime::prelude::*, + testing_prelude::*, +}; // Configure a mock runtime to test the pallet. -#[frame_support::runtime] +#[frame_construct_runtime] mod test_runtime { #[runtime::runtime] #[runtime::derive( @@ -39,6 +42,6 @@ impl crate::Config for Test { } // Build genesis storage according to the mock runtime. -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { GenesisConfig::::default().build_storage().unwrap().into() } diff --git a/templates/parachain/pallets/template/src/tests.rs b/templates/parachain/pallets/template/src/tests.rs index 2f641ce08fb..14609fd6dba 100644 --- a/templates/parachain/pallets/template/src/tests.rs +++ b/templates/parachain/pallets/template/src/tests.rs @@ -1,5 +1,5 @@ use crate::{mock::*, Error, Something}; -use frame_support::{assert_noop, assert_ok}; +use frame::testing_prelude::*; #[test] fn it_works_for_default_value() { diff --git a/templates/parachain/pallets/template/src/weights.rs b/templates/parachain/pallets/template/src/weights.rs index 4b4522429c6..9295492bc20 100644 --- a/templates/parachain/pallets/template/src/weights.rs +++ b/templates/parachain/pallets/template/src/weights.rs @@ -29,7 +29,7 @@ #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use frame::{deps::frame_support::weights::constants::RocksDbWeight, prelude::*}; use core::marker::PhantomData; /// Weight functions needed for pallet_template. diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index 236b7c04863..c9c08608f3b 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -32,168 +32,66 @@ serde_json = { workspace = true, default-features = false, features = ["alloc"] # Local pallet-parachain-template = { workspace = true } -# Substrate / FRAME -frame-benchmarking = { optional = true, workspace = true } -frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } -frame-support = { features = ["experimental"], workspace = true } -frame-system = { workspace = true } -frame-system-benchmarking = { optional = true, workspace = true } -frame-system-rpc-runtime-api = { workspace = true } -frame-try-runtime = { optional = true, workspace = true } +polkadot-sdk = { workspace = true, default-features = false, features = [ + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-message-queue", + "pallet-session", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", -# FRAME Pallets -pallet-aura = { workspace = true } -pallet-authorship = { workspace = true } -pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } -pallet-session = { workspace = true } -pallet-sudo = { workspace = true } -pallet-timestamp = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } + "pallet-xcm", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", -# Substrate Primitives -sp-api = { workspace = true } -sp-block-builder = { workspace = true } -sp-consensus-aura = { workspace = true } -sp-core = { workspace = true } -sp-genesis-builder = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-runtime = { workspace = true } -sp-session = { workspace = true } -sp-transaction-pool = { workspace = true } -sp-version = { workspace = true } + "cumulus-pallet-aura-ext", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", + "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", + "cumulus-primitives-utility", + "pallet-collator-selection", + "parachains-common", + "staging-parachain-info", -# Polkadot -pallet-xcm = { workspace = true } -polkadot-parachain-primitives = { workspace = true } -polkadot-runtime-common = { workspace = true } -xcm = { workspace = true } -xcm-builder = { workspace = true } -xcm-executor = { workspace = true } + "runtime", +] } # Cumulus -cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } -cumulus-pallet-session-benchmarking = { workspace = true } -cumulus-pallet-xcm = { workspace = true } -cumulus-pallet-xcmp-queue = { workspace = true } -cumulus-primitives-aura = { workspace = true } -cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } -pallet-collator-selection = { workspace = true } -parachains-common = { workspace = true } -parachain-info = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/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-storage-weight-reclaim/std", - "cumulus-primitives-utility/std", - "frame-benchmarking?/std", - "frame-executive/std", - "frame-metadata-hash-extension/std", - "frame-support/std", - "frame-system-benchmarking?/std", - "frame-system-rpc-runtime-api/std", - "frame-system/std", - "frame-try-runtime?/std", "log/std", - "pallet-aura/std", - "pallet-authorship/std", - "pallet-balances/std", - "pallet-collator-selection/std", - "pallet-message-queue/std", "pallet-parachain-template/std", - "pallet-session/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "pallet-xcm/std", - "parachain-info/std", - "parachains-common/std", - "polkadot-parachain-primitives/std", - "polkadot-runtime-common/std", + "polkadot-sdk/std", "scale-info/std", "serde_json/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-core/std", - "sp-genesis-builder/std", - "sp-inherents/std", - "sp-offchain/std", - "sp-runtime/std", - "sp-session/std", - "sp-transaction-pool/std", - "sp-version/std", "substrate-wasm-builder", - "xcm-builder/std", - "xcm-executor/std", - "xcm/std", ] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", - "cumulus-pallet-session-benchmarking/runtime-benchmarks", - "cumulus-pallet-xcmp-queue/runtime-benchmarks", - "cumulus-primitives-core/runtime-benchmarks", - "cumulus-primitives-utility/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", "hex-literal", - "pallet-balances/runtime-benchmarks", - "pallet-collator-selection/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", "pallet-parachain-template/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", + "polkadot-sdk/runtime-benchmarks", ] try-runtime = [ - "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", - "cumulus-pallet-xcm/try-runtime", - "cumulus-pallet-xcmp-queue/try-runtime", - "frame-executive/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "frame-try-runtime/try-runtime", - "pallet-aura/try-runtime", - "pallet-authorship/try-runtime", - "pallet-balances/try-runtime", - "pallet-collator-selection/try-runtime", - "pallet-message-queue/try-runtime", "pallet-parachain-template/try-runtime", - "pallet-session/try-runtime", - "pallet-sudo/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "pallet-xcm/try-runtime", - "parachain-info/try-runtime", - "polkadot-runtime-common/try-runtime", - "sp-runtime/try-runtime", + "polkadot-sdk/try-runtime", ] # Enable the metadata hash generation. diff --git a/templates/parachain/runtime/src/apis.rs b/templates/parachain/runtime/src/apis.rs index 243db1b6dde..eba9293a67b 100644 --- a/templates/parachain/runtime/src/apis.rs +++ b/templates/parachain/runtime/src/apis.rs @@ -25,6 +25,9 @@ // External crates imports use alloc::vec::Vec; + +use polkadot_sdk::*; + use frame_support::{ genesis_builder_helper::{build_state, get_preset}, weights::Weight, @@ -241,10 +244,10 @@ impl_runtime_apis! { impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( Vec, - Vec, + Vec, ) { use frame_benchmarking::{Benchmarking, BenchmarkList}; - use frame_support::traits::StorageInfoTrait; + use polkadot_sdk::frame_support::traits::StorageInfoTrait; use frame_system_benchmarking::Pallet as SystemBench; use cumulus_pallet_session_benchmarking::Pallet as SessionBench; use super::*; @@ -277,7 +280,7 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} - use frame_support::traits::WhitelistedStorageKeys; + use polkadot_sdk::frame_support::traits::WhitelistedStorageKeys; let whitelist = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); diff --git a/templates/parachain/runtime/src/benchmarks.rs b/templates/parachain/runtime/src/benchmarks.rs index 9fbf1ad82bd..aae50e7258c 100644 --- a/templates/parachain/runtime/src/benchmarks.rs +++ b/templates/parachain/runtime/src/benchmarks.rs @@ -23,7 +23,7 @@ // // For more information, please refer to -frame_benchmarking::define_benchmarks!( +polkadot_sdk::frame_benchmarking::define_benchmarks!( [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_session, SessionBench::] diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index 43ab63bd0b6..0cf6497fd95 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -25,6 +25,10 @@ mod xcm_config; +use polkadot_sdk::{staging_parachain_info as parachain_info, staging_xcm as xcm, *}; +#[cfg(not(feature = "runtime-benchmarks"))] +use polkadot_sdk::{staging_xcm_builder as xcm_builder, staging_xcm_executor as xcm_executor}; + // Substrate and Polkadot dependencies use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; diff --git a/templates/parachain/runtime/src/configs/xcm_config.rs b/templates/parachain/runtime/src/configs/xcm_config.rs index e162bcbf886..3da3b711f4f 100644 --- a/templates/parachain/runtime/src/configs/xcm_config.rs +++ b/templates/parachain/runtime/src/configs/xcm_config.rs @@ -2,6 +2,11 @@ use crate::{ AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, }; + +use polkadot_sdk::{ + staging_xcm as xcm, staging_xcm_builder as xcm_builder, staging_xcm_executor as xcm_executor, *, +}; + use frame_support::{ parameter_types, traits::{ConstU32, Contains, Everything, Nothing}, diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index fec53d17394..ac2aef734f4 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -1,10 +1,13 @@ -use cumulus_primitives_core::ParaId; - use crate::{ AccountId, BalancesConfig, CollatorSelectionConfig, ParachainInfoConfig, PolkadotXcmConfig, RuntimeGenesisConfig, SessionConfig, SessionKeys, SudoConfig, EXISTENTIAL_DEPOSIT, }; + use alloc::{vec, vec::Vec}; + +use polkadot_sdk::{staging_xcm as xcm, *}; + +use cumulus_primitives_core::ParaId; use parachains_common::{genesis_config_helpers::*, AuraId}; use serde_json::Value; use sp_core::sr25519; diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index ccec648ce4c..cb30eb0e80d 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -16,6 +16,9 @@ mod weights; extern crate alloc; use alloc::vec::Vec; use smallvec::smallvec; + +use polkadot_sdk::{staging_parachain_info as parachain_info, *}; + use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{BlakeTwo256, IdentifyAccount, Verify}, @@ -33,9 +36,6 @@ use frame_support::weights::{ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; -#[cfg(any(feature = "std", test))] -pub use sp_runtime::BuildStorage; - use weights::ExtrinsicBaseWeight; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. @@ -140,13 +140,12 @@ impl WeightToFeePolynomial for WeightToFee { /// to even the core data structures. pub mod opaque { use super::*; - use sp_runtime::{ + pub use polkadot_sdk::sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + use polkadot_sdk::sp_runtime::{ generic, traits::{BlakeTwo256, Hash as HashT}, }; - pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; - /// Opaque block header type. pub type Header = generic::Header; /// Opaque block type. diff --git a/templates/parachain/runtime/src/weights/block_weights.rs b/templates/parachain/runtime/src/weights/block_weights.rs index e7fdb2aae2a..9e095a412ec 100644 --- a/templates/parachain/runtime/src/weights/block_weights.rs +++ b/templates/parachain/runtime/src/weights/block_weights.rs @@ -16,6 +16,8 @@ // limitations under the License. pub mod constants { + use polkadot_sdk::*; + use frame_support::{ parameter_types, weights::{constants, Weight}, @@ -29,6 +31,8 @@ pub mod constants { #[cfg(test)] mod test_weights { + use polkadot_sdk::*; + use frame_support::weights::constants; /// Checks that the weight exists and is sane. diff --git a/templates/parachain/runtime/src/weights/extrinsic_weights.rs b/templates/parachain/runtime/src/weights/extrinsic_weights.rs index 1a4adb968bb..1a00a9cd039 100644 --- a/templates/parachain/runtime/src/weights/extrinsic_weights.rs +++ b/templates/parachain/runtime/src/weights/extrinsic_weights.rs @@ -16,6 +16,8 @@ // limitations under the License. pub mod constants { + use polkadot_sdk::*; + use frame_support::{ parameter_types, weights::{constants, Weight}, @@ -29,6 +31,8 @@ pub mod constants { #[cfg(test)] mod test_weights { + use polkadot_sdk::*; + use frame_support::weights::constants; /// Checks that the weight exists and is sane. diff --git a/templates/parachain/runtime/src/weights/paritydb_weights.rs b/templates/parachain/runtime/src/weights/paritydb_weights.rs index 25679703831..9071c58ec7f 100644 --- a/templates/parachain/runtime/src/weights/paritydb_weights.rs +++ b/templates/parachain/runtime/src/weights/paritydb_weights.rs @@ -16,6 +16,8 @@ // limitations under the License. pub mod constants { + use polkadot_sdk::*; + use frame_support::{ parameter_types, weights::{constants, RuntimeDbWeight}, @@ -32,6 +34,8 @@ pub mod constants { #[cfg(test)] mod test_db_weights { + use polkadot_sdk::*; + use super::constants::ParityDbWeight as W; use frame_support::weights::constants; diff --git a/templates/parachain/runtime/src/weights/rocksdb_weights.rs b/templates/parachain/runtime/src/weights/rocksdb_weights.rs index 3dd817aa6f1..89e0b643aab 100644 --- a/templates/parachain/runtime/src/weights/rocksdb_weights.rs +++ b/templates/parachain/runtime/src/weights/rocksdb_weights.rs @@ -16,6 +16,8 @@ // limitations under the License. pub mod constants { + use polkadot_sdk::*; + use frame_support::{ parameter_types, weights::{constants, RuntimeDbWeight}, @@ -32,6 +34,8 @@ pub mod constants { #[cfg(test)] mod test_db_weights { + use polkadot_sdk::*; + use super::constants::RocksDbWeight as W; use frame_support::weights::constants; -- GitLab From aca11dcca533b0b8a721c6e4b112fd2f3bba6a29 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 14 Oct 2024 19:28:16 +0100 Subject: [PATCH 095/127] Westend: Constant yearly emission (#5999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Testing the approach of this before it goes live on Polkadot https://github.com/polkadot-fellows/runtimes/pull/471 --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Dónal Murray --- Cargo.lock | 1 + Cargo.toml | 1 + polkadot/runtime/westend/Cargo.toml | 1 + polkadot/runtime/westend/src/lib.rs | 47 ++++++------- polkadot/runtime/westend/src/tests.rs | 95 +++++++++++++++++++++++++++ prdoc/pr_5999.prdoc | 15 +++++ 6 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 prdoc/pr_5999.prdoc diff --git a/Cargo.lock b/Cargo.lock index 131966b9a33..a5aa286485f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26256,6 +26256,7 @@ dependencies = [ name = "westend-runtime" version = "7.0.0" dependencies = [ + "approx", "binary-merkle-tree", "bitvec", "frame-benchmarking", diff --git a/Cargo.toml b/Cargo.toml index fde05e90ca6..08f06982d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -587,6 +587,7 @@ alloy-primitives = { version = "0.4.2", default-features = false } alloy-sol-types = { version = "0.4.2", default-features = false } always-assert = { version = "0.1" } anyhow = { version = "1.0.81", default-features = false } +approx = { version = "0.5.1" } aquamarine = { version = "0.5.0" } arbitrary = { version = "1.3.2" } ark-bls12-377 = { version = "0.4.0", default-features = false } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 28ffd9fb150..16bec1a7702 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -118,6 +118,7 @@ xcm-builder = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] +approx = { workspace = true } tiny-keccak = { features = ["keccak"], workspace = true } sp-keyring = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index fe1777bc94e..0216ccaf491 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -65,8 +65,8 @@ use polkadot_runtime_common::{ elections::OnChainAccuracy, identity_migrator, impl_runtime_weights, impls::{ - relay_era_payout, ContainsParts, EraPayoutParams, LocatableAssetConverter, ToAuthor, - VersionedLocatableAsset, VersionedLocationConverter, + ContainsParts, LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, + VersionedLocationConverter, }, paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, traits::OnSwap, @@ -681,33 +681,26 @@ impl pallet_bags_list::Config for Runtime { pub struct EraPayout; impl pallet_staking::EraPayout for EraPayout { fn era_payout( - total_staked: Balance, - total_issuance: Balance, + _total_staked: Balance, + _total_issuance: Balance, era_duration_millis: u64, ) -> (Balance, Balance) { - const MILLISECONDS_PER_YEAR: u64 = 1000 * 3600 * 24 * 36525 / 100; - - let params = EraPayoutParams { - total_staked, - total_stakable: total_issuance, - ideal_stake: dynamic_params::inflation::IdealStake::get(), - max_annual_inflation: dynamic_params::inflation::MaxInflation::get(), - min_annual_inflation: dynamic_params::inflation::MinInflation::get(), - falloff: dynamic_params::inflation::Falloff::get(), - period_fraction: Perquintill::from_rational(era_duration_millis, MILLISECONDS_PER_YEAR), - legacy_auction_proportion: if dynamic_params::inflation::UseAuctionSlots::get() { - let auctioned_slots = parachains_paras::Parachains::::get() - .into_iter() - // all active para-ids that do not belong to a system chain is the number of - // parachains that we should take into account for inflation. - .filter(|i| *i >= 2000.into()) - .count() as u64; - Some(Perquintill::from_rational(auctioned_slots.min(60), 200u64)) - } else { - None - }, - }; - relay_era_payout(params) + const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100; + // A normal-sized era will have 1 / 365.25 here: + let relative_era_len = + FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into()); + + // Fixed total TI that we use as baseline for the issuance. + let fixed_total_issuance: i128 = 5_216_342_402_773_185_773; + let fixed_inflation_rate = FixedU128::from_rational(8, 100); + let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance); + + let era_emission = relative_era_len.saturating_mul_int(yearly_emission); + // 15% to treasury, as per Polkadot ref 1139. + let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission); + let to_stakers = era_emission.saturating_sub(to_treasury); + + (to_stakers.saturated_into(), to_treasury.saturated_into()) } } diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index eef95006bce..c1b396a4cd2 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -19,11 +19,15 @@ use std::collections::HashSet; use crate::{xcm_config::LocationConverter, *}; +use approx::assert_relative_eq; use frame_support::traits::WhitelistedStorageKeys; +use pallet_staking::EraPayout; use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; use sp_keyring::AccountKeyring::Alice; use xcm_runtime_apis::conversions::LocationToAccountHelper; +const MILLISECONDS_PER_HOUR: u64 = 60 * 60 * 1000; + #[test] fn remove_keys_weight_is_sensible() { use polkadot_runtime_common::crowdloan::WeightInfo; @@ -311,3 +315,94 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn staking_inflation_correct_single_era() { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + MILLISECONDS_PER_HOUR, + ); + + assert_relative_eq!(to_stakers as f64, (4_046 * CENTS) as f64, max_relative = 0.01); + assert_relative_eq!(to_treasury as f64, (714 * CENTS) as f64, max_relative = 0.01); + // Total per hour is ~47.6 WND + assert_relative_eq!( + (to_stakers as f64 + to_treasury as f64), + (4_760 * CENTS) as f64, + max_relative = 0.001 + ); +} + +#[test] +fn staking_inflation_correct_longer_era() { + // Twice the era duration means twice the emission: + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + 2 * MILLISECONDS_PER_HOUR, + ); + + assert_relative_eq!(to_stakers as f64, (4_046 * CENTS) as f64 * 2.0, max_relative = 0.001); + assert_relative_eq!(to_treasury as f64, (714 * CENTS) as f64 * 2.0, max_relative = 0.001); +} + +#[test] +fn staking_inflation_correct_whole_year() { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + (36525 * 24 * MILLISECONDS_PER_HOUR) / 100, // 1 year + ); + + // Our yearly emissions is about 417k WND: + let yearly_emission = 417_307 * UNITS; + assert_relative_eq!( + to_stakers as f64 + to_treasury as f64, + yearly_emission as f64, + max_relative = 0.001 + ); + + assert_relative_eq!(to_stakers as f64, yearly_emission as f64 * 0.85, max_relative = 0.001); + assert_relative_eq!(to_treasury as f64, yearly_emission as f64 * 0.15, max_relative = 0.001); +} + +// 10 years into the future, our values do not overflow. +#[test] +fn staking_inflation_correct_not_overflow() { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + (36525 * 24 * MILLISECONDS_PER_HOUR) / 10, // 10 years + ); + let initial_ti: i128 = 5_216_342_402_773_185_773; + let projected_total_issuance = (to_stakers as i128 + to_treasury as i128) + initial_ti; + + // In 2034, there will be about 9.39 million WND in existence. + assert_relative_eq!( + projected_total_issuance as f64, + (9_390_000 * UNITS) as f64, + max_relative = 0.001 + ); +} + +// Print percent per year, just as convenience. +#[test] +fn staking_inflation_correct_print_percent() { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + (36525 * 24 * MILLISECONDS_PER_HOUR) / 100, // 1 year + ); + let yearly_emission = to_stakers + to_treasury; + let mut ti: i128 = 5_216_342_402_773_185_773; + + for y in 0..10 { + let new_ti = ti + yearly_emission as i128; + let inflation = 100.0 * (new_ti - ti) as f64 / ti as f64; + println!("Year {y} inflation: {inflation}%"); + ti = new_ti; + + assert!(inflation <= 8.0 && inflation > 2.0, "sanity check"); + } +} diff --git a/prdoc/pr_5999.prdoc b/prdoc/pr_5999.prdoc new file mode 100644 index 00000000000..5252de6289d --- /dev/null +++ b/prdoc/pr_5999.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: "Westend: Constant yearly emission" + +doc: + - audience: Runtime User + description: | + Integrating the new inflation approach from https://github.com/polkadot-fellows/runtimes/pull/471 + into Westend first to check that it is working. + + +crates: + - name: westend-runtime + bump: patch -- GitLab From 6f03f7a10182cf43e49623c4312000921856a033 Mon Sep 17 00:00:00 2001 From: Julian Eager Date: Tue, 15 Oct 2024 05:00:57 +0800 Subject: [PATCH 096/127] Fix `feeless_if` in pallet section (#6032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #5981 Could confirm the issue with the added tests: ``` test tests/split_ui/pass/split_call.rs [should pass] ... error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ error[E0423]: expected value, found attribute macro `origin` --> tests/split_ui/pass/split_call.rs:23:1 | 23 | #[frame_support::pallet(dev_mode)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not a value | = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) ``` # Description `origin` unexpectedly resolved to a macro, which is available at the span of invocation. The solution here is to use the expansion as a function instead of a call and pass in the desired values to avoid ambiguities. --- prdoc/pr_6032.prdoc | 11 ++++ .../procedural/src/pallet/expand/call.rs | 15 ++--- .../test/tests/split_ui/pass/call/mod.rs | 63 +++++++++++++++++++ .../test/tests/split_ui/pass/split_call.rs | 36 +++++++++++ 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_6032.prdoc create mode 100644 substrate/frame/support/test/tests/split_ui/pass/call/mod.rs create mode 100644 substrate/frame/support/test/tests/split_ui/pass/split_call.rs diff --git a/prdoc/pr_6032.prdoc b/prdoc/pr_6032.prdoc new file mode 100644 index 00000000000..ed47750f8fd --- /dev/null +++ b/prdoc/pr_6032.prdoc @@ -0,0 +1,11 @@ +title: Fix `feeless_if` in pallet section + +doc: + - audience: Runtime Dev + description: | + Fix compilation with `pallet::feeless_if` in a pallet section: a local binding unexpectely + resolved to a macro definition. + +crates: + - name: frame-support-procedural + bump: patch diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index 8b333d19087..206ffc1159f 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -253,13 +253,13 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { }) .collect::>(); - let feeless_check = methods.iter().map(|method| &method.feeless_check).collect::>(); - let feeless_check_result = - feeless_check.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| { - if let Some(feeless_check) = feeless_check { - quote::quote!(#feeless_check(origin, #( #arg_name, )*)) + let feeless_checks = methods.iter().map(|method| &method.feeless_check).collect::>(); + let feeless_check = + feeless_checks.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| { + if let Some(check) = feeless_check { + quote::quote_spanned!(span => #check) } else { - quote::quote!(false) + quote::quote_spanned!(span => |_origin, #( #arg_name, )*| { false }) } }); @@ -393,7 +393,8 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #( #cfg_attrs Self::#fn_name { #( #args_name_pattern_ref, )* } => { - #feeless_check_result + let feeless_check = #feeless_check; + feeless_check(origin, #( #args_name, )*) }, )* Self::__Ignore(_, _) => unreachable!("__Ignore cannot be used"), diff --git a/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs b/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs new file mode 100644 index 00000000000..27b3ec31b83 --- /dev/null +++ b/substrate/frame/support/test/tests/split_ui/pass/call/mod.rs @@ -0,0 +1,63 @@ +// 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. + +use frame_support::pallet_macros::pallet_section; + +#[pallet_section] +mod call { + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + pub fn noop0(origin: OriginFor) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + #[pallet::call_index(1)] + pub fn noop1(origin: OriginFor, _x: u64) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + #[pallet::call_index(2)] + pub fn noop2(origin: OriginFor, _x: u64, _y: u64) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::feeless_if(|_origin: &OriginFor| -> bool { true })] + pub fn noop_feeless0(origin: OriginFor) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::feeless_if(|_origin: &OriginFor, x: &u64| -> bool { *x == 1 })] + pub fn noop_feeless1(origin: OriginFor, _x: u64) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::feeless_if(|_origin: &OriginFor, x: &u64, y: &u64| -> bool { *x == *y })] + pub fn noop_feeless2(origin: OriginFor, _x: u64, _y: u64) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + } +} diff --git a/substrate/frame/support/test/tests/split_ui/pass/split_call.rs b/substrate/frame/support/test/tests/split_ui/pass/split_call.rs new file mode 100644 index 00000000000..09dbe6e3992 --- /dev/null +++ b/substrate/frame/support/test/tests/split_ui/pass/split_call.rs @@ -0,0 +1,36 @@ +// 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. + +use frame_support::pallet_macros::import_section; + +mod call; + +#[import_section(call::call)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} +} + +fn main() { +} -- GitLab From f7119e40fac5eac9e6f4fd9b16ea4dea99d7472b Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 15 Oct 2024 09:01:20 +0200 Subject: [PATCH 097/127] Remove `check-migrations` for rococo chain (#6061) This PR removes the `check-migrations` pipelines for all Rococo chains because they are going down: https://github.com/paritytech/devops/issues/3589, and these checks are starting to fail, e.g.: https://github.com/paritytech/polkadot-sdk/actions/runs/11339904745/job/31535485254?pr=4982 https://github.com/paritytech/polkadot-sdk/actions/runs/11339904745/job/31535486189?pr=4982 https://github.com/paritytech/polkadot-sdk/actions/runs/11339904745/job/31535486471?pr=4982 Additionally, `coretime-westend` was added to the `check-migrations` matrix. --- .github/workflows/check-runtime-migration.yml | 48 +++++-------------- .../coretime/coretime-westend/src/lib.rs | 1 + prdoc/pr_6061.prdoc | 10 ++++ 3 files changed, 22 insertions(+), 37 deletions(-) create mode 100644 prdoc/pr_6061.prdoc diff --git a/.github/workflows/check-runtime-migration.yml b/.github/workflows/check-runtime-migration.yml index 39a1c4d0348..758de0e7b43 100644 --- a/.github/workflows/check-runtime-migration.yml +++ b/.github/workflows/check-runtime-migration.yml @@ -36,14 +36,10 @@ jobs: network: [ westend, - rococo, asset-hub-westend, - asset-hub-rococo, bridge-hub-westend, - bridge-hub-rococo, - contracts-rococo, collectives-westend, - coretime-rococo, + coretime-westend, ] include: - network: westend @@ -52,49 +48,27 @@ jobs: uri: "wss://try-runtime-westend.polkadot.io:443" subcommand_extra_args: "--no-weight-warnings --blocktime 6000" command_extra_args: "" - - network: rococo - package: rococo-runtime - wasm: rococo_runtime.compact.compressed.wasm - uri: "wss://try-runtime-rococo.polkadot.io:443" - subcommand_extra_args: "--no-weight-warnings --blocktime 6000" - command_extra_args: "" - network: asset-hub-westend package: asset-hub-westend-runtime wasm: asset_hub_westend_runtime.compact.compressed.wasm uri: "wss://westend-asset-hub-rpc.polkadot.io:443" subcommand_extra_args: " --blocktime 6000" command_extra_args: "" - - network: "asset-hub-rococo" - package: "asset-hub-rococo-runtime" - wasm: "asset_hub_rococo_runtime.compact.compressed.wasm" - uri: "wss://rococo-asset-hub-rpc.polkadot.io:443" - subcommand_extra_args: " --blocktime 6000" - command_extra_args: "" - - network: "bridge-hub-westend" - package: "bridge-hub-westend-runtime" - wasm: "bridge_hub_westend_runtime.compact.compressed.wasm" + - network: bridge-hub-westend + package: bridge-hub-westend-runtime + wasm: bridge_hub_westend_runtime.compact.compressed.wasm uri: "wss://westend-bridge-hub-rpc.polkadot.io:443" subcommand_extra_args: " --blocktime 6000" - - network: "bridge-hub-rococo" - package: "bridge-hub-rococo-runtime" - wasm: "bridge_hub_rococo_runtime.compact.compressed.wasm" - uri: "wss://rococo-bridge-hub-rpc.polkadot.io:443" - subcommand_extra_args: " --blocktime 6000" - - network: "contracts-rococo" - package: "contracts-rococo-runtime" - wasm: "contracts_rococo_runtime.compact.compressed.wasm" - uri: "wss://rococo-contracts-rpc.polkadot.io:443" - subcommand_extra_args: " --blocktime 6000" - - network: "collectives-westend" - package: "collectives-westend-runtime" - wasm: "collectives_westend_runtime.compact.compressed.wasm" + - network: collectives-westend + package: collectives-westend-runtime + wasm: collectives_westend_runtime.compact.compressed.wasm uri: "wss://westend-collectives-rpc.polkadot.io:443" command_extra_args: "--disable-spec-name-check" subcommand_extra_args: " --blocktime 6000" - - network: "coretime-rococo" - package: "coretime-rococo-runtime" - wasm: "coretime_rococo_runtime.compact.compressed.wasm" - uri: "wss://rococo-coretime-rpc.polkadot.io:443" + - network: coretime-westend + package: coretime-westend-runtime + wasm: coretime_westend_runtime.compact.compressed.wasm + uri: "wss://westend-coretime-rpc.polkadot.io:443" subcommand_extra_args: " --blocktime 6000" steps: - name: Checkout diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index e909b804587..187856b6c61 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -120,6 +120,7 @@ pub type UncheckedExtrinsic = pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2, cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, pallet_broker::migration::MigrateV0ToV1, pallet_broker::migration::MigrateV1ToV2, pallet_broker::migration::MigrateV2ToV3, diff --git a/prdoc/pr_6061.prdoc b/prdoc/pr_6061.prdoc new file mode 100644 index 00000000000..742e69ea9ec --- /dev/null +++ b/prdoc/pr_6061.prdoc @@ -0,0 +1,10 @@ +title: Remove check-migrations for rococo chain + +doc: + - audience: [Runtime User] + description: | + This PR adds the missing `cumulus_pallet_xcmp_queue` v5 migration to the coretime-westend runtime. + +crates: +- name: coretime-westend-runtime + bump: none -- GitLab From 36eadec19de12ed68a9ecfdc148fbde1df937b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 15 Oct 2024 12:38:08 +0200 Subject: [PATCH 098/127] Remove "Check Features" test This is done by zepter any way --- .github/workflows/check-features.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/check-features.yml diff --git a/.github/workflows/check-features.yml b/.github/workflows/check-features.yml deleted file mode 100644 index d8e2f72c0ff..00000000000 --- a/.github/workflows/check-features.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Check Features - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - check-features: - runs-on: ubuntu-latest - steps: - - name: Fetch latest code - uses: actions/checkout@v4 - - name: Check - uses: hack-ink/cargo-featalign-action@bea88a864d6ca7d0c53c26f1391ce1d431dc7f34 # v0.1.1 - with: - crate: templates/parachain/runtime/ - features: std,runtime-benchmarks,try-runtime - ignore: sc-executor - default-std: true -- GitLab From b20be7c11733be65332518fb542e452e3bbd7eb7 Mon Sep 17 00:00:00 2001 From: Ayevbeosa Iyamu Date: Tue, 15 Oct 2024 11:14:34 +0100 Subject: [PATCH 099/127] pallet-xcm: added useful error logs (#2408) (#4982) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added error logs in pallet-xcm to help in debugging, fixes #2408 ## TODO - [x] change `log::error` to `tracing::error` format for `xcm-executor` - [x] check existing logs, e.g. this one can be extended with more info `tracing::error!(target: "xcm::reanchor", ?error, "Failed reanchoring with error");` - [x] use `tracing` instead of `log` for `pallet-xcm/src/lib.rs` --------- Co-authored-by: Ayevbeosa Iyamu Co-authored-by: Adrian Catangiu Co-authored-by: Francisco Aguirre Co-authored-by: Branislav Kontur Co-authored-by: Bastian Köcher --- Cargo.lock | 2 +- polkadot/xcm/pallet-xcm/Cargo.toml | 4 +- polkadot/xcm/pallet-xcm/src/benchmarking.rs | 8 +- polkadot/xcm/pallet-xcm/src/lib.rs | 192 ++++++++++++-------- polkadot/xcm/pallet-xcm/src/migration.rs | 6 +- polkadot/xcm/xcm-executor/src/lib.rs | 42 +++-- prdoc/pr_4982.prdoc | 13 ++ 7 files changed, 168 insertions(+), 99 deletions(-) create mode 100644 prdoc/pr_4982.prdoc diff --git a/Cargo.lock b/Cargo.lock index a5aa286485f..360e89d8ad8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12951,7 +12951,6 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log", "pallet-assets", "pallet-balances", "parity-scale-codec", @@ -12965,6 +12964,7 @@ dependencies = [ "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", + "tracing", "xcm-runtime-apis", ] diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index ed4b441d7c3..4d44d75e34d 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -14,7 +14,7 @@ bounded-collections = { workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -log = { workspace = true } +tracing = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -44,13 +44,13 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "log/std", "pallet-balances/std", "scale-info/std", "serde", "sp-core/std", "sp-io/std", "sp-runtime/std", + "tracing/std", "xcm-builder/std", "xcm-executor/std", "xcm-runtime-apis/std", diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index d09c81bf434..404b9358d4d 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -128,14 +128,14 @@ benchmarks! { &origin_location, None, ).map_err(|error| { - log::error!("Fungible asset couldn't be deposited, error: {:?}", error); + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, NonFungible(instance) => { ::AssetTransactor::deposit_asset(&asset, &origin_location, None) .map_err(|error| { - log::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; } @@ -178,14 +178,14 @@ benchmarks! { &origin_location, None, ).map_err(|error| { - log::error!("Fungible asset couldn't be deposited, error: {:?}", error); + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, NonFungible(instance) => { ::AssetTransactor::deposit_asset(&asset, &origin_location, None) .map_err(|error| { - log::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; } diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index ee448c3df60..d287987a96d 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -307,7 +307,7 @@ pub mod pallet { message: Box::RuntimeCall>>, max_weight: Weight, ) -> Result { - log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); + tracing::trace!(target: "xcm::pallet_xcm::execute", ?message, ?max_weight); let outcome = (|| { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; let mut hash = message.using_encoded(sp_io::hashing::blake2_256); @@ -330,7 +330,7 @@ pub mod pallet { Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); 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); + tracing::error!(target: "xcm::pallet_xcm::execute", ?error, "XCM execution failed with error"); Error::::LocalExecutionIncomplete.with_weight( weight_used.saturating_add( ::execute(), @@ -897,10 +897,10 @@ pub mod pallet { pub fn migrate_to_v1( ) -> frame_support::weights::Weight { let on_chain_storage_version =

::on_chain_storage_version(); - log::info!( + tracing::info!( target: "runtime::xcm", - "Running migration storage v1 for xcm with storage version {:?}", - on_chain_storage_version, + ?on_chain_storage_version, + "Running migration storage v1 for xcm with storage version", ); if on_chain_storage_version < 1 { @@ -910,18 +910,18 @@ pub mod pallet { Some(value.into()) }); StorageVersion::new(1).put::

(); - log::info!( + tracing::info!( target: "runtime::xcm", - "Running migration storage v1 for xcm with storage version {:?} was complete", - on_chain_storage_version, + ?on_chain_storage_version, + "Running migration storage v1 for xcm with storage version was complete", ); // calculate and return migration weights T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) } else { - log::warn!( + tracing::warn!( target: "runtime::xcm", - "Attempted to apply migration to v1 but failed because storage version is {:?}", - on_chain_storage_version, + ?on_chain_storage_version, + "Attempted to apply migration to v1 but failed because storage version is", ); T::DbWeight::get().reads(1) } @@ -1269,10 +1269,9 @@ pub mod pallet { let beneficiary: Location = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::transfer_assets", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", - origin, dest, beneficiary, assets, fee_asset_item, weight_limit, + ?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit, ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -1307,7 +1306,7 @@ pub mod pallet { beneficiary: Box, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", origin_location, assets, beneficiary); + tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary); // Extract version from `assets`. let assets_version = assets.identify_version(); let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; @@ -1330,7 +1329,7 @@ pub mod pallet { weight, ); outcome.ensure_complete().map_err(|error| { - log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", error); + tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error"); Error::::LocalExecutionIncomplete })?; Ok(()) @@ -1403,11 +1402,10 @@ pub mod pallet { (*remote_fees_id).try_into().map_err(|()| Error::::BadVersion)?; let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| Error::::BadVersion)?; - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::transfer_assets_using_type_and_then", - "origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \ - remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \ - custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}", + ?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type, + ?remote_xcm, ?weight_limit, ); let assets = assets.into_inner(); @@ -1568,10 +1566,9 @@ impl Pallet { let beneficiary: Location = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::do_reserve_transfer_assets", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}", - origin_location, dest, beneficiary, assets, fee_asset_item, + ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -1615,10 +1612,9 @@ impl Pallet { let beneficiary: Location = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::do_teleport_assets", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", - origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit, + ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit, ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -1719,11 +1715,9 @@ impl Pallet { fees: FeesHandling, weight_limit: WeightLimit, ) -> Result<(Xcm<::RuntimeCall>, Option>), Error> { - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::build_xcm_transfer_type", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ - fees_handling {:?}, weight_limit: {:?}", - origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, + ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit, ); match transfer_type { TransferType::LocalReserve => Self::local_reserve_transfer_programs( @@ -1778,10 +1772,9 @@ impl Pallet { mut local_xcm: Xcm<::RuntimeCall>, remote_xcm: Option>, ) -> DispatchResult { - log::debug!( + tracing::debug!( target: "xcm::pallet_xcm::execute_xcm_transfer", - "origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}", - origin, dest, local_xcm, remote_xcm, + ?origin, ?dest, ?local_xcm, ?remote_xcm, ); let weight = @@ -1795,10 +1788,10 @@ impl Pallet { weight, ); Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); - outcome.ensure_complete().map_err(|error| { - log::error!( + outcome.clone().ensure_complete().map_err(|error| { + tracing::error!( target: "xcm::pallet_xcm::execute_xcm_transfer", - "XCM execution failed with error {:?}", error + ?error, "XCM execution failed with error with outcome: {:?}", outcome ); Error::::LocalExecutionIncomplete })?; @@ -1807,10 +1800,10 @@ impl Pallet { let (ticket, price) = validate_send::(dest.clone(), remote_xcm.clone()) .map_err(Error::::from)?; if origin != Here.into_location() { - Self::charge_fees(origin.clone(), price).map_err(|error| { - log::error!( + Self::charge_fees(origin.clone(), price.clone()).map_err(|error| { + tracing::error!( target: "xcm::pallet_xcm::execute_xcm_transfer", - "Unable to charge fee with error {:?}", error + ?error, ?price, ?origin, "Unable to charge fee", ); Error::::FeesNotMet })?; @@ -1836,7 +1829,10 @@ impl Pallet { // no custom fees instructions, they are batched together with `assets` transfer; // BuyExecution happens after receiving all `assets` let reanchored_fees = - fees.reanchored(&dest, &context).map_err(|_| Error::::CannotReanchor)?; + fees.reanchored(&dest, &context).map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees"); + Error::::CannotReanchor + })?; // buy execution using `fees` batched together with above `reanchored_assets` remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit }); }, @@ -1901,7 +1897,10 @@ impl Pallet { let mut reanchored_assets = assets.clone(); reanchored_assets .reanchor(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets"); + Error::::CannotReanchor + })?; // XCM instructions to be executed on local chain let mut local_execute_xcm = Xcm(vec![ @@ -1948,7 +1947,10 @@ impl Pallet { let reanchored_fees = fees .clone() .reanchored(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees"); + Error::::CannotReanchor + })?; let fees: Assets = fees.into(); let local_execute_xcm = Xcm(vec![ @@ -1992,7 +1994,10 @@ impl Pallet { let mut reanchored_assets = assets.clone(); reanchored_assets .reanchor(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets"); + Error::::CannotReanchor + })?; // XCM instructions to be executed on local chain let mut local_execute_xcm = Xcm(vec![ @@ -2046,13 +2051,22 @@ impl Pallet { // identifies fee item as seen by `reserve` - to be used at reserve chain let reserve_fees = fees_half_1 .reanchored(&reserve, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees"); + Error::::CannotReanchor + })?; // identifies fee item as seen by `dest` - to be used at destination chain let dest_fees = fees_half_2 .reanchored(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees"); + Error::::CannotReanchor + })?; // identifies `dest` as seen by `reserve` - let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::::CannotReanchor)?; + let dest = dest.reanchored(&reserve, &context).map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest"); + Error::::CannotReanchor + })?; // xcm to be executed at dest let mut xcm_on_dest = Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]); @@ -2098,7 +2112,10 @@ impl Pallet { let reanchored_fees = fees .clone() .reanchored(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees"); + Error::::CannotReanchor + })?; // XcmContext irrelevant in teleports checks let dummy_context = @@ -2112,7 +2129,10 @@ impl Pallet { &fees, &dummy_context, ) - .map_err(|_| Error::::CannotCheckOutTeleport)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out"); + Error::::CannotCheckOutTeleport + })?; // safe to do this here, we're in a transactional call that will be reverted on any // errors down the line ::AssetTransactor::check_out( @@ -2163,7 +2183,10 @@ impl Pallet { let mut reanchored_assets = assets.clone(); reanchored_assets .reanchor(&dest, &context) - .map_err(|_| Error::::CannotReanchor)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset"); + Error::::CannotReanchor + })?; // XcmContext irrelevant in teleports checks let dummy_context = @@ -2178,7 +2201,10 @@ impl Pallet { asset, &dummy_context, ) - .map_err(|_| Error::::CannotCheckOutTeleport)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset"); + Error::::CannotCheckOutTeleport + })?; } for asset in assets.inner() { // safe to do this here, we're in a transactional call that will be reverted on any @@ -2448,10 +2474,17 @@ impl Pallet { } else { None }; - log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); + tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone()); let (ticket, price) = validate_send::(dest, message)?; if let Some(fee_payer) = maybe_fee_payer { - Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?; + Self::charge_fees(fee_payer, price).map_err(|e| { + tracing::error!( + target: "xcm::pallet_xcm::send_xcm", + ?e, + "Charging fees failed with error", + ); + SendError::Fees + })?; } T::XcmRouter::deliver(ticket) } @@ -2512,18 +2545,16 @@ impl Pallet { XcmConfig: xcm_executor::Config, { let origin_location: Location = origin_location.try_into().map_err(|error| { - log::error!( + tracing::error!( target: "xcm::DryRunApi::dry_run_xcm", - "Location version conversion failed with error: {:?}", - error, + ?error, "Location version conversion failed with error" ); XcmDryRunApiError::VersionedConversionFailed })?; let xcm: Xcm = xcm.try_into().map_err(|error| { - log::error!( + tracing::error!( target: "xcm::DryRunApi::dry_run_xcm", - "Xcm version conversion failed with error {:?}", - error, + ?error, "Xcm version conversion failed with error" ); XcmDryRunApiError::VersionedConversionFailed })?; @@ -2560,11 +2591,14 @@ impl Pallet { } pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result { - let message = Xcm::<()>::try_from(message) - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + let message = Xcm::<()>::try_from(message.clone()) + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message"); + XcmPaymentApiError::VersionedConversionFailed + })?; - T::Weigher::weight(&mut message.into()).map_err(|()| { - log::error!(target: "xcm::pallet_xcm::query_xcm_weight", "Error when querying XCM weight"); + T::Weigher::weight(&mut message.clone().into()).map_err(|()| { + tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?message, "Error when querying XCM weight"); XcmPaymentApiError::WeightNotComputable }) } @@ -2575,21 +2609,31 @@ impl Pallet { ) -> Result { let result_version = destination.identify_version().max(message.identify_version()); - let destination = destination + let destination: Location = destination + .clone() .try_into() - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination"); + XcmPaymentApiError::VersionedConversionFailed + })?; - let message = - message.try_into().map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?; + let message: Xcm<()> = + message.clone().try_into().map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message"); + XcmPaymentApiError::VersionedConversionFailed + })?; - let (_, fees) = validate_send::(destination, message).map_err(|error| { - log::error!(target: "xcm::pallet_xcm::query_delivery_fees", "Error when querying delivery fees: {:?}", error); + let (_, fees) = validate_send::(destination.clone(), message.clone()).map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination"); XcmPaymentApiError::Unroutable })?; VersionedAssets::from(fees) .into_version(result_version) - .map_err(|_| XcmPaymentApiError::VersionedConversionFailed) + .map_err(|e| { + tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version"); + XcmPaymentApiError::VersionedConversionFailed + }) } /// Create a new expectation of a query response with the querier being here. @@ -2673,10 +2717,9 @@ impl Pallet { /// Note that a particular destination to whom we would like to send a message is unknown /// and queue it for version discovery. fn note_unknown_version(dest: &Location) { - log::trace!( + tracing::trace!( target: "xcm::pallet_xcm::note_unknown_version", - "XCM version is unknown for destination: {:?}", - dest, + ?dest, "XCM version is unknown for destination" ); let versioned_dest = VersionedLocation::from(dest.clone()); VersionDiscoveryQueue::::mutate(|q| { @@ -2944,10 +2987,9 @@ impl WrapVersion for Pallet { SafeXcmVersion::::get() }) .ok_or_else(|| { - log::trace!( + tracing::trace!( target: "xcm::pallet_xcm::wrap_version", - "Could not determine a version to wrap XCM for destination: {:?}", - dest, + ?dest, "Could not determine a version to wrap XCM for destination", ); () }) diff --git a/polkadot/xcm/pallet-xcm/src/migration.rs b/polkadot/xcm/pallet-xcm/src/migration.rs index 0aec97ab410..2c5b2620f53 100644 --- a/polkadot/xcm/pallet-xcm/src/migration.rs +++ b/polkadot/xcm/pallet-xcm/src/migration.rs @@ -40,7 +40,7 @@ pub mod v1 { let mut weight = T::DbWeight::get().reads(1); if StorageVersion::get::>() != 0 { - log::warn!("skipping v1, should be removed"); + tracing::warn!("skipping v1, should be removed"); return weight } @@ -50,13 +50,13 @@ pub mod v1 { let translate = |pre: (u64, u64, u32)| -> Option<(u64, Weight, u32)> { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); let translated = (pre.0, Weight::from_parts(pre.1, DEFAULT_PROOF_SIZE), pre.2); - log::info!("Migrated VersionNotifyTarget {:?} to {:?}", pre, translated); + tracing::info!("Migrated VersionNotifyTarget {:?} to {:?}", pre, translated); Some(translated) }; VersionNotifyTargets::::translate_values(translate); - log::info!("v1 applied successfully"); + tracing::info!("v1 applied successfully"); weight.saturating_accrue(T::DbWeight::get().writes(1)); StorageVersion::new(1).put::>(); weight diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 14920f36445..71985360b7d 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -495,7 +495,7 @@ impl XcmExecutor { self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees); // We withdraw or take from holding the asset the user wants to use for fee payment. - let withdrawn_fee_asset = if self.fees_mode.jit_withdraw { + let withdrawn_fee_asset: AssetsInHolding = if self.fees_mode.jit_withdraw { let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; Config::AssetTransactor::withdraw_asset( &asset_to_pay_for_fees, @@ -508,7 +508,10 @@ impl XcmExecutor { let assets_taken_from_holding_to_pay_delivery_fees = self .holding .try_take(asset_to_pay_for_fees.clone().into()) - .map_err(|_| XcmError::NotHoldingFees)?; + .map_err(|e| { + tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees, "Failed to take asset_to_pay_for_fees from holding"); + XcmError::NotHoldingFees + })?; tracing::trace!(target: "xcm::fees", ?assets_taken_from_holding_to_pay_delivery_fees); let mut iter = assets_taken_from_holding_to_pay_delivery_fees.fungible_assets_iter(); let asset = iter.next().ok_or(XcmError::NotHoldingFees)?; @@ -518,15 +521,14 @@ impl XcmExecutor { let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id { let swapped_asset: Assets = Config::AssetExchanger::exchange_asset( self.origin_ref(), - withdrawn_fee_asset, + withdrawn_fee_asset.clone().into(), &asset_needed_for_fees.clone().into(), false, ) .map_err(|given_assets| { tracing::error!( target: "xcm::fees", - ?given_assets, - "Swap was deemed necessary but couldn't be done", + ?given_assets, "Swap was deemed necessary but couldn't be done for withdrawn_fee_asset: {:?} and asset_needed_for_fees: {:?}", withdrawn_fee_asset.clone(), asset_needed_for_fees, ); XcmError::FeesNotMet })? @@ -587,8 +589,10 @@ impl XcmExecutor { Ok(match local_querier { None => None, Some(q) => Some( - q.reanchored(&destination, &Config::UniversalLocation::get()) - .map_err(|_| XcmError::ReanchorFailed)?, + q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|e| { + tracing::error!(target: "xcm::xcm_executor::to_querier", ?e, ?destination, "Failed to re-anchor local_querier"); + XcmError::ReanchorFailed + })?, ), }) } @@ -617,7 +621,7 @@ impl XcmExecutor { let reanchor_context = Config::UniversalLocation::get(); let reanchored = reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| { - tracing::error!(target: "xcm::reanchor", ?error, "Failed reanchoring with error"); + tracing::error!(target: "xcm::reanchor", ?error, ?destination, ?reanchor_context, "Failed reanchoring with error."); XcmError::ReanchorFailed })?; Ok((reanchored, reanchor_context)) @@ -923,7 +927,10 @@ impl XcmExecutor { .as_mut() .ok_or(XcmError::BadOrigin)? .append_with(who) - .map_err(|_| XcmError::LocationFull), + .map_err(|e| { + tracing::error!(target: "xcm::process_instruction::descend_origin", ?e, "Failed to append junctions"); + XcmError::LocationFull + }), ClearOrigin => { self.context.origin = None; Ok(()) @@ -1087,7 +1094,10 @@ impl XcmExecutor { tracing::trace!(target: "xcm::executor::BuyExecution", asset_used_for_fees = ?self.asset_used_for_fees); // pay for `weight` using up to `fees` of the holding register. let max_fee = - self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + self.holding.try_take(fees.clone().into()).map_err(|e| { + tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees, "Failed to take fees from holding"); + XcmError::NotHoldingFees + })?; let result = || -> Result<(), XcmError> { let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?; self.holding.subsume_assets(unspent); @@ -1150,7 +1160,10 @@ impl XcmExecutor { Ok(()) }, ExpectAsset(assets) => - self.holding.ensure_contains(&assets).map_err(|_| XcmError::ExpectationFalse), + self.holding.ensure_contains(&assets).map_err(|e| { + tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding"); + XcmError::ExpectationFalse + }), ExpectOrigin(origin) => { ensure!(self.context.origin == origin, XcmError::ExpectationFalse); Ok(()) @@ -1272,9 +1285,10 @@ impl XcmExecutor { let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?; let lock_ticket = Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?; - let owner = origin - .reanchored(&unlocker, &context) - .map_err(|_| XcmError::ReanchorFailed)?; + let owner = origin.reanchored(&unlocker, &context).map_err(|e| { + tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin"); + XcmError::ReanchorFailed + })?; let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]); let (ticket, price) = validate_send::(unlocker, msg)?; self.take_fee(price, FeeReason::LockAsset)?; diff --git a/prdoc/pr_4982.prdoc b/prdoc/pr_4982.prdoc new file mode 100644 index 00000000000..9e6d103a0ad --- /dev/null +++ b/prdoc/pr_4982.prdoc @@ -0,0 +1,13 @@ +title: Add useful error logs in pallet-xcm + +doc: + - audience: Runtime Dev + description: | + This PR adds error logs to assist in debugging pallet-xcm. + Additionally, it replaces the usage of `log` with `tracing`. + +crates: + - name: staging-xcm-executor + bump: patch + - name: pallet-xcm + bump: patch -- GitLab From d2ba56778d3c0e1c3a238b241e24b9eae15f1b1d Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 15 Oct 2024 13:54:32 +0200 Subject: [PATCH 100/127] Add assets in pool with native to query_acceptable_payment_assets's return (#5599) `XcmPaymentApi::query_acceptable_payment_assets` is an API that returns the assets that can be used to pay fees on the runtime where it's called. For relays and most system chains this was configured only as the native asset: ROC and WND. However, the asset hubs have the asset conversion pallet, which allows fees to be paid in any asset in a pool with the native one. This PR adds the list of assets in a pool with the native one to the return value of this API for the asset hubs. --------- Co-authored-by: Branislav Kontur --- .../assets/asset-hub-rococo/src/lib.rs | 18 +++++++++++++++++- .../assets/asset-hub-westend/src/lib.rs | 18 +++++++++++++++++- prdoc/pr_5599.prdoc | 19 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_5599.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 367cf8a51f2..595e3d0711a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1334,7 +1334,23 @@ impl_runtime_apis! { impl xcm_runtime_apis::fees::XcmPaymentApi for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result, XcmPaymentApiError> { - let acceptable_assets = vec![AssetId(xcm_config::TokenLocation::get())]; + let native_token = xcm_config::TokenLocation::get(); + // We accept the native token to pay fees. + let mut acceptable_assets = vec![AssetId(native_token.clone())]; + // We also accept all assets in a pool with the native token. + acceptable_assets.extend( + pallet_asset_conversion::Pools::::iter_keys().filter_map( + |(asset_1, asset_2)| { + if asset_1 == native_token { + Some(asset_2.clone().into()) + } else if asset_2 == native_token { + Some(asset_1.clone().into()) + } else { + None + } + }, + ), + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } 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 bb77f576e20..bd3ba10d5df 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1367,7 +1367,23 @@ impl_runtime_apis! { impl xcm_runtime_apis::fees::XcmPaymentApi for Runtime { fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result, XcmPaymentApiError> { - let acceptable_assets = vec![AssetId(xcm_config::WestendLocation::get())]; + let native_token = xcm_config::WestendLocation::get(); + // We accept the native token to pay fees. + let mut acceptable_assets = vec![AssetId(native_token.clone())]; + // We also accept all assets in a pool with the native token. + acceptable_assets.extend( + pallet_asset_conversion::Pools::::iter_keys().filter_map( + |(asset_1, asset_2)| { + if asset_1 == native_token { + Some(asset_2.clone().into()) + } else if asset_2 == native_token { + Some(asset_1.clone().into()) + } else { + None + } + }, + ), + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } diff --git a/prdoc/pr_5599.prdoc b/prdoc/pr_5599.prdoc new file mode 100644 index 00000000000..990d2bb4e18 --- /dev/null +++ b/prdoc/pr_5599.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: Add assets in pool with native to query_acceptable_payment_assets + +doc: + - audience: Runtime Dev + description: | + The `XcmPaymentApi::query_acceptable_payment_assets` API can be used to get a list of all + the assets that can be used for fee payment. + This is usually just the native asset, but the asset hubs have the asset conversion pallet. + In the case of the asset hubs, this list now includes all assets in a liquidity pool with + the native one. + +crates: + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor -- GitLab From 183b55aae21e97ef39192e5a358287e2b6b7043c Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:07:21 -0300 Subject: [PATCH 101/127] Bump zombienet version `v1.3.115` (#6065) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Includes: - Fixes for `ci` - Support to pass a json as arg for `js-script` Co-authored-by: Bastian Köcher --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 2a7e2e7559a..85a14d90da8 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.114" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.115" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" -- GitLab From 26c11fc542310ef6841d8382418a319502de78a3 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 15 Oct 2024 16:04:50 +0200 Subject: [PATCH 102/127] fork-aware transaction pool added (#4639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Fork-Aware Transaction Pool Implementation This PR introduces a fork-aware transaction pool (fatxpool) enhancing transaction management by maintaining the valid state of txpool for different forks. ### High-level overview The high level overview was added to [`sc_transaction_pool::fork_aware_txpool`](https://github.com/paritytech/polkadot-sdk/blob/3ad0a1b7c08e63a2581595cb2cd55f11ccd60f4f/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs#L21) module. Use: ``` cargo doc --document-private-items -p sc-transaction-pool --open ``` to build the doc. It should give a good overview and nice entry point into the new pool's mechanics.

Quick overview (documentation excerpt) #### View For every fork, a view is created. The view is a persisted state of the transaction pool computed and updated at the tip of the fork. The view is built around the existing `ValidatedPool` structure. A view is created on every new best block notification. To create a view, one of the existing views is chosen and cloned. When the chain progresses, the view is kept in the cache (`retracted_views`) to allow building blocks upon intermediary blocks in the fork. The views are deleted on finalization: views lower than the finalized block are removed. The views are updated with the transactions from the mempool—all transactions are sent to the newly created views. A maintain process is also executed for the newly created views—basically resubmitting and pruning transactions from the appropriate tree route. ##### View store View store is the helper structure that acts as a container for all the views. It provides some convenient methods. ##### Submitting transactions Every transaction is submitted to every view at the tips of the forks. Retracted views are not updated. Every transaction also goes into the mempool. ##### Internal mempool Shortly, the main purpose of an internal mempool is to prevent a transaction from being lost. That could happen when a transaction is invalid on one fork and could be valid on another. It also allows the txpool to accept transactions when no blocks have been reported yet. The mempool removes its transactions when they get finalized. Transactions are also periodically verified on every finalized event and removed from the mempool if no longer valid. #### Events Transaction events from multiple views are merged and filtered to avoid duplicated events. `Ready` / `Future` / `Inblock` events are originated in the Views and are de-duplicated and forwarded to external listeners. `Finalized` events are originated in fork-aware-txpool logic. `Invalid` events requires special care and can be originated in both view and fork-aware-txpool logic. #### Light maintain Sometime transaction pool does not have enough time to prepare fully maintained view with all retracted transactions being revalidated. To avoid providing empty ready transaction set to block builder (what would result in empty block) the light maintain was implemented. It simply removes the imported transactions from ready iterator. #### Revalidation Revalidation is performed for every view. The revalidation process is started after a trigger is executed. The revalidation work is terminated just after a new best block / finalized event is notified to the transaction pool. The revalidation result is applied to the newly created view which is built upon the revalidated view. Additionally, parts of the mempool are also revalidated to make sure that no transactions are stuck in the mempool. #### Logs The most important log allowing to understand the state of the txpool is: ``` maintain: txs:(0, 92) views:[2;[(327, 76, 0), (326, 68, 0)]] event:Finalized { hash: 0x8...f, tree_route: [] } took:3.463522ms ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ unwatched txs in mempool ────┘ │ │ │ │ │ │ │ │ │ │ watched txs in mempool ───────┘ │ │ │ │ │ │ │ │ │ views ───────────────┘ │ │ │ │ │ │ │ │ 1st view block # ──────────┘ │ │ │ │ │ │ │ number of ready tx ───────┘ │ │ │ │ │ │ numer of future tx ─────┘ │ │ │ │ │ 2nd view block # ──────┘ │ │ │ │ number of ready tx ──────────┘ │ │ │ number of future tx ───────┘ │ │ event ────────┘ │ duration ──────────────────────────────────────────────────┘ ``` It is logged after the maintenance is done. The `debug` level enables per-transaction logging, allowing to keep track of all transaction-related actions that happened in txpool.
### Integration notes For teams having a custom node, the new txpool needs to be instantiated, typically in `service.rs` file, here is an example: https://github.com/paritytech/polkadot-sdk/blob/9c547ff3e36cf3b52c99f4ed7849a8e9f722d97d/cumulus/polkadot-omni-node/lib/src/common/spec.rs#L152-L161 To enable new transaction pool the following cli arg shall be specified: `--pool-type=fork-aware`. If it works, there shall be information printed in the log: ``` 2024-09-20 21:28:17.528 INFO main txpool: [Parachain] creating ForkAware txpool. ```` For debugging the following debugs shall be enabled: ``` "-lbasic-authorship=debug", "-ltxpool=debug", ``` *note:* trace for txpool enables per-transaction logging. ### Future work The current implementation seems to be stable, however further improvements are required. Here is the umbrella issue for future work: - https://github.com/paritytech/polkadot-sdk/issues/5472 Partially fixes: #1202 --------- Co-authored-by: Bastian Köcher Co-authored-by: Sebastian Kunert Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- Cargo.lock | 6 + cumulus/client/service/src/lib.rs | 2 +- .../polkadot-omni-node/lib/src/common/rpc.rs | 6 +- .../polkadot-omni-node/lib/src/common/spec.rs | 21 +- .../lib/src/common/types.rs | 4 +- .../polkadot-omni-node/lib/src/nodes/aura.rs | 6 +- cumulus/test/service/src/lib.rs | 19 +- cumulus/zombienet/tests/0008-main.js | 18 + ...08-parachain_extrinsic_gets_finalized.toml | 25 + ...8-parachain_extrinsic_gets_finalized.zndsl | 20 + .../tests/0008-transaction_gets_finalized.js | 69 + polkadot/node/service/src/lib.rs | 17 +- prdoc/pr_4639.prdoc | 69 + substrate/bin/node/bench/src/construct.rs | 25 +- .../bin/node/cli/benches/transaction_pool.rs | 19 +- substrate/bin/node/cli/src/service.rs | 26 +- .../basic-authorship/src/basic_authorship.rs | 94 +- substrate/client/basic-authorship/src/lib.rs | 4 +- substrate/client/cli/Cargo.toml | 1 + .../cli/src/params/transaction_pool_params.rs | 55 +- .../client/network/transactions/src/lib.rs | 2 + substrate/client/offchain/src/lib.rs | 9 +- .../src/transaction/tests/middleware_pool.rs | 19 +- substrate/client/rpc/src/author/tests.rs | 9 +- substrate/client/service/src/config.rs | 2 +- substrate/client/service/src/lib.rs | 30 +- substrate/client/transaction-pool/Cargo.toml | 4 + .../client/transaction-pool/api/src/error.rs | 2 +- .../client/transaction-pool/api/src/lib.rs | 36 +- .../client/transaction-pool/benches/basics.rs | 28 +- .../client/transaction-pool/src/builder.rs | 245 ++ .../transaction-pool/src/{ => common}/api.rs | 59 +- .../src/{ => common}/enactment_state.rs | 54 +- .../src/{ => common}/error.rs | 0 .../transaction-pool/src/common/log_xt.rs | 54 + .../src/{ => common}/metrics.rs | 80 +- .../client/transaction-pool/src/common/mod.rs | 48 + .../src/{ => common}/tests.rs | 11 +- .../src/fork_aware_txpool/dropped_watcher.rs | 533 ++++ .../fork_aware_txpool/fork_aware_txpool.rs | 1563 ++++++++++ .../import_notification_sink.rs | 396 +++ .../src/fork_aware_txpool/metrics.rs | 176 ++ .../src/fork_aware_txpool/mod.rs | 376 +++ .../fork_aware_txpool/multi_view_listener.rs | 736 +++++ .../fork_aware_txpool/revalidation_worker.rs | 240 ++ .../src/fork_aware_txpool/tx_mem_pool.rs | 535 ++++ .../src/fork_aware_txpool/view.rs | 415 +++ .../src/fork_aware_txpool/view_store.rs | 468 +++ .../transaction-pool/src/graph/base_pool.rs | 269 +- .../transaction-pool/src/graph/future.rs | 34 +- .../transaction-pool/src/graph/listener.rs | 74 +- .../client/transaction-pool/src/graph/mod.rs | 8 +- .../client/transaction-pool/src/graph/pool.rs | 602 ++-- .../transaction-pool/src/graph/ready.rs | 14 +- .../transaction-pool/src/graph/tracked_map.rs | 14 + .../src/graph/validated_pool.rs | 106 +- .../transaction-pool/src/graph/watcher.rs | 2 +- substrate/client/transaction-pool/src/lib.rs | 794 +---- .../src/single_state_txpool/metrics.rs | 67 + .../src/single_state_txpool/mod.rs | 26 + .../{ => single_state_txpool}/revalidation.rs | 56 +- .../single_state_txpool.rs | 790 +++++ .../src/transaction_pool_wrapper.rs | 198 ++ .../client/transaction-pool/tests/fatp.rs | 2617 +++++++++++++++++ .../transaction-pool/tests/fatp_common/mod.rs | 285 ++ .../transaction-pool/tests/fatp_limits.rs | 353 +++ .../client/transaction-pool/tests/pool.rs | 101 +- .../runtime/src/transaction_validity.rs | 2 +- .../runtime/transaction-pool/Cargo.toml | 1 + .../runtime/transaction-pool/src/lib.rs | 141 +- substrate/utils/frame/rpc/system/src/lib.rs | 36 +- substrate/utils/prometheus/src/lib.rs | 4 +- templates/minimal/node/src/service.rs | 17 +- templates/parachain/node/src/service.rs | 19 +- templates/solochain/node/src/service.rs | 17 +- 75 files changed, 11719 insertions(+), 1564 deletions(-) create mode 100644 cumulus/zombienet/tests/0008-main.js create mode 100644 cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.toml create mode 100644 cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.zndsl create mode 100644 cumulus/zombienet/tests/0008-transaction_gets_finalized.js create mode 100644 prdoc/pr_4639.prdoc create mode 100644 substrate/client/transaction-pool/src/builder.rs rename substrate/client/transaction-pool/src/{ => common}/api.rs (87%) rename substrate/client/transaction-pool/src/{ => common}/enactment_state.rs (94%) rename substrate/client/transaction-pool/src/{ => common}/error.rs (100%) create mode 100644 substrate/client/transaction-pool/src/common/log_xt.rs rename substrate/client/transaction-pool/src/{ => common}/metrics.rs (58%) create mode 100644 substrate/client/transaction-pool/src/common/mod.rs rename substrate/client/transaction-pool/src/{ => common}/tests.rs (94%) create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/view.rs create mode 100644 substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs create mode 100644 substrate/client/transaction-pool/src/single_state_txpool/metrics.rs create mode 100644 substrate/client/transaction-pool/src/single_state_txpool/mod.rs rename substrate/client/transaction-pool/src/{ => single_state_txpool}/revalidation.rs (91%) create mode 100644 substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs create mode 100644 substrate/client/transaction-pool/src/transaction_pool_wrapper.rs create mode 100644 substrate/client/transaction-pool/tests/fatp.rs create mode 100644 substrate/client/transaction-pool/tests/fatp_common/mod.rs create mode 100644 substrate/client/transaction-pool/tests/fatp_limits.rs diff --git a/Cargo.lock b/Cargo.lock index 360e89d8ad8..40b36e2602f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18394,6 +18394,7 @@ dependencies = [ "sc-service", "sc-telemetry", "sc-tracing", + "sc-transaction-pool", "sc-utils", "serde", "serde_json", @@ -19738,6 +19739,8 @@ dependencies = [ "criterion", "futures", "futures-timer", + "indexmap 2.2.3", + "itertools 0.11.0", "linked-hash-map", "log", "parity-scale-codec", @@ -19760,6 +19763,8 @@ dependencies = [ "substrate-test-runtime-client", "substrate-test-runtime-transaction-pool", "thiserror", + "tokio", + "tokio-stream", ] [[package]] @@ -23862,6 +23867,7 @@ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ "futures", + "log", "parity-scale-codec", "parking_lot 0.12.3", "sc-transaction-pool", diff --git a/cumulus/client/service/src/lib.rs b/cumulus/client/service/src/lib.rs index 92dc64371f3..25b8ee10a93 100644 --- a/cumulus/client/service/src/lib.rs +++ b/cumulus/client/service/src/lib.rs @@ -417,7 +417,7 @@ pub struct BuildNetworkParams< pub net_config: sc_network::config::FullNetworkConfiguration::Hash, Network>, pub client: Arc, - pub transaction_pool: Arc>, + pub transaction_pool: Arc>, pub para_id: ParaId, pub relay_chain_interface: RCInterface, pub spawn_handle: SpawnTaskHandle, diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index 85665c9b220..4879bd1eb7f 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -46,7 +46,7 @@ impl BuildRpcExtensions< ParachainClient, ParachainBackend, - sc_transaction_pool::FullPool>, + sc_transaction_pool::TransactionPoolHandle>, > for BuildParachainRpcExtensions where RuntimeApi: @@ -57,7 +57,9 @@ where fn build_rpc_extensions( client: Arc>, backend: Arc>, - pool: Arc>>, + pool: Arc< + sc_transaction_pool::TransactionPoolHandle>, + >, ) -> sc_service::error::Result { let build = || -> Result> { let mut module = RpcExtension::new(()); diff --git a/cumulus/polkadot-omni-node/lib/src/common/spec.rs b/cumulus/polkadot-omni-node/lib/src/common/spec.rs index dca28b3c28f..8397cb778dc 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/spec.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/spec.rs @@ -40,7 +40,7 @@ use sc_service::{Configuration, ImportQueue, PartialComponents, TaskManager}; use sc_sysinfo::HwBench; use sc_telemetry::{TelemetryHandle, TelemetryWorker}; use sc_tracing::tracing::Instrument; -use sc_transaction_pool::FullPool; +use sc_transaction_pool::TransactionPoolHandle; use sp_keystore::KeystorePtr; use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; @@ -65,7 +65,7 @@ where telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc>>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, para_id: ParaId, @@ -149,12 +149,15 @@ pub(crate) trait BaseNodeSpec { telemetry }); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); @@ -184,7 +187,7 @@ pub(crate) trait NodeSpec: BaseNodeSpec { type BuildRpcExtensions: BuildRpcExtensions< ParachainClient, ParachainBackend, - FullPool>, + TransactionPoolHandle>, >; type StartConsensus: StartConsensus; diff --git a/cumulus/polkadot-omni-node/lib/src/common/types.rs b/cumulus/polkadot-omni-node/lib/src/common/types.rs index 9cfdcb22451..4bc58dc9db7 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/types.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/types.rs @@ -20,7 +20,7 @@ use sc_consensus::DefaultImportQueue; use sc_executor::WasmExecutor; use sc_service::{PartialComponents, TFullBackend, TFullClient}; use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; -use sc_transaction_pool::FullPool; +use sc_transaction_pool::TransactionPoolHandle; use sp_runtime::{generic, traits::BlakeTwo256}; use std::sync::Arc; @@ -51,6 +51,6 @@ pub type ParachainService = PartialComponents< ParachainBackend, (), DefaultImportQueue, - FullPool>, + TransactionPoolHandle>, (ParachainBlockImport, Option, Option), >; diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index cf1ee91cbab..ec5d0a439ec 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -52,7 +52,7 @@ use sc_consensus::{ }; use sc_service::{Configuration, Error, TaskManager}; use sc_telemetry::TelemetryHandle; -use sc_transaction_pool::FullPool; +use sc_transaction_pool::TransactionPoolHandle; use sp_api::ProvideRuntimeApi; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; @@ -291,7 +291,7 @@ where telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc>>, keystore: KeystorePtr, _relay_chain_slot_duration: Duration, para_id: ParaId, @@ -387,7 +387,7 @@ where telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>>, + transaction_pool: Arc>>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, para_id: ParaId, diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 9f93572e9ce..a13399d3a40 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -134,7 +134,7 @@ pub type Backend = TFullBackend; pub type ParachainBlockImport = TParachainBlockImport, Backend>; /// Transaction pool type used by the test service -pub type TransactionPool = Arc>; +pub type TransactionPool = Arc>; /// Recovery handle that fails regularly to simulate unavailable povs. pub struct FailingRecoveryHandle { @@ -183,7 +183,7 @@ pub type Service = PartialComponents< Backend, (), sc_consensus::import_queue::BasicQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, ParachainBlockImport, >; @@ -219,12 +219,15 @@ pub fn new_partial( let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let slot_duration = sc_consensus_aura::slot_duration(&*client)?; diff --git a/cumulus/zombienet/tests/0008-main.js b/cumulus/zombienet/tests/0008-main.js new file mode 100644 index 00000000000..31c01324a77 --- /dev/null +++ b/cumulus/zombienet/tests/0008-main.js @@ -0,0 +1,18 @@ +// Allows to manually submit extrinsic to collator. +// Usage: +// zombienet-linux -p native spwan 0008-parachain-extrinsic-gets-finalized.toml +// node 0008-main.js + +global.zombie = null + +const fs = require('fs'); +const test = require('./0008-transaction_gets_finalized.js'); + +if (process.argv.length == 2) { + console.error('Path to zombie.json (generated by zombienet-linux spawn command shall be given)!'); + process.exit(1); +} + +let networkInfo = JSON.parse(fs.readFileSync(process.argv[2])); + +test.run("charlie", networkInfo).then(process.exit) diff --git a/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.toml b/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.toml new file mode 100644 index 00000000000..a295d3960bf --- /dev/null +++ b/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.toml @@ -0,0 +1,25 @@ +[relaychain] +default_image = "{{RELAY_IMAGE}}" +default_command = "polkadot" +chain = "rococo-local" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + +[[parachains]] +id = 2000 +cumulus_based = true +chain = "asset-hub-rococo-local" + + # run charlie as parachain collator + [[parachains.collators]] + name = "charlie" + validator = true + image = "{{POLKADOT_PARACHAIN_IMAGE}}" + command = "polkadot-parachain" + args = ["--force-authoring", "-ltxpool=trace", "--pool-type=fork-aware"] diff --git a/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.zndsl b/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.zndsl new file mode 100644 index 00000000000..5aab1bd923a --- /dev/null +++ b/cumulus/zombienet/tests/0008-parachain_extrinsic_gets_finalized.zndsl @@ -0,0 +1,20 @@ +Description: Block building +Network: ./0008-parachain_extrinsic_gets_finalized.toml +Creds: config + +alice: reports node_roles is 4 +bob: reports node_roles is 4 +charlie: reports node_roles is 4 + +alice: reports peers count is at least 1 +bob: reports peers count is at least 1 + +alice: reports block height is at least 5 within 60 seconds +bob: reports block height is at least 5 within 60 seconds +charlie: reports block height is at least 2 within 120 seconds + +alice: count of log lines containing "error" is 0 within 2 seconds +bob: count of log lines containing "error" is 0 within 2 seconds +charlie: count of log lines containing "error" is 0 within 2 seconds + +charlie: js-script ./0008-transaction_gets_finalized.js within 600 seconds diff --git a/cumulus/zombienet/tests/0008-transaction_gets_finalized.js b/cumulus/zombienet/tests/0008-transaction_gets_finalized.js new file mode 100644 index 00000000000..3031c45e3a4 --- /dev/null +++ b/cumulus/zombienet/tests/0008-transaction_gets_finalized.js @@ -0,0 +1,69 @@ +//based on: https://polkadot.js.org/docs/api/examples/promise/transfer-events + +const assert = require("assert"); + +async function run(nodeName, networkInfo, args) { + const {wsUri, userDefinedTypes} = networkInfo.nodesByName[nodeName]; + // Create the API and wait until ready + var api = null; + var keyring = null; + if (zombie == null) { + const testKeyring = require('@polkadot/keyring/testing'); + const { WsProvider, ApiPromise } = require('@polkadot/api'); + const provider = new WsProvider(wsUri); + api = await ApiPromise.create({provider}); + // Construct the keyring after the API (crypto has an async init) + keyring = testKeyring.createTestKeyring({ type: "sr25519" }); + } else { + keyring = new zombie.Keyring({ type: "sr25519" }); + api = await zombie.connect(wsUri, userDefinedTypes); + } + + + // Add Alice to our keyring with a hard-derivation path (empty phrase, so uses dev) + const alice = keyring.addFromUri('//Alice'); + + // Create an extrinsic: + const extrinsic = api.tx.system.remark("xxx"); + + let extrinsic_success_event = false; + try { + await new Promise( async (resolve, reject) => { + const unsubscribe = await extrinsic + .signAndSend(alice, { nonce: -1 }, ({ events = [], status }) => { + console.log('Extrinsic status:', status.type); + + if (status.isInBlock) { + console.log('Included at block hash', status.asInBlock.toHex()); + console.log('Events:'); + + events.forEach(({ event: { data, method, section }, phase }) => { + console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString()); + + if (section=="system" && method =="ExtrinsicSuccess") { + extrinsic_success_event = true; + } + }); + } else if (status.isFinalized) { + console.log('Finalized block hash', status.asFinalized.toHex()); + unsubscribe(); + if (extrinsic_success_event) { + resolve(); + } else { + reject("ExtrinsicSuccess has not been seen"); + } + } else if (status.isError) { + unsubscribe(); + reject("Extrinsic status.isError"); + } + + }); + }); + } catch (error) { + assert.fail("Transfer promise failed, error: " + error); + } + + assert.ok("test passed"); +} + +module.exports = { run } diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 9515dd23113..da3ab760ed2 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -450,7 +450,7 @@ fn new_partial( FullBackend, ChainSelection, sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, ( impl Fn( polkadot_rpc::SubscriptionTaskExecutor, @@ -478,12 +478,15 @@ fn new_partial( where ChainSelection: 'static + SelectChain, { - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let grandpa_hard_forks = if config.chain_spec.is_kusama() { diff --git a/prdoc/pr_4639.prdoc b/prdoc/pr_4639.prdoc new file mode 100644 index 00000000000..dfdd60f2bdb --- /dev/null +++ b/prdoc/pr_4639.prdoc @@ -0,0 +1,69 @@ +title: "Added the fork-aware transaction pool implementation" + +doc: + - audience: Node Dev + description: | + Most important changes introduced by this PR: + - The transaction pool references spread across codebase are now wrapper to a transaction pool trait object, + - The fork-aware pool implementation was added. + - The `sc-transaction-pool` refactored, + - Trasnaction pool builder was introduced to allow to instantiation of either old or new transaction pool. Refer to PR description for + more details on how to enable fork-aware pool in the custom node. + - audience: Node Operator + description: | + - New command line option was added, allowing to select implementation of transaction pool: + - `--pool-type=fork-aware` - new fork aware transaction pool, + - `--pool-type=single-state` - old transaction pool implementation which is still default, + +crates: + - name: sc-basic-authorship + bump: patch + - name: sc-cli + bump: major + - name: sc-consensus-manual-seal + bump: patch + - name: sc-network-transactions + bump: none + - name: sc-rpc + bump: patch + - name: sc-rpc-spec-v2 + bump: patch + - name: sc-offchain + bump: patch + - name: sc-service + bump: patch + - name: sc-service-test + bump: minor + - name: sc-transaction-pool + bump: major + - name: sc-transaction-pool-api + bump: major + validate: false + - name: sp-runtime + bump: patch + - name: substrate-test-runtime-transaction-pool + bump: minor + - name: staging-node-cli + bump: minor + - name: node-bench + bump: patch + - name: node-rpc + bump: minor + - name: substrate-prometheus-endpoint + bump: patch + - name: substrate-frame-rpc-system + bump: patch + - name: minimal-template-node + bump: minor + - name: parachain-template-node + bump: minor + - name: solochain-template-node + bump: minor + - name: polkadot-service + bump: patch + - name: cumulus-client-service + bump: patch + - name: cumulus-test-service + bump: major + - name: polkadot-omni-node-lib + bump: patch diff --git a/substrate/bin/node/bench/src/construct.rs b/substrate/bin/node/bench/src/construct.rs index 23d0a0cc1ee..bed6e3d914c 100644 --- a/substrate/bin/node/bench/src/construct.rs +++ b/substrate/bin/node/bench/src/construct.rs @@ -35,7 +35,7 @@ use sc_transaction_pool_api::{ }; use sp_consensus::{Environment, Proposer}; use sp_inherents::InherentDataProvider; -use sp_runtime::{traits::NumberFor, OpaqueExtrinsic}; +use sp_runtime::OpaqueExtrinsic; use crate::{ common::SizeType, @@ -165,18 +165,18 @@ impl core::Benchmark for ConstructionBenchmark { #[derive(Clone, Debug)] pub struct PoolTransaction { - data: OpaqueExtrinsic, + data: Arc, hash: node_primitives::Hash, } impl From for PoolTransaction { fn from(e: OpaqueExtrinsic) -> Self { - PoolTransaction { data: e, hash: node_primitives::Hash::zero() } + PoolTransaction { data: Arc::from(e), hash: node_primitives::Hash::zero() } } } impl sc_transaction_pool_api::InPoolTransaction for PoolTransaction { - type Transaction = OpaqueExtrinsic; + type Transaction = Arc; type Hash = node_primitives::Hash; fn data(&self) -> &Self::Transaction { @@ -261,7 +261,7 @@ impl sc_transaction_pool_api::TransactionPool for Transactions { fn ready_at( &self, - _at: NumberFor, + _at: Self::Hash, ) -> Pin< Box< dyn Future< @@ -305,4 +305,19 @@ impl sc_transaction_pool_api::TransactionPool for Transactions { fn ready_transaction(&self, _hash: &TxHash) -> Option> { unimplemented!() } + + fn ready_at_with_timeout( + &self, + _at: Self::Hash, + _timeout: std::time::Duration, + ) -> Pin< + Box< + dyn Future< + Output = Box> + Send>, + > + Send + + '_, + >, + > { + unimplemented!() + } } diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index efec081427f..c07cb3ec0d1 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -16,15 +16,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use polkadot_sdk::*; -use std::time::Duration; - use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; use futures::{future, StreamExt}; use kitchensink_runtime::{constants::currency::*, BalancesCall, SudoCall}; use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool}; use node_primitives::AccountId; -use polkadot_sdk::sc_service::config::{ExecutorConfiguration, RpcConfiguration}; +use polkadot_sdk::{ + sc_service::config::{ExecutorConfiguration, RpcConfiguration}, + sc_transaction_pool_api::TransactionPool as _, + *, +}; use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, @@ -32,8 +33,7 @@ use sc_service::{ }, BasePath, Configuration, Role, }; -use sc_transaction_pool::PoolLimit; -use sc_transaction_pool_api::{TransactionPool as _, TransactionSource, TransactionStatus}; +use sc_transaction_pool_api::{TransactionSource, TransactionStatus}; use sp_core::{crypto::Pair, sr25519}; use sp_keyring::Sr25519Keyring; use sp_runtime::OpaqueExtrinsic; @@ -58,12 +58,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { impl_version: "1.0".into(), role: Role::Authority, tokio_handle: tokio_handle.clone(), - transaction_pool: TransactionPoolOptions { - ready: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, - future: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, - reject_future_transactions: false, - ban_time: Duration::from_secs(30 * 60), - }, + transaction_pool: TransactionPoolOptions::new_for_benchmarks(), network: network_config, keystore: KeystoreConfig::InMemory, database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 69e953f54e4..4eb1db185e9 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -42,6 +42,7 @@ use sc_network_sync::{strategy::warp::WarpSyncConfig, SyncingService}; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; use sc_statement_store::Store as StatementStore; use sc_telemetry::{Telemetry, TelemetryWorker}; +use sc_transaction_pool::TransactionPoolHandle; use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::ProvideRuntimeApi; use sp_core::crypto::Pair; @@ -80,7 +81,7 @@ type FullBeefyBlockImport = beefy::import::BeefyBlockImport< >; /// The transaction pool type definition. -pub type TransactionPool = sc_transaction_pool::FullPool; +pub type TransactionPool = sc_transaction_pool::TransactionPoolHandle; /// The minimum period of blocks on which justifications will be /// imported and generated. @@ -175,7 +176,7 @@ pub fn new_partial( FullBackend, FullSelectChain, sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, ( impl Fn( sc_rpc::SubscriptionTaskExecutor, @@ -226,12 +227,15 @@ pub fn new_partial( let select_chain = sc_consensus::LongestChain::new(backend.clone()); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let (grandpa_block_import, grandpa_link) = grandpa::block_import( @@ -385,7 +389,7 @@ pub struct NewFullBase { /// The syncing service of the node. pub sync: Arc>, /// The transaction pool of the node. - pub transaction_pool: Arc, + pub transaction_pool: Arc>, /// The rpc handlers of the node. pub rpc_handlers: RpcHandlers, } @@ -865,14 +869,14 @@ mod tests { Address, BalancesCall, RuntimeCall, UncheckedExtrinsic, }; use node_primitives::{Block, DigestItem, Signature}; - use polkadot_sdk::*; + use polkadot_sdk::{sc_transaction_pool_api::MaintainedTransactionPool, *}; use sc_client_api::BlockBackend; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY}; use sc_consensus_epochs::descendent_query; use sc_keystore::LocalKeystore; use sc_service_test::TestNetNode; - use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool}; + use sc_transaction_pool_api::ChainEvent; use sp_consensus::{BlockOrigin, Environment, Proposer}; use sp_core::crypto::Pair; use sp_inherents::InherentDataProvider; diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 527a3d12d9e..79e6fddae99 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -25,7 +25,6 @@ use futures::{ channel::oneshot, future, future::{Future, FutureExt}, - select, }; use log::{debug, error, info, trace, warn}; use sc_block_builder::{BlockBuilderApi, BlockBuilderBuilder}; @@ -416,26 +415,13 @@ where let mut skipped = 0; let mut unqueue_invalid = Vec::new(); - let mut t1 = self.transaction_pool.ready_at(self.parent_number).fuse(); - let mut t2 = - futures_timer::Delay::new(deadline.saturating_duration_since((self.now)()) / 8).fuse(); - - let mut pending_iterator = select! { - res = t1 => res, - _ = t2 => { - warn!(target: LOG_TARGET, - "Timeout fired waiting for transaction pool at block #{}. \ - Proceeding with production.", - self.parent_number, - ); - self.transaction_pool.ready() - }, - }; + let delay = deadline.saturating_duration_since((self.now)()) / 8; + let mut pending_iterator = + self.transaction_pool.ready_at_with_timeout(self.parent_hash, delay).await; let block_size_limit = block_size_limit.unwrap_or(self.default_block_size_limit); - debug!(target: LOG_TARGET, "Attempting to push transactions from the pool."); - debug!(target: LOG_TARGET, "Pool status: {:?}", self.transaction_pool.status()); + debug!(target: LOG_TARGET, "Attempting to push transactions from the pool at {:?}.", self.parent_hash); let mut transaction_pushed = false; let end_reason = loop { @@ -460,7 +446,7 @@ where break EndProposingReason::HitDeadline } - let pending_tx_data = pending_tx.data().clone(); + let pending_tx_data = (**pending_tx.data()).clone(); let pending_tx_hash = pending_tx.hash().clone(); let block_size = @@ -524,7 +510,7 @@ where pending_iterator.report_invalid(&pending_tx); debug!( target: LOG_TARGET, - "[{:?}] Invalid transaction: {}", pending_tx_hash, e + "[{:?}] Invalid transaction: {} at: {}", pending_tx_hash, e, self.parent_hash ); unqueue_invalid.push(pending_tx_hash); }, @@ -577,13 +563,25 @@ where ) }; - info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", - block.header().number(), - block_took.as_millis(), - ::Hash::from(block.header().hash()), - block.header().parent_hash(), - ); + if log::log_enabled!(log::Level::Info) { + info!( + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics_count: {}", + block.header().number(), + block_took.as_millis(), + ::Hash::from(block.header().hash()), + block.header().parent_hash(), + extrinsics.len() + ) + } else if log::log_enabled!(log::Level::Debug) { + debug!( + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", + block.header().number(), + block_took.as_millis(), + ::Hash::from(block.header().hash()), + block.header().parent_hash(), + ); + } + telemetry!( self.telemetry; CONSENSUS_INFO; @@ -643,22 +641,20 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let hashof0 = client.info().genesis_hash; block_on(txpool.submit_at(hashof0, SOURCE, vec![extrinsic(0), extrinsic(1)])).unwrap(); block_on( txpool.maintain(chain_event( - client - .expect_header(client.info().genesis_hash) - .expect("there should be header"), + client.expect_header(hashof0).expect("there should be header"), )), ); @@ -698,13 +694,13 @@ mod tests { fn should_not_panic_when_deadline_is_reached() { let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let mut proposer_factory = ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); @@ -735,13 +731,13 @@ mod tests { let (client, backend) = TestClientBuilder::new().build_with_backend(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let genesis_hash = client.info().best_hash; @@ -791,13 +787,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let medium = |nonce| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(MEDIUM)) @@ -871,27 +867,27 @@ mod tests { // let's create one block and import it let block = propose_block(&client, 0, 2, 7); - import_and_maintain(client.clone(), block); + import_and_maintain(client.clone(), block.clone()); assert_eq!(txpool.ready().count(), 5); // now let's make sure that we can still make some progress let block = propose_block(&client, 1, 1, 5); - import_and_maintain(client.clone(), block); + import_and_maintain(client.clone(), block.clone()); assert_eq!(txpool.ready().count(), 4); // again let's make sure that we can still make some progress let block = propose_block(&client, 2, 1, 4); - import_and_maintain(client.clone(), block); + import_and_maintain(client.clone(), block.clone()); assert_eq!(txpool.ready().count(), 3); // again let's make sure that we can still make some progress let block = propose_block(&client, 3, 1, 3); - import_and_maintain(client.clone(), block); + import_and_maintain(client.clone(), block.clone()); assert_eq!(txpool.ready().count(), 2); // again let's make sure that we can still make some progress let block = propose_block(&client, 4, 2, 2); - import_and_maintain(client.clone(), block); + import_and_maintain(client.clone(), block.clone()); assert_eq!(txpool.ready().count(), 0); } @@ -899,13 +895,13 @@ mod tests { fn should_cease_building_block_when_block_limit_is_reached() { let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let genesis_hash = client.info().genesis_hash; let genesis_header = client.expect_header(genesis_hash).expect("there should be header"); @@ -1004,13 +1000,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let genesis_hash = client.info().genesis_hash; let tiny = |nonce| { @@ -1073,13 +1069,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let txpool = BasicPool::new_full( + let txpool = Arc::from(BasicPool::new_full( Default::default(), true.into(), None, spawner.clone(), client.clone(), - ); + )); let genesis_hash = client.info().genesis_hash; let tiny = |who| { diff --git a/substrate/client/basic-authorship/src/lib.rs b/substrate/client/basic-authorship/src/lib.rs index 8f47c2ea00e..adea7a3571d 100644 --- a/substrate/client/basic-authorship/src/lib.rs +++ b/substrate/client/basic-authorship/src/lib.rs @@ -32,13 +32,13 @@ //! # use sc_transaction_pool::{BasicPool, FullChainApi}; //! # let client = Arc::new(substrate_test_runtime_client::new()); //! # let spawner = sp_core::testing::TaskExecutor::new(); -//! # let txpool = BasicPool::new_full( +//! # let txpool = Arc::from(BasicPool::new_full( //! # Default::default(), //! # true.into(), //! # None, //! # spawner.clone(), //! # client.clone(), -//! # ); +//! # )); //! // The first step is to create a `ProposerFactory`. //! let mut proposer_factory = ProposerFactory::new( //! spawner, diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index b7d29aebc3d..f0b9f8f9b90 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -43,6 +43,7 @@ sc-network = { workspace = true, default-features = true } sc-service = { workspace = true } sc-telemetry = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/client/cli/src/params/transaction_pool_params.rs b/substrate/client/cli/src/params/transaction_pool_params.rs index 48b2e5b1572..9cf738f58b6 100644 --- a/substrate/client/cli/src/params/transaction_pool_params.rs +++ b/substrate/client/cli/src/params/transaction_pool_params.rs @@ -16,8 +16,28 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use clap::Args; -use sc_service::config::TransactionPoolOptions; +use clap::{Args, ValueEnum}; +use sc_transaction_pool::TransactionPoolOptions; + +/// Type of transaction pool to be used +#[derive(Debug, Clone, Copy, ValueEnum)] +#[value(rename_all = "kebab-case")] +pub enum TransactionPoolType { + /// Uses a legacy, single-state transaction pool. + SingleState, + /// Uses a fork-aware transaction pool. + ForkAware, +} + +impl Into for TransactionPoolType { + fn into(self) -> sc_transaction_pool::TransactionPoolType { + match self { + TransactionPoolType::SingleState => + sc_transaction_pool::TransactionPoolType::SingleState, + TransactionPoolType::ForkAware => sc_transaction_pool::TransactionPoolType::ForkAware, + } + } +} /// Parameters used to create the pool configuration. #[derive(Debug, Clone, Args)] @@ -35,30 +55,21 @@ pub struct TransactionPoolParams { /// If it is considered invalid. Defaults to 1800s. #[arg(long, value_name = "SECONDS")] pub tx_ban_seconds: Option, + + /// The type of transaction pool to be instantiated. + #[arg(long, value_enum, default_value_t = TransactionPoolType::SingleState)] + pub pool_type: TransactionPoolType, } impl TransactionPoolParams { /// Fill the given `PoolConfiguration` by looking at the cli parameters. pub fn transaction_pool(&self, is_dev: bool) -> TransactionPoolOptions { - let mut opts = TransactionPoolOptions::default(); - - // ready queue - opts.ready.count = self.pool_limit; - opts.ready.total_bytes = self.pool_kbytes * 1024; - - // future queue - let factor = 10; - opts.future.count = self.pool_limit / factor; - opts.future.total_bytes = self.pool_kbytes * 1024 / factor; - - opts.ban_time = if let Some(ban_seconds) = self.tx_ban_seconds { - std::time::Duration::from_secs(ban_seconds) - } else if is_dev { - std::time::Duration::from_secs(0) - } else { - std::time::Duration::from_secs(30 * 60) - }; - - opts + TransactionPoolOptions::new_with_params( + self.pool_limit, + self.pool_kbytes * 1024, + self.tx_ban_seconds, + self.pool_type.into(), + is_dev, + ) } } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index a241041968f..2b5297fe0e1 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -462,6 +462,8 @@ where if let Some(transaction) = self.transaction_pool.transaction(hash) { let propagated_to = self.do_propagate_transactions(&[(hash.clone(), transaction)]); self.transaction_pool.on_broadcasted(propagated_to); + } else { + debug!(target: "sync", "Propagating transaction failure [{:?}]", hash); } } diff --git a/substrate/client/offchain/src/lib.rs b/substrate/client/offchain/src/lib.rs index 7cee64e6ce7..3d5728aad17 100644 --- a/substrate/client/offchain/src/lib.rs +++ b/substrate/client/offchain/src/lib.rs @@ -446,8 +446,13 @@ mod tests { let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let network = Arc::new(TestNetwork()); let header = client.header(client.chain_info().genesis_hash).unwrap().unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs index aa8ac572dec..adcc987f9c3 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/middleware_pool.rs @@ -27,7 +27,7 @@ use sc_transaction_pool_api::{ use crate::hex_string; use futures::{FutureExt, StreamExt}; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, pin::Pin, sync::Arc}; use substrate_test_runtime_transaction_pool::TestApi; use tokio::sync::mpsc; @@ -166,7 +166,7 @@ impl TransactionPool for MiddlewarePool { fn ready_at( &self, - at: NumberFor, + at: ::Hash, ) -> Pin< Box< dyn Future< @@ -184,4 +184,19 @@ impl TransactionPool for MiddlewarePool { fn futures(&self) -> Vec { self.inner_pool.futures() } + + fn ready_at_with_timeout( + &self, + at: ::Hash, + _timeout: std::time::Duration, + ) -> Pin< + Box< + dyn Future< + Output = Box> + Send>, + > + Send + + '_, + >, + > { + self.inner_pool.ready_at(at) + } } diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index bde60960eaf..ab0b8bdab69 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -66,8 +66,13 @@ impl Default for TestSetup { let client = Arc::new(substrate_test_runtime_client::TestClientBuilder::new().build()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); TestSetup { client, keystore, pool } } } diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 6f65c2e2d81..fb9e9264dfe 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -37,7 +37,7 @@ pub use sc_rpc_server::{ IpNetwork, RpcEndpoint, RpcMethods, SubscriptionIdProvider as RpcSubscriptionIdProvider, }; pub use sc_telemetry::TelemetryEndpoints; -pub use sc_transaction_pool::Options as TransactionPoolOptions; +pub use sc_transaction_pool::TransactionPoolOptions; use sp_core::crypto::SecretString; use std::{ io, iter, diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index b6acdb8ed00..54e847791cf 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -94,7 +94,7 @@ pub use sc_network_sync::WarpSyncConfig; pub use sc_network_transactions::config::{TransactionImport, TransactionImportFuture}; pub use sc_rpc::{RandomIntegerSubscriptionId, RandomStringSubscriptionId}; pub use sc_tracing::TracingReceiver; -pub use sc_transaction_pool::Options as TransactionPoolOptions; +pub use sc_transaction_pool::TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; #[doc(hidden)] pub use std::{ops::Deref, result::Result, sync::Arc}; @@ -484,7 +484,7 @@ where .filter(|t| t.is_propagable()) .map(|t| { let hash = t.hash().clone(); - let ex: B::Extrinsic = t.data().clone(); + let ex: B::Extrinsic = (**t.data()).clone(); (hash, ex) }) .collect() @@ -523,6 +523,7 @@ where }, }; + let start = std::time::Instant::now(); let import_future = self.pool.submit_one( self.client.info().best_hash, sc_transaction_pool_api::TransactionSource::External, @@ -530,16 +531,16 @@ where ); Box::pin(async move { match import_future.await { - Ok(_) => TransactionImport::NewGood, + Ok(_) => { + let elapsed = start.elapsed(); + debug!(target: sc_transaction_pool::LOG_TARGET, "import transaction: {elapsed:?}"); + TransactionImport::NewGood + }, Err(e) => match e.into_pool_error() { Ok(sc_transaction_pool_api::error::Error::AlreadyImported(_)) => TransactionImport::KnownGood, - Ok(e) => { - debug!("Error adding transaction to the pool: {:?}", e); - TransactionImport::Bad - }, - Err(e) => { - debug!("Error converting pool error: {}", e); + Ok(_) => TransactionImport::Bad, + Err(_) => { // it is not bad at least, just some internal node logic error, so peer is // innocent. TransactionImport::KnownGood @@ -556,7 +557,7 @@ where fn transaction(&self, hash: &H) -> Option { self.pool.ready_transaction(hash).and_then( // Only propagable transactions should be resolved for network service. - |tx| if tx.is_propagable() { Some(tx.data().clone()) } else { None }, + |tx| if tx.is_propagable() { Some((**tx.data()).clone()) } else { None }, ) } } @@ -578,8 +579,13 @@ mod tests { let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain(); let client = Arc::new(client); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let source = sp_runtime::transaction_validity::TransactionSource::External; let best = block_on(longest_chain.best_chain()).unwrap(); let transaction = Transfer { diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index 98994cc742f..d346add93a6 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -20,6 +20,8 @@ async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +indexmap = { workspace = true } +itertools = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } @@ -36,6 +38,8 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } +tokio-stream = { workspace = true } +tokio = { workspace = true, default-features = true, features = ["macros", "time"] } [dev-dependencies] array-bytes = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/api/src/error.rs b/substrate/client/transaction-pool/api/src/error.rs index d0744bfa3e1..e81955ebe54 100644 --- a/substrate/client/transaction-pool/api/src/error.rs +++ b/substrate/client/transaction-pool/api/src/error.rs @@ -38,7 +38,7 @@ pub enum Error { /// The transaction validity returned no "provides" tag. /// /// Such transactions are not accepted to the pool, since we use those tags - /// to define identity of transactions (occupance of the same "slot"). + /// to define identity of transactions (occupancy of the same "slot"). #[error("Transaction does not provide any tags, so the pool can't identify it")] NoTagsProvided, diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index 0a313c5b782..3ac1a79a0c2 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -26,7 +26,7 @@ use codec::Codec; use futures::{Future, Stream}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_core::offchain::TransactionPoolExt; -use sp_runtime::traits::{Block as BlockT, Member, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Member}; use std::{collections::HashMap, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc}; const LOG_TARGET: &str = "txpool::api"; @@ -36,7 +36,7 @@ pub use sp_runtime::transaction_validity::{ }; /// Transaction pool status. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PoolStatus { /// Number of transactions in the ready queue. pub ready: usize, @@ -49,7 +49,7 @@ pub struct PoolStatus { } impl PoolStatus { - /// Returns true if the are no transactions in the pool. + /// Returns true if there are no transactions in the pool. pub fn is_empty(&self) -> bool { self.ready == 0 && self.future == 0 } @@ -57,7 +57,7 @@ impl PoolStatus { /// Possible transaction status events. /// -/// This events are being emitted by `TransactionPool` watchers, +/// These events are being emitted by `TransactionPool` watchers, /// which are also exposed over RPC. /// /// The status events can be grouped based on their kinds as: @@ -144,7 +144,7 @@ pub enum TransactionStatus { /// Maximum number of finality watchers has been reached, /// old watchers are being removed. FinalityTimeout(BlockHash), - /// Transaction has been finalized by a finality-gadget, e.g GRANDPA. + /// Transaction has been finalized by a finality-gadget, e.g. GRANDPA. #[serde(with = "v1_compatible")] Finalized((BlockHash, TxIndex)), /// Transaction has been replaced in the pool, by another transaction @@ -245,7 +245,7 @@ pub trait TransactionPool: Send + Sync { type Hash: Hash + Eq + Member + Serialize + DeserializeOwned + Codec; /// In-pool transaction type. type InPoolTransaction: InPoolTransaction< - Transaction = TransactionFor, + Transaction = Arc>, Hash = TxHash, >; /// Error type. @@ -269,7 +269,7 @@ pub trait TransactionPool: Send + Sync { xt: TransactionFor, ) -> PoolFuture, Self::Error>; - /// Returns a future that import a single transaction and starts to watch their progress in the + /// Returns a future that imports a single transaction and starts to watch their progress in the /// pool. fn submit_and_watch( &self, @@ -285,7 +285,7 @@ pub trait TransactionPool: Send + Sync { /// Guarantees to return immediately when `None` is passed. fn ready_at( &self, - at: NumberFor, + at: ::Hash, ) -> Pin< Box< dyn Future< @@ -321,6 +321,23 @@ pub trait TransactionPool: Send + Sync { /// Return specific ready transaction by hash, if there is one. fn ready_transaction(&self, hash: &TxHash) -> Option>; + + /// Returns set of ready transaction at given block within given timeout. + /// + /// If the timeout is hit during method execution then the best effort set of ready transactions + /// for given block, without executing full maintain process is returned. + fn ready_at_with_timeout( + &self, + at: ::Hash, + timeout: std::time::Duration, + ) -> Pin< + Box< + dyn Future< + Output = Box> + Send>, + > + Send + + '_, + >, + >; } /// An iterator of ready transactions. @@ -345,6 +362,7 @@ impl ReadyTransactions for std::iter::Empty { } /// Events that the transaction pool listens for. +#[derive(Debug)] pub enum ChainEvent { /// New best block have been added to the chain. NewBestBlock { @@ -441,7 +459,7 @@ impl OffchainSubmitTransaction for TP at: ::Hash, extrinsic: ::Extrinsic, ) -> Result<(), ()> { - log::debug!( + log::trace!( target: LOG_TARGET, "(offchain call) Submitting a transaction to the pool: {:?}", extrinsic diff --git a/substrate/client/transaction-pool/benches/basics.rs b/substrate/client/transaction-pool/benches/basics.rs index 65c83f09053..2db34bc3f32 100644 --- a/substrate/client/transaction-pool/benches/basics.rs +++ b/substrate/client/transaction-pool/benches/basics.rs @@ -24,6 +24,7 @@ use futures::{ future::{ready, Ready}, }; use sc_transaction_pool::*; +use sp_blockchain::HashAndNumber; use sp_crypto_hashing::blake2_256; use sp_runtime::{ generic::BlockId, @@ -64,8 +65,9 @@ impl ChainApi for TestApi { &self, at: ::Hash, _source: TransactionSource, - uxt: ::Extrinsic, + uxt: Arc<::Extrinsic>, ) -> Self::ValidationFuture { + let uxt = (*uxt).clone(); let transfer = TransferData::try_from(&uxt) .expect("uxt is expected to be bench_call (carrying TransferData)"); let nonce = transfer.nonce; @@ -144,6 +146,10 @@ fn bench_configured(pool: Pool, number: u64, api: Arc) { let source = TransactionSource::External; let mut futures = Vec::new(); let mut tags = Vec::new(); + let at = HashAndNumber { + hash: api.block_id_to_hash(&BlockId::Number(1)).unwrap().unwrap(), + number: 1, + }; for nonce in 1..=number { let xt = uxt(TransferData { @@ -151,15 +157,12 @@ fn bench_configured(pool: Pool, number: u64, api: Arc) { to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce, - }); + }) + .into(); tags.push(to_tag(nonce, AccountId::from_h256(H256::from_low_u64_be(1)))); - futures.push(pool.submit_one( - api.block_id_to_hash(&BlockId::Number(1)).unwrap().unwrap(), - source, - xt, - )); + futures.push(pool.submit_one(&at, source, xt)); } let res = block_on(futures::future::join_all(futures.into_iter())); @@ -170,12 +173,11 @@ fn bench_configured(pool: Pool, number: u64, api: Arc) { // Prune all transactions. let block_num = 6; - block_on(pool.prune_tags( - api.block_id_to_hash(&BlockId::Number(block_num)).unwrap().unwrap(), - tags, - vec![], - )) - .expect("Prune failed"); + let at = HashAndNumber { + hash: api.block_id_to_hash(&BlockId::Number(block_num)).unwrap().unwrap(), + number: block_num, + }; + block_on(pool.prune_tags(&at, tags, vec![])); // pool is empty assert_eq!(pool.validated_pool().status().ready, 0); diff --git a/substrate/client/transaction-pool/src/builder.rs b/substrate/client/transaction-pool/src/builder.rs new file mode 100644 index 00000000000..e1fddcdd895 --- /dev/null +++ b/substrate/client/transaction-pool/src/builder.rs @@ -0,0 +1,245 @@ +// 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 . + +//! Utility for building substrate transaction pool trait object. + +use crate::{ + common::api::FullChainApi, + fork_aware_txpool::ForkAwareTxPool as ForkAwareFullPool, + graph::{base_pool::Transaction, ChainApi, ExtrinsicFor, ExtrinsicHash, IsValidator, Options}, + single_state_txpool::BasicPool as SingleStateFullPool, + TransactionPoolWrapper, LOG_TARGET, +}; +use prometheus_endpoint::Registry as PrometheusRegistry; +use sc_transaction_pool_api::{LocalTransactionPool, MaintainedTransactionPool}; +use sp_core::traits::SpawnEssentialNamed; +use sp_runtime::traits::Block as BlockT; +use std::{marker::PhantomData, sync::Arc, time::Duration}; + +/// The type of transaction pool. +#[derive(Debug, Clone)] +pub enum TransactionPoolType { + /// Single-state transaction pool + SingleState, + /// Fork-aware transaction pool + ForkAware, +} + +/// Transaction pool options. +#[derive(Debug, Clone)] +pub struct TransactionPoolOptions { + txpool_type: TransactionPoolType, + options: Options, +} + +impl Default for TransactionPoolOptions { + fn default() -> Self { + Self { txpool_type: TransactionPoolType::SingleState, options: Default::default() } + } +} + +impl TransactionPoolOptions { + /// Creates the options for the transaction pool using given parameters. + pub fn new_with_params( + pool_limit: usize, + pool_bytes: usize, + tx_ban_seconds: Option, + txpool_type: TransactionPoolType, + is_dev: bool, + ) -> TransactionPoolOptions { + let mut options = Options::default(); + + // ready queue + options.ready.count = pool_limit; + options.ready.total_bytes = pool_bytes; + + // future queue + let factor = 10; + options.future.count = pool_limit / factor; + options.future.total_bytes = pool_bytes / factor; + + options.ban_time = if let Some(ban_seconds) = tx_ban_seconds { + Duration::from_secs(ban_seconds) + } else if is_dev { + Duration::from_secs(0) + } else { + Duration::from_secs(30 * 60) + }; + + TransactionPoolOptions { options, txpool_type } + } + + /// Creates predefined options for benchmarking + pub fn new_for_benchmarks() -> TransactionPoolOptions { + TransactionPoolOptions { + options: Options { + ready: crate::graph::base_pool::Limit { + count: 100_000, + total_bytes: 100 * 1024 * 1024, + }, + future: crate::graph::base_pool::Limit { + count: 100_000, + total_bytes: 100 * 1024 * 1024, + }, + reject_future_transactions: false, + ban_time: Duration::from_secs(30 * 60), + }, + txpool_type: TransactionPoolType::SingleState, + } + } +} + +/// `FullClientTransactionPool` is a trait that combines the functionality of +/// `MaintainedTransactionPool` and `LocalTransactionPool` for a given `Client` and `Block`. +/// +/// This trait defines the requirements for a full client transaction pool, ensuring +/// that it can handle transactions submission and maintenance. +pub trait FullClientTransactionPool: + MaintainedTransactionPool< + Block = Block, + Hash = ExtrinsicHash>, + InPoolTransaction = Transaction< + ExtrinsicHash>, + ExtrinsicFor>, + >, + Error = as ChainApi>::Error, + > + LocalTransactionPool< + Block = Block, + Hash = ExtrinsicHash>, + Error = as ChainApi>::Error, + > +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ +} + +impl FullClientTransactionPool for P +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, + P: MaintainedTransactionPool< + Block = Block, + Hash = ExtrinsicHash>, + InPoolTransaction = Transaction< + ExtrinsicHash>, + ExtrinsicFor>, + >, + Error = as ChainApi>::Error, + > + LocalTransactionPool< + Block = Block, + Hash = ExtrinsicHash>, + Error = as ChainApi>::Error, + >, +{ +} + +/// The public type alias for the actual type providing the implementation of +/// `FullClientTransactionPool` with the given `Client` and `Block` types. +/// +/// This handle abstracts away the specific type of the transaction pool. Should be used +/// externally to keep reference to transaction pool. +pub type TransactionPoolHandle = TransactionPoolWrapper; + +/// Builder allowing to create specific instance of transaction pool. +pub struct Builder<'a, Block, Client> { + options: TransactionPoolOptions, + is_validator: IsValidator, + prometheus: Option<&'a PrometheusRegistry>, + client: Arc, + spawner: Box, + _phantom: PhantomData<(Client, Block)>, +} + +impl<'a, Client, Block> Builder<'a, Block, Client> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sc_client_api::ExecutorProvider + + sc_client_api::UsageProvider + + sp_blockchain::HeaderMetadata + + Send + + Sync + + 'static, + ::Hash: std::marker::Unpin, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + /// Creates new instance of `Builder` + pub fn new( + spawner: impl SpawnEssentialNamed + 'static, + client: Arc, + is_validator: IsValidator, + ) -> Builder<'a, Block, Client> { + Builder { + options: Default::default(), + _phantom: Default::default(), + spawner: Box::new(spawner), + client, + is_validator, + prometheus: None, + } + } + + /// Sets the options used for creating a transaction pool instance. + pub fn with_options(mut self, options: TransactionPoolOptions) -> Self { + self.options = options; + self + } + + /// Sets the prometheus endpoint used in a transaction pool instance. + pub fn with_prometheus(mut self, prometheus: Option<&'a PrometheusRegistry>) -> Self { + self.prometheus = prometheus; + self + } + + /// Creates an instance of transaction pool. + pub fn build(self) -> TransactionPoolHandle { + log::info!(target:LOG_TARGET, " creating {:?} txpool {:?}/{:?}.", self.options.txpool_type, self.options.options.ready, self.options.options.future); + TransactionPoolWrapper::(match self.options.txpool_type { + TransactionPoolType::SingleState => Box::new(SingleStateFullPool::new_full( + self.options.options, + self.is_validator, + self.prometheus, + self.spawner, + self.client, + )), + TransactionPoolType::ForkAware => Box::new(ForkAwareFullPool::new_full( + self.options.options, + self.is_validator, + self.prometheus, + self.spawner, + self.client, + )), + }) + } +} diff --git a/substrate/client/transaction-pool/src/api.rs b/substrate/client/transaction-pool/src/common/api.rs similarity index 87% rename from substrate/client/transaction-pool/src/api.rs rename to substrate/client/transaction-pool/src/common/api.rs index cccaad7c899..a5185ba606e 100644 --- a/substrate/client/transaction-pool/src/api.rs +++ b/substrate/client/transaction-pool/src/common/api.rs @@ -40,18 +40,18 @@ use sp_runtime::{ }; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; -use crate::{ +use super::{ error::{self, Error}, - graph, metrics::{ApiMetrics, ApiMetricsExt}, }; +use crate::graph; /// The transaction pool logic for full client. pub struct FullChainApi { client: Arc, _marker: PhantomData, metrics: Option>, - validation_pool: Arc + Send>>>>>, + validation_pool: mpsc::Sender + Send>>>, } /// Spawn a validation task that will be used by the transaction pool to validate transactions. @@ -101,12 +101,7 @@ impl FullChainApi { spawn_validation_pool_task("transaction-pool-task-0", receiver.clone(), spawner); spawn_validation_pool_task("transaction-pool-task-1", receiver, spawner); - FullChainApi { - client, - validation_pool: Arc::new(Mutex::new(sender)), - _marker: Default::default(), - metrics, - } + FullChainApi { client, validation_pool: sender, _marker: Default::default(), metrics } } } @@ -139,25 +134,25 @@ where ) -> Self::ValidationFuture { let (tx, rx) = oneshot::channel(); let client = self.client.clone(); - let validation_pool = self.validation_pool.clone(); + let mut validation_pool = self.validation_pool.clone(); let metrics = self.metrics.clone(); async move { metrics.report(|m| m.validations_scheduled.inc()); - validation_pool - .lock() - .await - .send( - async move { - let res = validate_transaction_blocking(&*client, at, source, uxt); - let _ = tx.send(res); - metrics.report(|m| m.validations_finished.inc()); - } - .boxed(), - ) - .await - .map_err(|e| Error::RuntimeApi(format!("Validation pool down: {:?}", e)))?; + { + validation_pool + .send( + async move { + let res = validate_transaction_blocking(&*client, at, source, uxt); + let _ = tx.send(res); + metrics.report(|m| m.validations_finished.inc()); + } + .boxed(), + ) + .await + .map_err(|e| Error::RuntimeApi(format!("Validation pool down: {:?}", e)))?; + } match rx.await { Ok(r) => r, @@ -183,7 +178,7 @@ where fn hash_and_length( &self, - ex: &graph::ExtrinsicFor, + ex: &graph::RawExtrinsicFor, ) -> (graph::ExtrinsicHash, usize) { ex.using_encoded(|x| ( as traits::Hash>::hash(x), x.len())) } @@ -222,7 +217,10 @@ where Client: Send + Sync + 'static, Client::Api: TaggedTransactionQueue, { - sp_tracing::within_span!(sp_tracing::Level::TRACE, "validate_transaction"; + let s = std::time::Instant::now(); + let h = uxt.using_encoded(|x| as traits::Hash>::hash(x)); + + let result = sp_tracing::within_span!(sp_tracing::Level::TRACE, "validate_transaction"; { let runtime_api = client.runtime_api(); let api_version = sp_tracing::within_span! { sp_tracing::Level::TRACE, "check_version"; @@ -240,7 +238,7 @@ where sp_tracing::Level::TRACE, "runtime::validate_transaction"; { if api_version >= 3 { - runtime_api.validate_transaction(at, source, uxt, at) + runtime_api.validate_transaction(at, source, (*uxt).clone(), at) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { let block_number = client.to_number(&BlockId::Hash(at)) @@ -260,16 +258,19 @@ where if api_version == 2 { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_3(at, source, uxt) + runtime_api.validate_transaction_before_version_3(at, source, (*uxt).clone()) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_2(at, uxt) + runtime_api.validate_transaction_before_version_2(at, (*uxt).clone()) .map_err(|e| Error::RuntimeApi(e.to_string())) } } }) - }) + }); + log::trace!(target: LOG_TARGET, "[{h:?}] validate_transaction_blocking: at:{at:?} took:{:?}", s.elapsed()); + + result } impl FullChainApi diff --git a/substrate/client/transaction-pool/src/enactment_state.rs b/substrate/client/transaction-pool/src/common/enactment_state.rs similarity index 94% rename from substrate/client/transaction-pool/src/enactment_state.rs rename to substrate/client/transaction-pool/src/common/enactment_state.rs index 85c572c127e..a7eb6a3687c 100644 --- a/substrate/client/transaction-pool/src/enactment_state.rs +++ b/substrate/client/transaction-pool/src/common/enactment_state.rs @@ -34,7 +34,7 @@ const SKIP_MAINTENANCE_THRESHOLD: u16 = 20; /// is to figure out which phases (enactment / finalization) of transaction pool /// maintenance are needed. /// -/// Given the following chain: +/// Example: given the following chain: /// /// B1-C1-D1-E1 /// / @@ -42,8 +42,8 @@ const SKIP_MAINTENANCE_THRESHOLD: u16 = 20; /// \ /// B2-C2-D2-E2 /// -/// Some scenarios and expected behavior for sequence of `NewBestBlock` (`nbb`) and `Finalized` -/// (`f`) events: +/// the list presents scenarios and expected behavior for sequence of `NewBestBlock` (`nbb`) +/// and `Finalized` (`f`) events. true/false means if enactiment is required: /// /// - `nbb(C1)`, `f(C1)` -> false (enactment was already performed in `nbb(C1))` /// - `f(C1)`, `nbb(C1)` -> false (enactment was already performed in `f(C1))` @@ -103,7 +103,7 @@ where let new_hash = event.hash(); let finalized = event.is_finalized(); - // do not proceed with txpool maintain if block distance is to high + // do not proceed with txpool maintain if block distance is too high let skip_maintenance = match (hash_to_number(new_hash), hash_to_number(self.recent_best_block)) { (Ok(Some(new)), Ok(Some(current))) => @@ -112,14 +112,14 @@ where }; if skip_maintenance { - log::debug!(target: LOG_TARGET, "skip maintain: tree_route would be too long"); + log::trace!(target: LOG_TARGET, "skip maintain: tree_route would be too long"); self.force_update(event); return Ok(EnactmentAction::Skip) } // block was already finalized if self.recent_finalized_block == new_hash { - log::debug!(target: LOG_TARGET, "handle_enactment: block already finalized"); + log::trace!(target: LOG_TARGET, "handle_enactment: block already finalized"); return Ok(EnactmentAction::Skip) } @@ -127,7 +127,7 @@ where // it instead of tree_route provided with event let tree_route = tree_route(self.recent_best_block, new_hash)?; - log::debug!( + log::trace!( target: LOG_TARGET, "resolve hash: {new_hash:?} finalized: {finalized:?} \ tree_route: (common {:?}, last {:?}) best_block: {:?} finalized_block:{:?}", @@ -141,7 +141,7 @@ where // happening if we first received a finalization event and then a new // best event for some old stale best head. if tree_route.retracted().iter().any(|x| x.hash == self.recent_finalized_block) { - log::debug!( + log::trace!( target: LOG_TARGET, "Recently finalized block {} would be retracted by ChainEvent {}, skipping", self.recent_finalized_block, @@ -180,7 +180,7 @@ where ChainEvent::NewBestBlock { hash, .. } => self.recent_best_block = *hash, ChainEvent::Finalized { hash, .. } => self.recent_finalized_block = *hash, }; - log::debug!( + log::trace!( target: LOG_TARGET, "forced update: {:?}, {:?}", self.recent_best_block, @@ -296,7 +296,7 @@ mod enactment_state_tests { use super::*; /// asserts that tree routes are equal - fn assert_treeroute_eq( + fn assert_tree_route_eq( expected: Result, String>, result: Result, String>, ) { @@ -323,56 +323,56 @@ mod enactment_state_tests { fn tree_route_mock_test_01() { let result = tree_route(b1().hash, a().hash); let expected = TreeRoute::new(vec![b1(), a()], 1); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_02() { let result = tree_route(a().hash, b1().hash); let expected = TreeRoute::new(vec![a(), b1()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_03() { let result = tree_route(a().hash, c2().hash); let expected = TreeRoute::new(vec![a(), b2(), c2()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_04() { let result = tree_route(e2().hash, a().hash); let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a()], 4); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_05() { let result = tree_route(d1().hash, b1().hash); let expected = TreeRoute::new(vec![d1(), c1(), b1()], 2); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_06() { let result = tree_route(d2().hash, b2().hash); let expected = TreeRoute::new(vec![d2(), c2(), b2()], 2); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_07() { let result = tree_route(b1().hash, d1().hash); let expected = TreeRoute::new(vec![b1(), c1(), d1()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_08() { let result = tree_route(b2().hash, d2().hash); let expected = TreeRoute::new(vec![b2(), c2(), d2()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] @@ -380,7 +380,7 @@ mod enactment_state_tests { let result = tree_route(e2().hash, e1().hash); let expected = TreeRoute::new(vec![e2(), d2(), c2(), b2(), a(), b1(), c1(), d1(), e1()], 4); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] @@ -388,55 +388,55 @@ mod enactment_state_tests { let result = tree_route(e1().hash, e2().hash); let expected = TreeRoute::new(vec![e1(), d1(), c1(), b1(), a(), b2(), c2(), d2(), e2()], 4); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_11() { let result = tree_route(b1().hash, c2().hash); let expected = TreeRoute::new(vec![b1(), a(), b2(), c2()], 1); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_12() { let result = tree_route(d2().hash, b1().hash); let expected = TreeRoute::new(vec![d2(), c2(), b2(), a(), b1()], 3); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_13() { let result = tree_route(c2().hash, e1().hash); let expected = TreeRoute::new(vec![c2(), b2(), a(), b1(), c1(), d1(), e1()], 2); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_14() { let result = tree_route(b1().hash, b1().hash); let expected = TreeRoute::new(vec![b1()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_15() { let result = tree_route(b2().hash, b2().hash); let expected = TreeRoute::new(vec![b2()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_16() { let result = tree_route(a().hash, a().hash); let expected = TreeRoute::new(vec![a()], 0); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } #[test] fn tree_route_mock_test_17() { let result = tree_route(x2().hash, b1().hash); let expected = TreeRoute::new(vec![x2(), e2(), d2(), c2(), b2(), a(), b1()], 5); - assert_treeroute_eq(result, expected); + assert_tree_route_eq(result, expected); } } diff --git a/substrate/client/transaction-pool/src/error.rs b/substrate/client/transaction-pool/src/common/error.rs similarity index 100% rename from substrate/client/transaction-pool/src/error.rs rename to substrate/client/transaction-pool/src/common/error.rs diff --git a/substrate/client/transaction-pool/src/common/log_xt.rs b/substrate/client/transaction-pool/src/common/log_xt.rs new file mode 100644 index 00000000000..6c3752c1d50 --- /dev/null +++ b/substrate/client/transaction-pool/src/common/log_xt.rs @@ -0,0 +1,54 @@ +// 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 . + +//! Utility for logging transaction collections. + +/// Logs every transaction from given `tx_collection` with given level. +macro_rules! log_xt { + (data: hash, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr) => { + if log::log_enabled!(target: $target, $level) { + for tx in $tx_collection { + log::log!(target: $target, $level, $text_with_format, tx); + } + } + }; + (data: hash, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr, $($arg:expr),*) => { + if log::log_enabled!(target: $target, $level) { + for tx in $tx_collection { + log::log!(target: $target, $level, $text_with_format, tx, $($arg),*); + } + } + }; + (data: tuple, target: $target:expr, $level:expr, $tx_collection:expr, $text_with_format:expr) => { + if log::log_enabled!(target: $target, $level) { + for tx in $tx_collection { + log::log!(target: $target, $level, $text_with_format, tx.0, tx.1) + } + } + }; +} + +/// Logs every transaction from given `tx_collection` with trace level. +macro_rules! log_xt_trace { + (data: $datatype:ident, target: $target:expr, $($arg:tt)+) => ($crate::common::log_xt::log_xt!(data: $datatype, target: $target, log::Level::Trace, $($arg)+)); + (target: $target:expr, $tx_collection:expr, $text_with_format:expr) => ($crate::common::log_xt::log_xt!(data: hash, target: $target, log::Level::Trace, $tx_collection, $text_with_format)); + (target: $target:expr, $tx_collection:expr, $text_with_format:expr, $($arg:expr)*) => ($crate::common::log_xt::log_xt!(data: hash, target: $target, log::Level::Trace, $tx_collection, $text_with_format, $($arg)*)); +} + +pub(crate) use log_xt; +pub(crate) use log_xt_trace; diff --git a/substrate/client/transaction-pool/src/metrics.rs b/substrate/client/transaction-pool/src/common/metrics.rs similarity index 58% rename from substrate/client/transaction-pool/src/metrics.rs rename to substrate/client/transaction-pool/src/common/metrics.rs index 170bface964..0ec3b511fa0 100644 --- a/substrate/client/transaction-pool/src/metrics.rs +++ b/substrate/client/transaction-pool/src/common/metrics.rs @@ -16,76 +16,52 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Transaction pool Prometheus metrics. +//! Transaction pool Prometheus metrics for implementation of Chain API. +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use std::sync::Arc; -use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; +use crate::LOG_TARGET; -#[derive(Clone, Default)] -pub struct MetricsLink(Arc>); +/// Provides interface to register the specific metrics in the Prometheus register. +pub(crate) trait MetricsRegistrant { + /// Registers the metrics at given Prometheus registry. + fn register(registry: &Registry) -> Result, PrometheusError>; +} -impl MetricsLink { +/// Generic structure to keep a link to metrics register. +pub(crate) struct GenericMetricsLink(Arc>>); + +impl Default for GenericMetricsLink { + fn default() -> Self { + Self(Arc::from(None)) + } +} + +impl Clone for GenericMetricsLink { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl GenericMetricsLink { pub fn new(registry: Option<&Registry>) -> Self { Self(Arc::new(registry.and_then(|registry| { - Metrics::register(registry) + M::register(registry) .map_err(|err| { - log::warn!("Failed to register prometheus metrics: {}", err); + log::warn!(target: LOG_TARGET, "Failed to register prometheus metrics: {}", err); }) .ok() }))) } - pub fn report(&self, do_this: impl FnOnce(&Metrics)) { + pub fn report(&self, do_this: impl FnOnce(&M)) { if let Some(metrics) = self.0.as_ref() { - do_this(metrics); + do_this(&**metrics); } } } -/// Transaction pool Prometheus metrics. -pub struct Metrics { - pub submitted_transactions: Counter, - pub validations_invalid: Counter, - pub block_transactions_pruned: Counter, - pub block_transactions_resubmitted: Counter, -} - -impl Metrics { - pub fn register(registry: &Registry) -> Result { - Ok(Self { - submitted_transactions: register( - Counter::new( - "substrate_sub_txpool_submitted_transactions", - "Total number of transactions submitted", - )?, - registry, - )?, - validations_invalid: register( - Counter::new( - "substrate_sub_txpool_validations_invalid", - "Total number of transactions that were removed from the pool as invalid", - )?, - registry, - )?, - block_transactions_pruned: register( - Counter::new( - "substrate_sub_txpool_block_transactions_pruned", - "Total number of transactions that was requested to be pruned by block events", - )?, - registry, - )?, - block_transactions_resubmitted: register( - Counter::new( - "substrate_sub_txpool_block_transactions_resubmitted", - "Total number of transactions that was requested to be resubmitted by block events", - )?, - registry, - )?, - }) - } -} - /// Transaction pool api Prometheus metrics. pub struct ApiMetrics { pub validations_scheduled: Counter, diff --git a/substrate/client/transaction-pool/src/common/mod.rs b/substrate/client/transaction-pool/src/common/mod.rs new file mode 100644 index 00000000000..fb280e8780a --- /dev/null +++ b/substrate/client/transaction-pool/src/common/mod.rs @@ -0,0 +1,48 @@ +// 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 . + +//! Common components re-used across different txpool implementations. + +pub(crate) mod api; +pub(crate) mod enactment_state; +pub(crate) mod error; +pub(crate) mod log_xt; +pub(crate) mod metrics; +#[cfg(test)] +pub(crate) mod tests; + +use futures::StreamExt; +use std::sync::Arc; + +/// Inform the transaction pool about imported and finalized blocks. +pub async fn notification_future(client: Arc, txpool: Arc) +where + Block: sp_runtime::traits::Block, + Client: sc_client_api::BlockchainEvents, + Pool: sc_transaction_pool_api::MaintainedTransactionPool, +{ + let import_stream = client + .import_notification_stream() + .filter_map(|n| futures::future::ready(n.try_into().ok())) + .fuse(); + let finality_stream = client.finality_notification_stream().map(Into::into).fuse(); + + futures::stream::select(import_stream, finality_stream) + .for_each(|evt| txpool.maintain(evt)) + .await +} diff --git a/substrate/client/transaction-pool/src/tests.rs b/substrate/client/transaction-pool/src/common/tests.rs similarity index 94% rename from substrate/client/transaction-pool/src/tests.rs rename to substrate/client/transaction-pool/src/common/tests.rs index 325add3fb1c..1cbabf8b5fd 100644 --- a/substrate/client/transaction-pool/src/tests.rs +++ b/substrate/client/transaction-pool/src/common/tests.rs @@ -18,11 +18,11 @@ //! Testing related primitives for internal usage in this crate. -use crate::graph::{BlockHash, ChainApi, ExtrinsicFor, NumberFor, Pool}; +use crate::graph::{BlockHash, ChainApi, ExtrinsicFor, NumberFor, Pool, RawExtrinsicFor}; use codec::Encode; use parking_lot::Mutex; use sc_transaction_pool_api::error; -use sp_blockchain::TreeRoute; +use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Hash}, @@ -58,6 +58,10 @@ impl TestApi { pub fn expect_hash_from_number(&self, n: BlockNumber) -> H256 { self.block_id_to_hash(&BlockId::Number(n)).unwrap().unwrap() } + + pub fn expect_hash_and_number(&self, n: BlockNumber) -> HashAndNumber { + HashAndNumber { hash: self.expect_hash_from_number(n), number: n } + } } impl ChainApi for TestApi { @@ -73,6 +77,7 @@ impl ChainApi for TestApi { _source: TransactionSource, uxt: ExtrinsicFor, ) -> Self::ValidationFuture { + let uxt = (*uxt).clone(); self.validation_requests.lock().push(uxt.clone()); let hash = self.hash_and_length(&uxt).0; let block_number = self.block_id_to_number(&BlockId::Hash(at)).unwrap().unwrap(); @@ -176,7 +181,7 @@ impl ChainApi for TestApi { } /// Hash the extrinsic. - fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (BlockHash, usize) { + fn hash_and_length(&self, uxt: &RawExtrinsicFor) -> (BlockHash, usize) { let encoded = uxt.encode(); let len = encoded.len(); (Hashing::hash(&encoded), len) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs new file mode 100644 index 00000000000..2dd5836c570 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -0,0 +1,533 @@ +// 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 . + +//! Multi-view pool dropped events listener provides means to combine streams from multiple pool +//! views into a single event stream. It allows management of dropped transaction events, adding new +//! views, and removing views as needed, ensuring that transactions which are no longer referenced +//! by any view are detected and properly notified. + +use crate::{ + common::log_xt::log_xt_trace, + fork_aware_txpool::stream_map_util::next_event, + graph::{BlockHash, ChainApi, ExtrinsicHash}, + LOG_TARGET, +}; +use futures::stream::StreamExt; +use log::{debug, trace}; +use sc_transaction_pool_api::TransactionStatus; +use sc_utils::mpsc; +use sp_runtime::traits::Block as BlockT; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + fmt::{self, Debug, Formatter}, + pin::Pin, +}; +use tokio_stream::StreamMap; + +/// Dropped-logic related event from the single view. +pub type ViewStreamEvent = crate::graph::DroppedByLimitsEvent, BlockHash>; + +/// Dropped-logic stream of events coming from the single view. +type ViewStream = Pin> + Send>>; + +/// Stream of extrinsic hashes that were dropped by the views and have no references by existing +/// views. +pub(crate) type StreamOfDropped = Pin> + Send>>; + +/// A type alias for a sender used as the controller of the [`MultiViewDropWatcherContext`]. +/// Used to send control commands from the [`MultiViewDroppedWatcherController`] to +/// [`MultiViewDropWatcherContext`]. +type Controller = mpsc::TracingUnboundedSender; + +/// A type alias for a receiver used as the commands receiver in the +/// [`MultiViewDropWatcherContext`]. +type CommandReceiver = mpsc::TracingUnboundedReceiver; + +/// Commands to control the instance of dropped transactions stream [`StreamOfDropped`]. +enum Command +where + C: ChainApi, +{ + /// Adds a new stream of dropped-related events originating in a view with a specific block + /// hash + AddView(BlockHash, ViewStream), + /// Removes an existing view's stream associated with a specific block hash. + RemoveView(BlockHash), + /// Adds initial views for given extrinsics hashes. + /// + /// This message should be sent when the external submission of a transaction occures. It + /// provides the list of initial views for given extrinsics hashes. + /// The dropped notification is not sent if it comes from the initial views. It allows to keep + /// transaction in the mempool, even if all the views are full at the time of submitting + /// transaction to the pool. + AddInitialViews(Vec>, BlockHash), + /// Removes all initial views for given extrinsic hashes. + /// + /// Intended to ba called on finalization. + RemoveFinalizedTxs(Vec>), +} + +impl Debug for Command +where + C: ChainApi, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Command::AddView(..) => write!(f, "AddView"), + Command::RemoveView(..) => write!(f, "RemoveView"), + Command::AddInitialViews(..) => write!(f, "AddInitialViews"), + Command::RemoveFinalizedTxs(..) => write!(f, "RemoveFinalizedTxs"), + } + } +} + +/// Manages the state and logic for handling events related to dropped transactions across multiple +/// views. +/// +/// This struct maintains a mapping of active views and their corresponding streams, as well as the +/// state of each transaction with respect to these views. +struct MultiViewDropWatcherContext +where + C: ChainApi, +{ + /// A map that associates the views identified by corresponding block hashes with their streams + /// of dropped-related events. This map is used to keep track of active views and their event + /// streams. + stream_map: StreamMap, ViewStream>, + /// A receiver for commands to control the state of the stream, allowing the addition and + /// removal of views. This is used to dynamically update which views are being tracked. + command_receiver: CommandReceiver>, + + /// For each transaction hash we keep the set of hashes representing the views that see this + /// transaction as ready or future. + /// + /// Once transaction is dropped, dropping view is removed from the set. + transaction_states: HashMap, HashSet>>, + + /// The list of initial view for every extrinsic. + /// + /// Dropped notifications from initial views will be silenced. This allows to accept the + /// transaction into the mempool, even if all the views are full at the time of submitting new + /// transaction. + initial_views: HashMap, HashSet>>, +} + +impl MultiViewDropWatcherContext +where + C: ChainApi + 'static, + <::Block as BlockT>::Hash: Unpin, +{ + /// Processes a `ViewStreamEvent` from a specific view and updates the internal state + /// accordingly. + /// + /// If the event indicates that a transaction has been dropped and is no longer referenced by + /// any active views, the transaction hash is returned. Otherwise `None` is returned. + fn handle_event( + &mut self, + block_hash: BlockHash, + event: ViewStreamEvent, + ) -> Option> { + trace!( + target: LOG_TARGET, + "dropped_watcher: handle_event: event:{:?} views:{:?}, ", + event, + self.stream_map.keys().collect::>(), + ); + let (tx_hash, status) = event; + match status { + TransactionStatus::Ready | TransactionStatus::Future => { + self.transaction_states.entry(tx_hash).or_default().insert(block_hash); + }, + TransactionStatus::Dropped | TransactionStatus::Usurped(_) => { + if let Entry::Occupied(mut views_keeping_tx_valid) = + self.transaction_states.entry(tx_hash) + { + views_keeping_tx_valid.get_mut().remove(&block_hash); + if views_keeping_tx_valid.get().is_empty() || + views_keeping_tx_valid + .get() + .iter() + .all(|h| !self.stream_map.contains_key(h)) + { + return self + .initial_views + .get(&tx_hash) + .map(|list| !list.contains(&block_hash)) + .unwrap_or(true) + .then(|| { + debug!("[{:?}] dropped_watcher: removing tx", tx_hash); + tx_hash + }) + } + } else { + debug!("[{:?}] dropped_watcher: removing (non-tracked) tx", tx_hash); + return Some(tx_hash) + } + }, + _ => {}, + }; + None + } + + /// Creates a new `StreamOfDropped` and its associated event stream controller. + /// + /// This method initializes the internal structures and unfolds the stream of dropped + /// transactions. Returns a tuple containing this stream and the controller for managing + /// this stream. + fn event_stream() -> (StreamOfDropped, Controller>) { + //note: 64 allows to avoid warning messages during execution of unit tests. + const CHANNEL_SIZE: usize = 64; + let (sender, command_receiver) = sc_utils::mpsc::tracing_unbounded::>( + "tx-pool-dropped-watcher-cmd-stream", + CHANNEL_SIZE, + ); + + let ctx = Self { + stream_map: StreamMap::new(), + command_receiver, + transaction_states: Default::default(), + initial_views: Default::default(), + }; + + let stream_map = futures::stream::unfold(ctx, |mut ctx| async move { + loop { + tokio::select! { + biased; + cmd = ctx.command_receiver.next() => { + match cmd? { + Command::AddView(key,stream) => { + trace!(target: LOG_TARGET,"dropped_watcher: Command::AddView {key:?} views:{:?}",ctx.stream_map.keys().collect::>()); + ctx.stream_map.insert(key,stream); + }, + Command::RemoveView(key) => { + trace!(target: LOG_TARGET,"dropped_watcher: Command::RemoveView {key:?} views:{:?}",ctx.stream_map.keys().collect::>()); + ctx.stream_map.remove(&key); + }, + Command::AddInitialViews(xts,block_hash) => { + log_xt_trace!(target: LOG_TARGET, xts.clone(), "[{:?}] dropped_watcher: xt initial view added {block_hash:?}"); + xts.into_iter().for_each(|xt| { + ctx.initial_views.entry(xt).or_default().insert(block_hash); + }); + }, + Command::RemoveFinalizedTxs(xts) => { + log_xt_trace!(target: LOG_TARGET, xts.clone(), "[{:?}] dropped_watcher: finalized xt removed"); + xts.iter().for_each(|xt| { + ctx.initial_views.remove(xt); + ctx.transaction_states.remove(xt); + }); + + }, + } + }, + + Some(event) = next_event(&mut ctx.stream_map) => { + if let Some(dropped) = ctx.handle_event(event.0, event.1) { + debug!("dropped_watcher: sending out: {dropped:?}"); + return Some((dropped, ctx)); + } + } + } + } + }) + .boxed(); + + (stream_map, sender) + } +} + +/// The controller for manipulating the state of the [`StreamOfDropped`]. +/// +/// This struct provides methods to add and remove streams associated with views to and from the +/// stream. +pub struct MultiViewDroppedWatcherController { + /// A controller allowing to update the state of the associated [`StreamOfDropped`]. + controller: Controller>, +} + +impl Clone for MultiViewDroppedWatcherController { + fn clone(&self) -> Self { + Self { controller: self.controller.clone() } + } +} + +impl MultiViewDroppedWatcherController +where + C: ChainApi + 'static, + <::Block as BlockT>::Hash: Unpin, +{ + /// Creates new [`StreamOfDropped`] and its controller. + pub fn new() -> (MultiViewDroppedWatcherController, StreamOfDropped) { + let (stream_map, ctrl) = MultiViewDropWatcherContext::::event_stream(); + (Self { controller: ctrl }, stream_map.boxed()) + } + + /// Notifies the [`StreamOfDropped`] that new view was created. + pub fn add_view(&self, key: BlockHash, view: ViewStream) { + let _ = self.controller.unbounded_send(Command::AddView(key, view)).map_err(|e| { + trace!(target: LOG_TARGET, "dropped_watcher: add_view {key:?} send message failed: {e}"); + }); + } + + /// Notifies the [`StreamOfDropped`] that the view was destroyed and shall be removed the + /// stream map. + pub fn remove_view(&self, key: BlockHash) { + let _ = self.controller.unbounded_send(Command::RemoveView(key)).map_err(|e| { + trace!(target: LOG_TARGET, "dropped_watcher: remove_view {key:?} send message failed: {e}"); + }); + } + + /// Adds the initial view for the given transactions hashes. + /// + /// This message should be called when the external submission of a transaction occures. It + /// provides the list of initial views for given extrinsics hashes. + /// + /// The dropped notification is not sent if it comes from the initial views. It allows to keep + /// transaction in the mempool, even if all the views are full at the time of submitting + /// transaction to the pool. + pub fn add_initial_views( + &self, + xts: impl IntoIterator> + Clone, + block_hash: BlockHash, + ) { + let _ = self + .controller + .unbounded_send(Command::AddInitialViews(xts.into_iter().collect(), block_hash)) + .map_err(|e| { + trace!(target: LOG_TARGET, "dropped_watcher: add_initial_views_ send message failed: {e}"); + }); + } + + /// Removes all initial views for finalized transactions. + pub fn remove_finalized_txs(&self, xts: impl IntoIterator> + Clone) { + let _ = self + .controller + .unbounded_send(Command::RemoveFinalizedTxs(xts.into_iter().collect())) + .map_err(|e| { + trace!(target: LOG_TARGET, "dropped_watcher: remove_initial_views send message failed: {e}"); + }); + } +} + +#[cfg(test)] +mod dropped_watcher_tests { + use super::*; + use crate::common::tests::TestApi; + use futures::{stream::pending, FutureExt, StreamExt}; + use sp_core::H256; + + type MultiViewDroppedWatcher = super::MultiViewDroppedWatcherController; + + #[tokio::test] + async fn test01() { + sp_tracing::try_init_simple(); + let (watcher, output_stream) = MultiViewDroppedWatcher::new(); + + let block_hash = H256::repeat_byte(0x01); + let tx_hash = H256::repeat_byte(0x0a); + + let view_stream = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + + watcher.add_view(block_hash, view_stream); + let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); + assert_eq!(handle.await.unwrap(), vec![tx_hash]); + } + + #[tokio::test] + async fn test02() { + sp_tracing::try_init_simple(); + let (watcher, mut output_stream) = MultiViewDroppedWatcher::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash = H256::repeat_byte(0x0a); + + let view_stream0 = futures::stream::iter(vec![(tx_hash, TransactionStatus::Future)]) + .chain(pending()) + .boxed(); + let view_stream1 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + + watcher.add_view(block_hash0, view_stream0); + + assert!(output_stream.next().now_or_never().is_none()); + watcher.add_view(block_hash1, view_stream1); + assert!(output_stream.next().now_or_never().is_none()); + } + + #[tokio::test] + async fn test03() { + sp_tracing::try_init_simple(); + let (watcher, output_stream) = MultiViewDroppedWatcher::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash0 = H256::repeat_byte(0x0a); + let tx_hash1 = H256::repeat_byte(0x0b); + + let view_stream0 = futures::stream::iter(vec![(tx_hash0, TransactionStatus::Future)]) + .chain(pending()) + .boxed(); + let view_stream1 = futures::stream::iter(vec![ + (tx_hash1, TransactionStatus::Ready), + (tx_hash1, TransactionStatus::Dropped), + ]) + .boxed(); + + watcher.add_view(block_hash0, view_stream0); + watcher.add_view(block_hash1, view_stream1); + let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); + assert_eq!(handle.await.unwrap(), vec![tx_hash1]); + } + + #[tokio::test] + async fn test04() { + sp_tracing::try_init_simple(); + let (watcher, mut output_stream) = MultiViewDroppedWatcher::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash = H256::repeat_byte(0x0b); + + let view_stream0 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Future), + (tx_hash, TransactionStatus::InBlock((block_hash1, 0))), + ]) + .boxed(); + let view_stream1 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + + watcher.add_view(block_hash0, view_stream0); + assert!(output_stream.next().now_or_never().is_none()); + + watcher.add_view(block_hash1, view_stream1); + let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); + assert_eq!(handle.await.unwrap(), vec![tx_hash]); + } + + #[tokio::test] + async fn test05() { + sp_tracing::try_init_simple(); + let (watcher, mut output_stream) = MultiViewDroppedWatcher::new(); + assert!(output_stream.next().now_or_never().is_none()); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash = H256::repeat_byte(0x0b); + + let view_stream0 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Future), + (tx_hash, TransactionStatus::InBlock((block_hash1, 0))), + ]) + .boxed(); + watcher.add_view(block_hash0, view_stream0); + assert!(output_stream.next().now_or_never().is_none()); + + let view_stream1 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::InBlock((block_hash0, 0))), + ]) + .boxed(); + + watcher.add_view(block_hash1, view_stream1); + assert!(output_stream.next().now_or_never().is_none()); + assert!(output_stream.next().now_or_never().is_none()); + assert!(output_stream.next().now_or_never().is_none()); + assert!(output_stream.next().now_or_never().is_none()); + assert!(output_stream.next().now_or_never().is_none()); + + let tx_hash = H256::repeat_byte(0x0c); + let view_stream2 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Future), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + let block_hash2 = H256::repeat_byte(0x03); + watcher.add_view(block_hash2, view_stream2); + let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); + assert_eq!(handle.await.unwrap(), vec![tx_hash]); + } + + #[tokio::test] + async fn test06() { + sp_tracing::try_init_simple(); + let (watcher, mut output_stream) = MultiViewDroppedWatcher::new(); + assert!(output_stream.next().now_or_never().is_none()); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash = H256::repeat_byte(0x0b); + + let view_stream0 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Future), + (tx_hash, TransactionStatus::InBlock((block_hash1, 0))), + ]) + .boxed(); + watcher.add_view(block_hash0, view_stream0); + assert!(output_stream.next().now_or_never().is_none()); + + let view_stream1 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + + watcher.add_view(block_hash1, view_stream1); + watcher.add_initial_views(vec![tx_hash], block_hash1); + assert!(output_stream.next().now_or_never().is_none()); + } + + #[tokio::test] + async fn test07() { + sp_tracing::try_init_simple(); + let (watcher, mut output_stream) = MultiViewDroppedWatcher::new(); + assert!(output_stream.next().now_or_never().is_none()); + + let block_hash0 = H256::repeat_byte(0x01); + let block_hash1 = H256::repeat_byte(0x02); + let tx_hash = H256::repeat_byte(0x0b); + + let view_stream0 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Future), + (tx_hash, TransactionStatus::InBlock((block_hash1, 0))), + ]) + .boxed(); + watcher.add_view(block_hash0, view_stream0); + watcher.add_initial_views(vec![tx_hash], block_hash0); + assert!(output_stream.next().now_or_never().is_none()); + + let view_stream1 = futures::stream::iter(vec![ + (tx_hash, TransactionStatus::Ready), + (tx_hash, TransactionStatus::Dropped), + ]) + .boxed(); + watcher.add_view(block_hash1, view_stream1); + + let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); + assert_eq!(handle.await.unwrap(), vec![tx_hash]); + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs new file mode 100644 index 00000000000..404225167e5 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -0,0 +1,1563 @@ +// 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 . + +//! Substrate fork-aware transaction pool implementation. + +use super::{ + dropped_watcher::{MultiViewDroppedWatcherController, StreamOfDropped}, + import_notification_sink::MultiViewImportNotificationSink, + metrics::MetricsLink as PrometheusMetrics, + multi_view_listener::MultiViewListener, + tx_mem_pool::{TxInMemPool, TxMemPool, TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER}, + view::View, + view_store::ViewStore, +}; +use crate::{ + api::FullChainApi, + common::log_xt::log_xt_trace, + enactment_state::{EnactmentAction, EnactmentState}, + fork_aware_txpool::revalidation_worker, + graph::{self, base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, IsValidator, Options}, + PolledIterator, ReadyIteratorFor, LOG_TARGET, +}; +use async_trait::async_trait; +use futures::{ + channel::oneshot, + future::{self}, + prelude::*, + FutureExt, +}; +use parking_lot::Mutex; +use prometheus_endpoint::Registry as PrometheusRegistry; +use sc_transaction_pool_api::{ + error::{Error, IntoPoolError}, + ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolFuture, PoolStatus, + TransactionFor, TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, +}; +use sp_blockchain::{HashAndNumber, TreeRoute}; +use sp_core::traits::SpawnEssentialNamed; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Extrinsic, NumberFor}, +}; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + sync::Arc, + time::Instant, +}; +use tokio::select; + +/// Fork aware transaction pool task, that needs to be polled. +pub type ForkAwareTxPoolTask = Pin + Send>>; + +/// A structure that maintains a collection of pollers associated with specific block hashes +/// (views). +struct ReadyPoll +where + Block: BlockT, +{ + pollers: HashMap>>, +} + +impl ReadyPoll +where + Block: BlockT, +{ + /// Creates a new `ReadyPoll` instance with an empty collection of pollers. + fn new() -> Self { + Self { pollers: Default::default() } + } + + /// Adds a new poller for a specific block hash and returns the `Receiver` end of the created + /// oneshot channel which will be used to deliver polled result. + fn add(&mut self, at: ::Hash) -> oneshot::Receiver { + let (s, r) = oneshot::channel(); + self.pollers.entry(at).or_default().push(s); + r + } + + /// Triggers all pollers associated with a specific block by sending the polled result through + /// each oneshot channel. + /// + /// `ready_iterator` is a closure that generates the result data to be sent to the pollers. + fn trigger(&mut self, at: Block::Hash, ready_iterator: impl Fn() -> T) { + log::trace!(target: LOG_TARGET, "fatp::trigger {at:?} pending keys: {:?}", self.pollers.keys()); + let Some(pollers) = self.pollers.remove(&at) else { return }; + pollers.into_iter().for_each(|p| { + log::debug!(target: LOG_TARGET, "trigger ready signal at block {}", at); + let _ = p.send(ready_iterator()); + }); + } + + /// Removes pollers that have their oneshot channels cancelled. + fn remove_cancelled(&mut self) { + self.pollers.retain(|_, v| v.iter().any(|sender| !sender.is_canceled())); + } +} + +/// The fork-aware transaction pool. +/// +/// It keeps track of every fork and provides the set of transactions that is valid for every fork. +pub struct ForkAwareTxPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, +{ + /// The reference to the `ChainApi` provided by client/backend. + api: Arc, + + /// Intermediate buffer for the incoming transaction. + mempool: Arc>, + + /// The store for all the views. + view_store: Arc>, + + /// Utility for managing pollers of `ready_at` future. + ready_poll: Arc, Block>>>, + + /// Prometheus's metrics endpoint. + metrics: PrometheusMetrics, + + /// Util tracking best and finalized block. + enactment_state: Arc>>, + + /// The channel allowing to send revalidation jobs to the background thread. + revalidation_queue: Arc>, + + /// Util providing an aggregated stream of transactions that were imported to ready queue in + /// any view. + import_notification_sink: MultiViewImportNotificationSink>, + + /// Externally provided pool options. + options: Options, + + /// Is node the validator. + is_validator: IsValidator, +} + +impl ForkAwareTxPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, + ::Hash: Unpin, +{ + /// Create new fork aware transaction pool with provided shared instance of `ChainApi` intended + /// for tests. + pub fn new_test( + pool_api: Arc, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + ) -> (Self, ForkAwareTxPoolTask) { + Self::new_test_with_limits( + pool_api, + best_block_hash, + finalized_hash, + Options::default().ready, + Options::default().future, + usize::MAX, + ) + } + + /// Create new fork aware transaction pool with given limits and with provided shared instance + /// of `ChainApi` intended for tests. + pub fn new_test_with_limits( + pool_api: Arc, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + ready_limits: crate::PoolLimit, + future_limits: crate::PoolLimit, + mempool_max_transactions_count: usize, + ) -> (Self, ForkAwareTxPoolTask) { + let listener = Arc::from(MultiViewListener::new()); + let (import_notification_sink, import_notification_sink_task) = + MultiViewImportNotificationSink::new_with_worker(); + + let mempool = Arc::from(TxMemPool::new( + pool_api.clone(), + listener.clone(), + Default::default(), + mempool_max_transactions_count, + )); + + let (dropped_stream_controller, dropped_stream) = + MultiViewDroppedWatcherController::::new(); + let dropped_monitor_task = Self::dropped_monitor_task( + dropped_stream, + mempool.clone(), + import_notification_sink.clone(), + ); + + let combined_tasks = async move { + tokio::select! { + _ = import_notification_sink_task => {}, + _ = dropped_monitor_task => {} + } + } + .boxed(); + + let options = Options { ready: ready_limits, future: future_limits, ..Default::default() }; + + ( + Self { + mempool, + api: pool_api.clone(), + view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), + revalidation_queue: Arc::from(revalidation_worker::RevalidationQueue::new()), + import_notification_sink, + options, + is_validator: false.into(), + metrics: Default::default(), + }, + combined_tasks, + ) + } + + /// Monitors the stream of dropped transactions and removes them from the mempool. + /// + /// This asynchronous task continuously listens for dropped transaction notifications provided + /// within `dropped_stream` and ensures that these transactions are removed from the `mempool` + /// and `import_notification_sink` instances. + async fn dropped_monitor_task( + mut dropped_stream: StreamOfDropped, + mempool: Arc>, + import_notification_sink: MultiViewImportNotificationSink< + Block::Hash, + ExtrinsicHash, + >, + ) { + loop { + let Some(dropped) = dropped_stream.next().await else { + log::debug!(target: LOG_TARGET, "fatp::dropped_monitor_task: terminated..."); + break; + }; + log::trace!(target: LOG_TARGET, "[{:?}] fatp::dropped notification, removing", dropped); + mempool.remove_dropped_transactions(&[dropped]).await; + import_notification_sink.clean_notified_items(&[dropped]); + } + } + + /// Creates new fork aware transaction pool with the background revalidation worker. + /// + /// The txpool essential tasks (including a revalidation worker) are spawned using provided + /// spawner. + pub fn new_with_background_worker( + options: Options, + is_validator: IsValidator, + pool_api: Arc, + prometheus: Option<&PrometheusRegistry>, + spawner: impl SpawnEssentialNamed, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + ) -> Self { + let metrics = PrometheusMetrics::new(prometheus); + let listener = Arc::from(MultiViewListener::new()); + let (revalidation_queue, revalidation_task) = + revalidation_worker::RevalidationQueue::new_with_worker(); + + let (import_notification_sink, import_notification_sink_task) = + MultiViewImportNotificationSink::new_with_worker(); + + let mempool = Arc::from(TxMemPool::new( + pool_api.clone(), + listener.clone(), + metrics.clone(), + TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER * (options.ready.count + options.future.count), + )); + + let (dropped_stream_controller, dropped_stream) = + MultiViewDroppedWatcherController::::new(); + let dropped_monitor_task = Self::dropped_monitor_task( + dropped_stream, + mempool.clone(), + import_notification_sink.clone(), + ); + + let combined_tasks = async move { + tokio::select! { + _ = revalidation_task => {}, + _ = import_notification_sink_task => {}, + _ = dropped_monitor_task => {} + } + } + .boxed(); + spawner.spawn_essential("txpool-background", Some("transaction-pool"), combined_tasks); + + Self { + mempool, + api: pool_api.clone(), + view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), + revalidation_queue: Arc::from(revalidation_queue), + import_notification_sink, + options, + metrics, + is_validator, + } + } + + /// Get access to the underlying api + pub fn api(&self) -> &ChainApi { + &self.api + } + + /// Provides a status for all views at the tips of the forks. + pub fn status_all(&self) -> HashMap { + self.view_store.status() + } + + /// Provides a number of views at the tips of the forks. + pub fn active_views_count(&self) -> usize { + self.view_store.active_views.read().len() + } + + /// Provides a number of views at the tips of the forks. + pub fn inactive_views_count(&self) -> usize { + self.view_store.inactive_views.read().len() + } + + /// Provides internal views statistics. + /// + /// Provides block number, count of ready, count of future transactions for every view. It is + /// suitable for printing log information. + fn views_stats(&self) -> Vec<(NumberFor, usize, usize)> { + self.view_store + .active_views + .read() + .iter() + .map(|v| (v.1.at.number, v.1.status().ready, v.1.status().future)) + .collect() + } + + /// Checks if there is a view at the tip of the fork with given hash. + pub fn has_view(&self, hash: &Block::Hash) -> bool { + self.view_store.active_views.read().contains_key(hash) + } + + /// Returns a number of unwatched and watched transactions in internal mempool. + /// + /// Intended for use in unit tests. + pub fn mempool_len(&self) -> (usize, usize) { + self.mempool.unwatched_and_watched_count() + } + + /// Returns a best-effort set of ready transactions for a given block, without executing full + /// maintain process. + /// + /// The method attempts to build a temporary view and create an iterator of ready transactions + /// for a specific `at` hash. If a valid view is found, it collects and prunes + /// transactions already included in the blocks and returns the valid set. + /// + /// Pruning is just rebuilding the underlying transactions graph, no validations are executed, + /// so this process shall be fast. + pub fn ready_at_light(&self, at: Block::Hash) -> PolledIterator { + let start = Instant::now(); + let api = self.api.clone(); + log::trace!(target: LOG_TARGET, "fatp::ready_at_light {:?}", at); + + let Ok(block_number) = self.api.resolve_block_number(at) else { + let empty: ReadyIteratorFor = Box::new(std::iter::empty()); + return Box::pin(async { empty }) + }; + + let best_result = { + api.tree_route(self.enactment_state.lock().recent_finalized_block(), at).map( + |tree_route| { + if let Some((index, view)) = + tree_route.enacted().iter().enumerate().rev().skip(1).find_map(|(i, b)| { + self.view_store.get_view_at(b.hash, true).map(|(view, _)| (i, view)) + }) { + let e = tree_route.enacted()[index..].to_vec(); + (TreeRoute::new(e, 0).ok(), Some(view)) + } else { + (None, None) + } + }, + ) + }; + + Box::pin(async move { + if let Ok((Some(best_tree_route), Some(best_view))) = best_result { + let tmp_view: View = View::new_from_other( + &best_view, + &HashAndNumber { hash: at, number: block_number }, + ); + + let mut all_extrinsics = vec![]; + + for h in best_tree_route.enacted() { + let extrinsics = api + .block_body(h.hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); + None + }) + .unwrap_or_default() + .into_iter() + .map(|t| api.hash_and_length(&t).0); + all_extrinsics.extend(extrinsics); + } + + let before_count = tmp_view.pool.validated_pool().status().ready; + let tags = tmp_view + .pool + .validated_pool() + .extrinsics_tags(&all_extrinsics) + .into_iter() + .flatten() + .flatten() + .collect::>(); + let _ = tmp_view.pool.validated_pool().prune_tags(tags); + + let after_count = tmp_view.pool.validated_pool().status().ready; + log::debug!(target: LOG_TARGET, + "fatp::ready_at_light {} from {} before: {} to be removed: {} after: {} took:{:?}", + at, + best_view.at.hash, + before_count, + all_extrinsics.len(), + after_count, + start.elapsed() + ); + Box::new(tmp_view.pool.validated_pool().ready()) + } else { + let empty: ReadyIteratorFor = Box::new(std::iter::empty()); + log::debug!(target: LOG_TARGET, "fatp::ready_at_light {} -> empty, took:{:?}", at, start.elapsed()); + empty + } + }) + } + + /// Waits for the set of ready transactions for a given block up to a specified timeout. + /// + /// This method combines two futures: + /// - The `ready_at` future, which waits for the ready transactions resulting from the full + /// maintenance process to be available. + /// - The `ready_at_light` future, used as a fallback if the timeout expires before `ready_at` + /// completes. This provides a best-effort, ready set of transactions as a result light + /// maintain. + /// + /// Returns a future resolving to a ready iterator of transactions. + fn ready_at_with_timeout_internal( + &self, + at: Block::Hash, + timeout: std::time::Duration, + ) -> PolledIterator { + log::debug!(target: LOG_TARGET, "fatp::ready_at_with_timeout at {:?} allowed delay: {:?}", at, timeout); + + let timeout = futures_timer::Delay::new(timeout); + let (view_already_exists, ready_at) = self.ready_at_internal(at); + + if view_already_exists { + return ready_at; + } + + let maybe_ready = async move { + select! { + ready = ready_at => Some(ready), + _ = timeout => { + log::warn!(target: LOG_TARGET, + "Timeout fired waiting for transaction pool at block: ({:?}). \ + Proceeding with production.", + at, + ); + None + } + } + }; + + let fall_back_ready = self.ready_at_light(at); + Box::pin(async { + let (maybe_ready, fall_back_ready) = + futures::future::join(maybe_ready.boxed(), fall_back_ready.boxed()).await; + maybe_ready.unwrap_or(fall_back_ready) + }) + } + + fn ready_at_internal(&self, at: Block::Hash) -> (bool, PolledIterator) { + let mut ready_poll = self.ready_poll.lock(); + + if let Some((view, inactive)) = self.view_store.get_view_at(at, true) { + log::debug!(target: LOG_TARGET, "fatp::ready_at {at:?} (inactive:{inactive:?})"); + let iterator: ReadyIteratorFor = Box::new(view.pool.validated_pool().ready()); + return (true, async move { iterator }.boxed()); + } + + let pending = ready_poll + .add(at) + .map(|received| { + received.unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Error receiving ready-set iterator: {:?}", e); + Box::new(std::iter::empty()) + }) + }) + .boxed(); + log::debug!(target: LOG_TARGET, + "fatp::ready_at {at:?} pending keys: {:?}", + ready_poll.pollers.keys() + ); + (false, pending) + } +} + +/// Converts the input view-to-statuses map into the output vector of statuses. +/// +/// The result of importing a bunch of transactions into a single view is the vector of statuses. +/// Every item represents a status for single transaction. The input is the map that associates +/// hash-views with vectors indicating the statuses of transactions imports. +/// +/// Import to multiple views result in two-dimensional array of statuses, which is provided as +/// input map. +/// +/// This function converts the map into the vec of results, according to the following rules: +/// - for given transaction if at least one status is success, then output vector contains success, +/// - if given transaction status is error for every view, then output vector contains error. +/// +/// The results for transactions are in the same order for every view. An output vector preserves +/// this order. +/// +/// ```skip +/// in: +/// view | xt0 status | xt1 status | xt2 status +/// h1 -> [ Ok(xth0), Ok(xth1), Err ] +/// h2 -> [ Ok(xth0), Err, Err ] +/// h3 -> [ Ok(xth0), Ok(xth1), Err ] +/// +/// out: +/// [ Ok(xth0), Ok(xth1), Err ] +/// ``` +fn reduce_multiview_result(input: HashMap>>) -> Vec> { + let mut values = input.values(); + let Some(first) = values.next() else { + return Default::default(); + }; + let length = first.len(); + debug_assert!(values.all(|x| length == x.len())); + + input + .into_values() + .reduce(|mut agg_results, results| { + agg_results.iter_mut().zip(results.into_iter()).for_each(|(agg_r, r)| { + if agg_r.is_err() { + *agg_r = r; + } + }); + agg_results + }) + .unwrap_or_default() +} + +impl TransactionPool for ForkAwareTxPool +where + Block: BlockT, + ChainApi: 'static + graph::ChainApi, + ::Hash: Unpin, +{ + type Block = ChainApi::Block; + type Hash = ExtrinsicHash; + type InPoolTransaction = Transaction, ExtrinsicFor>; + type Error = ChainApi::Error; + + /// Submits multiple transactions and returns a future resolving to the submission results. + /// + /// Actual transactions submission process is delegated to the `ViewStore` internal instance. + /// + /// The internal limits of the pool are checked. The results of submissions to individual views + /// are reduced to single result. Refer to `reduce_multiview_result` for more details. + fn submit_at( + &self, + _: ::Hash, + source: TransactionSource, + xts: Vec>, + ) -> PoolFuture, Self::Error>>, Self::Error> { + let view_store = self.view_store.clone(); + log::debug!(target: LOG_TARGET, "fatp::submit_at count:{} views:{}", xts.len(), self.active_views_count()); + log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.tx_hash(xt)), "[{:?}] fatp::submit_at"); + let xts = xts.into_iter().map(Arc::from).collect::>(); + let mempool_result = self.mempool.extend_unwatched(source, xts.clone()); + + if view_store.is_empty() { + return future::ready(Ok(mempool_result)).boxed() + } + + let (hashes, to_be_submitted): (Vec>, Vec>) = + mempool_result + .iter() + .zip(xts) + .filter_map(|(result, xt)| result.as_ref().ok().map(|xt_hash| (xt_hash, xt))) + .unzip(); + + self.metrics + .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); + + let mempool = self.mempool.clone(); + async move { + let results_map = view_store.submit(source, to_be_submitted.into_iter(), hashes).await; + let mut submission_results = reduce_multiview_result(results_map).into_iter(); + + Ok(mempool_result + .into_iter() + .map(|result| { + result.and_then(|xt_hash| { + let result = submission_results + .next() + .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed."); + result.or_else(|error| { + let error = error.into_pool_error(); + match error { + Ok( + // The transaction is still in mempool it may get included into the view for the next block. + Error::ImmediatelyDropped + ) => Ok(xt_hash), + Ok(e) => { + mempool.remove(xt_hash); + Err(e.into()) + }, + Err(e) => Err(e), + } + }) + }) + }) + .collect::>()) + } + .boxed() + } + + /// Submits a single transaction and returns a future resolving to the submission results. + /// + /// Actual transaction submission process is delegated to the `submit_at` function. + fn submit_one( + &self, + _at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture, Self::Error> { + log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_one views:{}", self.tx_hash(&xt), self.active_views_count()); + let result_future = self.submit_at(_at, source, vec![xt]); + async move { + let result = result_future.await; + match result { + Ok(mut v) => + v.pop().expect("There is exactly one element in result of submit_at. qed."), + Err(e) => Err(e), + } + } + .boxed() + } + + /// Submits a transaction and starts to watch its progress in the pool, returning a stream of + /// status updates. + /// + /// Actual transaction submission process is delegated to the `ViewStore` internal instance. + fn submit_and_watch( + &self, + at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture>>, Self::Error> { + log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); + let xt = Arc::from(xt); + let xt_hash = match self.mempool.push_watched(source, xt.clone()) { + Ok(xt_hash) => xt_hash, + Err(e) => return future::ready(Err(e)).boxed(), + }; + + self.metrics.report(|metrics| metrics.submitted_transactions.inc()); + + let view_store = self.view_store.clone(); + let mempool = self.mempool.clone(); + async move { + let result = view_store.submit_and_watch(at, source, xt).await; + let result = result.or_else(|(e, maybe_watcher)| { + let error = e.into_pool_error(); + match (error, maybe_watcher) { + ( + Ok( + // The transaction is still in mempool it may get included into the + // view for the next block. + Error::ImmediatelyDropped, + ), + Some(watcher), + ) => Ok(watcher), + (Ok(e), _) => { + mempool.remove(xt_hash); + Err(e.into()) + }, + (Err(e), _) => Err(e), + } + }); + result + } + .boxed() + } + + /// Intended to remove transactions identified by the given hashes, and any dependent + /// transactions, from the pool. In current implementation this function only outputs the error. + /// Seems that API change is needed here to make this call reasonable. + // todo [#5491]: api change? we need block hash here (assuming we need it at all - could be + // useful for verification for debugging purposes). + fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { + if !hashes.is_empty() { + log::debug!(target: LOG_TARGET, "fatp::remove_invalid {}", hashes.len()); + log_xt_trace!(target:LOG_TARGET, hashes, "[{:?}] fatp::remove_invalid"); + self.metrics + .report(|metrics| metrics.removed_invalid_txs.inc_by(hashes.len() as _)); + } + Default::default() + } + + // todo [#5491]: api change? + // status(Hash) -> Option + /// Returns the pool status which includes information like the number of ready and future + /// transactions. + /// + /// Currently the status for the most recently notified best block is returned (for which + /// maintain process was accomplished). + fn status(&self) -> PoolStatus { + self.view_store + .most_recent_view + .read() + .map(|hash| self.view_store.status()[&hash].clone()) + .unwrap_or(PoolStatus { ready: 0, ready_bytes: 0, future: 0, future_bytes: 0 }) + } + + /// Return an event stream of notifications when transactions are imported to the pool. + /// + /// Consumers of this stream should use the `ready` method to actually get the + /// pending transactions in the right order. + fn import_notification_stream(&self) -> ImportNotificationStream> { + self.import_notification_sink.event_stream() + } + + /// Returns the hash of a given transaction. + fn hash_of(&self, xt: &TransactionFor) -> TxHash { + self.api().hash_and_length(xt).0 + } + + /// Notifies the pool about the broadcasting status of transactions. + fn on_broadcasted(&self, propagations: HashMap, Vec>) { + self.view_store.listener.transactions_broadcasted(propagations); + } + + /// Return specific ready transaction by hash, if there is one. + /// + /// Currently the ready transaction is returned if it exists for the most recently notified best + /// block (for which maintain process was accomplished). + // todo [#5491]: api change: we probably should have at here? + fn ready_transaction(&self, tx_hash: &TxHash) -> Option> { + let most_recent_view = self.view_store.most_recent_view.read(); + let result = most_recent_view + .map(|block_hash| self.view_store.ready_transaction(block_hash, tx_hash)) + .flatten(); + log::trace!( + target: LOG_TARGET, + "[{tx_hash:?}] ready_transaction: {} {:?}", + result.is_some(), + most_recent_view + ); + result + } + + /// Returns an iterator for ready transactions at a specific block, ordered by priority. + fn ready_at(&self, at: ::Hash) -> PolledIterator { + let (_, result) = self.ready_at_internal(at); + result + } + + /// Returns an iterator for ready transactions, ordered by priority. + /// + /// Currently the set of ready transactions is returned if it exists for the most recently + /// notified best block (for which maintain process was accomplished). + fn ready(&self) -> ReadyIteratorFor { + self.view_store.ready() + } + + /// Returns a list of future transactions in the pool. + /// + /// Currently the set of future transactions is returned if it exists for the most recently + /// notified best block (for which maintain process was accomplished). + fn futures(&self) -> Vec { + self.view_store.futures() + } + + /// Returns a set of ready transactions at a given block within the specified timeout. + /// + /// If the timeout expires before the maintain process is accomplished, a best-effort + /// set of transactions is returned (refer to `ready_at_light`). + fn ready_at_with_timeout( + &self, + at: ::Hash, + timeout: std::time::Duration, + ) -> PolledIterator { + self.ready_at_with_timeout_internal(at, timeout) + } +} + +impl sc_transaction_pool_api::LocalTransactionPool + for ForkAwareTxPool, Block> +where + Block: BlockT, + ::Hash: Unpin, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata, + Client: Send + Sync + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + type Block = Block; + type Hash = ExtrinsicHash>; + type Error = as graph::ChainApi>::Error; + + fn submit_local( + &self, + _at: Block::Hash, + _xt: sc_transaction_pool_api::LocalTransactionFor, + ) -> Result { + //todo [#5493] + //looks like view_store / view needs non async submit_local method ?. + let e = Err(sc_transaction_pool_api::error::Error::Unactionable.into()); + log::warn!( + target: LOG_TARGET, + "LocalTransactionPool::submit_local is not implemented for ForkAwareTxPool, returning error: {e:?}", + ); + e + } +} + +impl ForkAwareTxPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, + ::Hash: Unpin, +{ + /// Handles a new block notification. + /// + /// It is responsible for handling a newly notified block. It executes some sanity checks, find + /// the best view to clone from and executes the new view build procedure for the notified + /// block. + /// + /// If the view is correctly created, `ready_at` pollers for this block will be triggered. + async fn handle_new_block(&self, tree_route: &TreeRoute) { + let hash_and_number = match tree_route.last() { + Some(hash_and_number) => hash_and_number, + None => { + log::warn!( + target: LOG_TARGET, + "Skipping ChainEvent - no last block in tree route {:?}", + tree_route, + ); + return + }, + }; + + if self.has_view(&hash_and_number.hash) { + log::trace!( + target: LOG_TARGET, + "view already exists for block: {:?}", + hash_and_number, + ); + return + } + + let best_view = self.view_store.find_best_view(tree_route); + let new_view = self.build_new_view(best_view, hash_and_number, tree_route).await; + + if let Some(view) = new_view { + { + let view = view.clone(); + self.ready_poll.lock().trigger(hash_and_number.hash, move || { + Box::from(view.pool.validated_pool().ready()) + }); + } + + View::start_background_revalidation(view, self.revalidation_queue.clone()).await; + } + } + + /// Builds a new view. + /// + /// If `origin_view` is provided, the new view will be cloned from it. Otherwise an empty view + /// will be created. + /// + /// The new view will be updated with transactions from the tree_route and the mempool, all + /// required events will be triggered, it will be inserted to the view store. + /// + /// This method will also update multi-view listeners with newly created view. + async fn build_new_view( + &self, + origin_view: Option>>, + at: &HashAndNumber, + tree_route: &TreeRoute, + ) -> Option>> { + log::debug!( + target: LOG_TARGET, + "build_new_view: for: {:?} from: {:?} tree_route: {:?}", + at, + origin_view.as_ref().map(|v| v.at.clone()), + tree_route + ); + let mut view = if let Some(origin_view) = origin_view { + let mut view = View::new_from_other(&origin_view, at); + if !tree_route.retracted().is_empty() { + view.pool.clear_recently_pruned(); + } + view + } else { + log::debug!(target: LOG_TARGET, "creating non-cloned view: for: {at:?}"); + View::new( + self.api.clone(), + at.clone(), + self.options.clone(), + self.metrics.clone(), + self.is_validator.clone(), + ) + }; + + // 1. Capture all import notification from the very beginning, so first register all + //the listeners. + self.import_notification_sink.add_view( + view.at.hash, + view.pool.validated_pool().import_notification_stream().boxed(), + ); + + self.view_store.dropped_stream_controller.add_view( + view.at.hash, + view.pool.validated_pool().create_dropped_by_limits_stream().boxed(), + ); + + let start = Instant::now(); + let watched_xts = self.register_listeners(&mut view).await; + let duration = start.elapsed(); + log::debug!(target: LOG_TARGET, "register_listeners: at {at:?} took {duration:?}"); + + // 2. Handle transactions from the tree route. Pruning transactions from the view first + // will make some space for mempool transactions in case we are at the view's limits. + let start = Instant::now(); + self.update_view_with_fork(&view, tree_route, at.clone()).await; + let duration = start.elapsed(); + log::debug!(target: LOG_TARGET, "update_view_with_fork: at {at:?} took {duration:?}"); + + // 3. Finally, submit transactions from the mempool. + let start = Instant::now(); + self.update_view_with_mempool(&mut view, watched_xts).await; + let duration = start.elapsed(); + log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {at:?} took {duration:?}"); + + let view = Arc::from(view); + self.view_store.insert_new_view(view.clone(), tree_route).await; + Some(view) + } + + /// Returns the list of xts included in all block ancestors, including the block itself. + /// + /// Example: for the following chain `F<-B1<-B2<-B3` xts from `F,B1,B2,B3` will be returned. + async fn extrinsics_included_since_finalized(&self, at: Block::Hash) -> HashSet> { + let start = Instant::now(); + let recent_finalized_block = self.enactment_state.lock().recent_finalized_block(); + + let Ok(tree_route) = self.api.tree_route(recent_finalized_block, at) else { + return Default::default() + }; + + let api = self.api.clone(); + let mut all_extrinsics = HashSet::new(); + + for h in tree_route.enacted().iter().rev() { + api.block_body(h.hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Compute ready light transactions: error request: {}", e); + None + }) + .unwrap_or_default() + .into_iter() + .map(|t| self.hash_of(&t)) + .for_each(|tx_hash| { + all_extrinsics.insert(tx_hash); + }); + } + + log::debug!(target: LOG_TARGET, + "fatp::extrinsics_included_since_finalized {} from {} count: {} took:{:?}", + at, + recent_finalized_block, + all_extrinsics.len(), + start.elapsed() + ); + all_extrinsics + } + + /// For every watched transaction in the mempool registers a transaction listener in the view. + /// + /// The transaction listener for a given view is also added to multi-view listener. This allows + /// to track aggreagated progress of the transaction within the transaction pool. + /// + /// Function returns a list of currently watched transactions in the mempool. + async fn register_listeners( + &self, + view: &View, + ) -> Vec<(ExtrinsicHash, Arc>)> { + log::debug!( + target: LOG_TARGET, + "register_listeners: {:?} xts:{:?} v:{}", + view.at, + self.mempool.unwatched_and_watched_count(), + self.active_views_count() + ); + + //todo [#5495]: maybe we don't need to register listener in view? We could use + // multi_view_listener.transaction_in_block + let results = self + .mempool + .clone_watched() + .into_iter() + .map(|(tx_hash, tx)| { + let watcher = view.create_watcher(tx_hash); + let at = view.at.clone(); + async move { + log::trace!(target: LOG_TARGET, "[{:?}] adding watcher {:?}", tx_hash, at.hash); + self.view_store.listener.add_view_watcher_for_tx( + tx_hash, + at.hash, + watcher.into_stream().boxed(), + ); + (tx_hash, tx) + } + }) + .collect::>(); + + future::join_all(results).await + } + + /// Updates the given view with the transaction from the internal mempol. + /// + /// All transactions from the mempool (excluding those which are either already imported or + /// already included in blocks since recently finalized block) are submitted to the + /// view. + /// + /// If there are no views, and mempool transaction is reported as invalid for the given view, + /// the transaction is reported as invalid and removed from the mempool. This does not apply to + /// stale and temporarily banned transactions. + /// + /// As the listeners for watched transactions were registered at the very beginning of maintain + /// procedure (`register_listeners`), this function accepts the list of watched transactions + /// from the mempool for which listener was actually registered to avoid submit/maintain races. + async fn update_view_with_mempool( + &self, + view: &View, + watched_xts: Vec<(ExtrinsicHash, Arc>)>, + ) { + log::debug!( + target: LOG_TARGET, + "update_view_with_mempool: {:?} xts:{:?} v:{}", + view.at, + self.mempool.unwatched_and_watched_count(), + self.active_views_count() + ); + let included_xts = self.extrinsics_included_since_finalized(view.at.hash).await; + let xts = self.mempool.clone_unwatched(); + + let mut all_submitted_count = 0; + if !xts.is_empty() { + let unwatched_count = xts.len(); + let mut buckets = HashMap::>>::default(); + xts.into_iter() + .filter(|(hash, _)| !view.pool.validated_pool().pool.read().is_imported(hash)) + .filter(|(hash, _)| !included_xts.contains(&hash)) + .map(|(_, tx)| (tx.source(), tx.tx())) + .for_each(|(source, tx)| buckets.entry(source).or_default().push(tx)); + + for (source, xts) in buckets { + all_submitted_count += xts.len(); + let _ = view.submit_many(source, xts).await; + } + log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} unwatched {}/{}", view.at.hash, all_submitted_count, unwatched_count); + } + + let watched_submitted_count = watched_xts.len(); + + let mut buckets = HashMap::< + TransactionSource, + Vec<(ExtrinsicHash, ExtrinsicFor)>, + >::default(); + watched_xts + .into_iter() + .filter(|(hash, _)| !included_xts.contains(&hash)) + .map(|(tx_hash, tx)| (tx.source(), tx_hash, tx.tx())) + .for_each(|(source, tx_hash, tx)| { + buckets.entry(source).or_default().push((tx_hash, tx)) + }); + + let mut watched_results = Vec::default(); + for (source, watched_xts) in buckets { + let hashes = watched_xts.iter().map(|i| i.0).collect::>(); + let results = view + .submit_many(source, watched_xts.into_iter().map(|i| i.1)) + .await + .into_iter() + .zip(hashes) + .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) + .collect::>(); + watched_results.extend(results); + } + + log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} watched {}/{}", view.at.hash, watched_submitted_count, self.mempool_len().1); + + all_submitted_count += watched_submitted_count; + let _ = all_submitted_count + .try_into() + .map(|v| self.metrics.report(|metrics| metrics.submitted_from_mempool_txs.inc_by(v))); + + // if there are no views yet, and a single newly created view is reporting error, just send + // out the invalid event, and remove transaction. + if self.view_store.is_empty() { + for result in watched_results { + match result { + Err(tx_hash) => { + self.view_store.listener.invalidate_transactions(&[tx_hash]); + self.mempool.remove(tx_hash); + }, + Ok(_) => {}, + } + } + } + } + + /// Updates the view with the transactions from the given tree route. + /// + /// Transactions from the retracted blocks are resubmitted to the given view. Tags for + /// transactions included in blocks on enacted fork are pruned from the provided view. + async fn update_view_with_fork( + &self, + view: &View, + tree_route: &TreeRoute, + hash_and_number: HashAndNumber, + ) { + log::debug!(target: LOG_TARGET, "update_view_with_fork tree_route: {:?} {tree_route:?}", view.at); + let api = self.api.clone(); + + // We keep track of everything we prune so that later we won't add + // transactions with those hashes from the retracted blocks. + let mut pruned_log = HashSet::>::new(); + + future::join_all( + tree_route + .enacted() + .iter() + .map(|h| crate::prune_known_txs_for_block(h, &*api, &view.pool)), + ) + .await + .into_iter() + .for_each(|enacted_log| { + pruned_log.extend(enacted_log); + }); + + //resubmit + { + let mut resubmit_transactions = Vec::new(); + + for retracted in tree_route.retracted() { + let hash = retracted.hash; + + let block_transactions = api + .block_body(hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Failed to fetch block body: {}", e); + None + }) + .unwrap_or_default() + .into_iter() + .filter(|tx| tx.is_signed().unwrap_or(true)); + + let mut resubmitted_to_report = 0; + + resubmit_transactions.extend( + block_transactions + .into_iter() + .map(|tx| (self.hash_of(&tx), tx)) + .filter(|(tx_hash, _)| { + let contains = pruned_log.contains(&tx_hash); + + // need to count all transactions, not just filtered, here + resubmitted_to_report += 1; + + if !contains { + log::trace!( + target: LOG_TARGET, + "[{:?}]: Resubmitting from retracted block {:?}", + tx_hash, + hash, + ); + } + !contains + }) + .map(|(tx_hash, tx)| { + //find arc if tx is known + self.mempool.get_by_hash(tx_hash).unwrap_or_else(|| Arc::from(tx)) + }), + ); + + self.metrics.report(|metrics| { + metrics.resubmitted_retracted_txs.inc_by(resubmitted_to_report) + }); + } + + let _ = view + .pool + .resubmit_at( + &hash_and_number, + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TransactionSource::External, + resubmit_transactions, + ) + .await; + } + } + + /// Executes the maintainance for the finalized event. + /// + /// Performs a house-keeping required for finalized event. This includes: + /// - executing the on finalized procedure for the view store, + /// - purging finalized transactions from the mempool and triggering mempool revalidation, + async fn handle_finalized(&self, finalized_hash: Block::Hash, tree_route: &[Block::Hash]) { + let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); + log::debug!(target: LOG_TARGET, "handle_finalized {finalized_number:?} tree_route: {tree_route:?} views_count:{}", self.active_views_count()); + + let finalized_xts = self.view_store.handle_finalized(finalized_hash, tree_route).await; + + self.mempool.purge_finalized_transactions(&finalized_xts).await; + self.import_notification_sink.clean_notified_items(&finalized_xts); + + self.metrics + .report(|metrics| metrics.finalized_txs.inc_by(finalized_xts.len() as _)); + + if let Ok(Some(finalized_number)) = finalized_number { + self.revalidation_queue + .revalidate_mempool( + self.mempool.clone(), + HashAndNumber { hash: finalized_hash, number: finalized_number }, + ) + .await; + } else { + log::trace!(target: LOG_TARGET, "purge_transactions_later skipped, cannot find block number {finalized_number:?}"); + } + + self.ready_poll.lock().remove_cancelled(); + log::trace!(target: LOG_TARGET, "handle_finalized after views_count:{:?}", self.active_views_count()); + } + + /// Computes a hash of the provided transaction + fn tx_hash(&self, xt: &TransactionFor) -> TxHash { + self.api.hash_and_length(xt).0 + } +} + +#[async_trait] +impl MaintainedTransactionPool for ForkAwareTxPool +where + Block: BlockT, + ChainApi: 'static + graph::ChainApi, + ::Hash: Unpin, +{ + /// Executes the maintainance for the given chain event. + async fn maintain(&self, event: ChainEvent) { + let start = Instant::now(); + log::debug!(target: LOG_TARGET, "processing event: {event:?}"); + + self.view_store.finish_background_revalidations().await; + + let prev_finalized_block = self.enactment_state.lock().recent_finalized_block(); + + let compute_tree_route = |from, to| -> Result, String> { + match self.api.tree_route(from, to) { + Ok(tree_route) => Ok(tree_route), + Err(e) => + return Err(format!( + "Error occurred while computing tree_route from {from:?} to {to:?}: {e}" + )), + } + }; + let block_id_to_number = + |hash| self.api.block_id_to_number(&BlockId::Hash(hash)).map_err(|e| format!("{}", e)); + + let result = + self.enactment_state + .lock() + .update(&event, &compute_tree_route, &block_id_to_number); + + match result { + Err(msg) => { + log::trace!(target: LOG_TARGET, "enactment_state::update error: {msg}"); + self.enactment_state.lock().force_update(&event); + }, + Ok(EnactmentAction::Skip) => return, + Ok(EnactmentAction::HandleFinalization) => { + // todo [#5492]: in some cases handle_new_block is actually needed (new_num > + // tips_of_forks) let hash = event.hash(); + // if !self.has_view(hash) { + // if let Ok(tree_route) = compute_tree_route(prev_finalized_block, hash) { + // self.handle_new_block(&tree_route).await; + // } + // } + }, + Ok(EnactmentAction::HandleEnactment(tree_route)) => { + if matches!(event, ChainEvent::Finalized { .. }) { + self.view_store.handle_pre_finalized(event.hash()).await; + }; + self.handle_new_block(&tree_route).await; + }, + }; + + match event { + ChainEvent::NewBestBlock { .. } => {}, + ChainEvent::Finalized { hash, ref tree_route } => { + self.handle_finalized(hash, tree_route).await; + + log::trace!( + target: LOG_TARGET, + "on-finalized enacted: {tree_route:?}, previously finalized: \ + {prev_finalized_block:?}", + ); + }, + } + + let maintain_duration = start.elapsed(); + + log::info!( + target: LOG_TARGET, + "maintain: txs:{:?} views:[{};{:?}] event:{event:?} took:{:?}", + self.mempool_len(), + self.active_views_count(), + self.views_stats(), + maintain_duration + ); + + self.metrics.report(|metrics| { + let (unwatched, watched) = self.mempool_len(); + let _ = ( + self.active_views_count().try_into().map(|v| metrics.active_views.set(v)), + self.inactive_views_count().try_into().map(|v| metrics.inactive_views.set(v)), + watched.try_into().map(|v| metrics.watched_txs.set(v)), + unwatched.try_into().map(|v| metrics.unwatched_txs.set(v)), + ); + metrics.maintain_duration.observe(maintain_duration.as_secs_f64()); + }); + } +} + +impl ForkAwareTxPool, Block> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sc_client_api::ExecutorProvider + + sc_client_api::UsageProvider + + sp_blockchain::HeaderMetadata + + Send + + Sync + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, + ::Hash: std::marker::Unpin, +{ + /// Create new fork aware transaction pool for a full node with the provided api. + pub fn new_full( + options: Options, + is_validator: IsValidator, + prometheus: Option<&PrometheusRegistry>, + spawner: impl SpawnEssentialNamed, + client: Arc, + ) -> Self { + let pool_api = Arc::new(FullChainApi::new(client.clone(), prometheus, &spawner)); + let pool = Self::new_with_background_worker( + options, + is_validator, + pool_api, + prometheus, + spawner, + client.usage_info().chain.best_hash, + client.usage_info().chain.finalized_hash, + ); + + pool + } +} + +#[cfg(test)] +mod reduce_multiview_result_tests { + use super::*; + use sp_core::H256; + #[derive(Debug, PartialEq, Clone)] + enum Error { + Custom(u8), + } + + #[test] + fn empty() { + sp_tracing::try_init_simple(); + let input = HashMap::default(); + let r = reduce_multiview_result::(input); + assert!(r.is_empty()); + } + + #[test] + fn errors_only() { + sp_tracing::try_init_simple(); + let v: Vec<(H256, Vec>)> = vec![ + ( + H256::repeat_byte(0x13), + vec![ + Err(Error::Custom(10)), + Err(Error::Custom(11)), + Err(Error::Custom(12)), + Err(Error::Custom(13)), + ], + ), + ( + H256::repeat_byte(0x14), + vec![ + Err(Error::Custom(20)), + Err(Error::Custom(21)), + Err(Error::Custom(22)), + Err(Error::Custom(23)), + ], + ), + ( + H256::repeat_byte(0x15), + vec![ + Err(Error::Custom(30)), + Err(Error::Custom(31)), + Err(Error::Custom(32)), + Err(Error::Custom(33)), + ], + ), + ]; + let input = HashMap::from_iter(v.clone()); + let r = reduce_multiview_result(input); + + //order in HashMap is random, the result shall be one of: + assert!(r == v[0].1 || r == v[1].1 || r == v[2].1); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn invalid_lengths() { + sp_tracing::try_init_simple(); + let v: Vec<(H256, Vec>)> = vec![ + (H256::repeat_byte(0x13), vec![Err(Error::Custom(12)), Err(Error::Custom(13))]), + (H256::repeat_byte(0x14), vec![Err(Error::Custom(23))]), + ]; + let input = HashMap::from_iter(v); + let _ = reduce_multiview_result(input); + } + + #[test] + fn only_hashes() { + sp_tracing::try_init_simple(); + + let v: Vec<(H256, Vec>)> = vec![ + ( + H256::repeat_byte(0x13), + vec![Ok(H256::repeat_byte(0x13)), Ok(H256::repeat_byte(0x14))], + ), + ( + H256::repeat_byte(0x14), + vec![Ok(H256::repeat_byte(0x13)), Ok(H256::repeat_byte(0x14))], + ), + ]; + let input = HashMap::from_iter(v); + let r = reduce_multiview_result(input); + + assert_eq!(r, vec![Ok(H256::repeat_byte(0x13)), Ok(H256::repeat_byte(0x14))]); + } + + #[test] + fn one_view() { + sp_tracing::try_init_simple(); + let v: Vec<(H256, Vec>)> = vec![( + H256::repeat_byte(0x13), + vec![Ok(H256::repeat_byte(0x10)), Err(Error::Custom(11))], + )]; + let input = HashMap::from_iter(v); + let r = reduce_multiview_result(input); + + assert_eq!(r, vec![Ok(H256::repeat_byte(0x10)), Err(Error::Custom(11))]); + } + + #[test] + fn mix() { + sp_tracing::try_init_simple(); + let v: Vec<(H256, Vec>)> = vec![ + ( + H256::repeat_byte(0x13), + vec![ + Ok(H256::repeat_byte(0x10)), + Err(Error::Custom(11)), + Err(Error::Custom(12)), + Err(Error::Custom(33)), + ], + ), + ( + H256::repeat_byte(0x14), + vec![ + Err(Error::Custom(20)), + Ok(H256::repeat_byte(0x21)), + Err(Error::Custom(22)), + Err(Error::Custom(33)), + ], + ), + ( + H256::repeat_byte(0x15), + vec![ + Err(Error::Custom(30)), + Err(Error::Custom(31)), + Ok(H256::repeat_byte(0x32)), + Err(Error::Custom(33)), + ], + ), + ]; + let input = HashMap::from_iter(v); + let r = reduce_multiview_result(input); + + assert_eq!( + r, + vec![ + Ok(H256::repeat_byte(0x10)), + Ok(H256::repeat_byte(0x21)), + Ok(H256::repeat_byte(0x32)), + Err(Error::Custom(33)) + ] + ); + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs new file mode 100644 index 00000000000..7fbdcade63b --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs @@ -0,0 +1,396 @@ +// 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 . + +//! Multi view import notification sink. This module provides a unified stream of transactions that +//! have been notified as ready by any of the active views maintained by the transaction pool. It +//! combines streams (`import_notification_stream`) from multiple views into a single stream. Events +//! coming from this stream are dynamically dispatched to many external watchers. + +use crate::{fork_aware_txpool::stream_map_util::next_event, LOG_TARGET}; +use futures::{ + channel::mpsc::{channel, Receiver as EventStream, Sender as ExternalSink}, + stream::StreamExt, + Future, FutureExt, +}; +use log::trace; +use parking_lot::RwLock; +use sc_utils::mpsc; +use std::{ + collections::HashSet, + fmt::{self, Debug, Formatter}, + hash::Hash, + pin::Pin, + sync::Arc, +}; +use tokio_stream::StreamMap; + +/// A type alias for a pinned, boxed stream of items of type `I`. +/// This alias is particularly useful for defining the types of the incoming streams from various +/// views, and is intended to build the stream of transaction hashes that become ready. +/// +/// Note: generic parameter allows better testing of all types involved. +type StreamOf = Pin + Send>>; + +/// A type alias for a tracing unbounded sender used as the command channel controller. +/// Used to send control commands to the [`AggregatedStreamContext`]. +type Controller = mpsc::TracingUnboundedSender; + +/// A type alias for a tracing unbounded receiver used as the command channel receiver. +/// Used to receive control commands in the [`AggregatedStreamContext`]. +type CommandReceiver = mpsc::TracingUnboundedReceiver; + +/// An enum representing commands that can be sent to the multi-sinks context. +/// +/// This enum contains variants that encapsulate control commands used to manage multiple streams +/// within the `AggregatedStreamContext`. +enum Command { + /// Adds a new view with a unique key and a stream of items of type `I`. + AddView(K, StreamOf), +} + +impl Debug for Command { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Command::AddView(..) => write!(f, "AddView"), + } + } +} + +/// A context used to unfold the single stream of items aggregated from the multiple +/// streams. +/// +/// The `AggregatedStreamContext` continuously monitors both the command receiver and the stream +/// map, ensuring new views can be dynamically added and events from any active view can be +/// processed. +struct AggregatedStreamContext { + /// A map of streams identified by unique keys, + stream_map: StreamMap>, + /// A receiver for handling control commands, such as adding new views. + command_receiver: CommandReceiver>, +} + +impl AggregatedStreamContext +where + K: Send + Debug + Unpin + Clone + Default + Hash + Eq + 'static, + I: Send + Sync + 'static + PartialEq + Eq + Hash + Clone + Debug, +{ + /// Creates a new aggregated stream of items and its command controller. + /// + /// This function sets up the initial context with an empty stream map. The aggregated output + /// stream of items (e.g. hashes of transactions that become ready) is unfolded. + /// + /// It returns a tuple containing the output stream and the command controller, allowing + /// external components to control this stream. + fn event_stream() -> (StreamOf, Controller>) { + let (sender, receiver) = + sc_utils::mpsc::tracing_unbounded::>("import-notification-sink", 16); + + let ctx = Self { stream_map: StreamMap::new(), command_receiver: receiver }; + + let output_stream = futures::stream::unfold(ctx, |mut ctx| async move { + loop { + tokio::select! { + biased; + cmd = ctx.command_receiver.next() => { + match cmd? { + Command::AddView(key,stream) => { + trace!(target: LOG_TARGET,"Command::AddView {key:?}"); + ctx.stream_map.insert(key,stream); + }, + } + }, + + Some(event) = next_event(&mut ctx.stream_map) => { + trace!(target: LOG_TARGET, "import_notification_sink: select_next_some -> {:?}", event); + return Some((event.1, ctx)); + } + } + } + }) + .boxed(); + + (output_stream, sender) + } +} + +/// A struct that facilitates the relaying notifications of ready transactions from multiple views +/// to many external sinks. +/// +/// `MultiViewImportNotificationSink` provides mechanisms to dynamically add new views, filter +/// notifications of imported transactions hashes and relay them to the multiple external sinks. +#[derive(Clone)] +pub struct MultiViewImportNotificationSink { + /// A controller used to send commands to the internal [`AggregatedStreamContext`]. + controller: Controller>, + /// A vector of the external sinks, each receiving a copy of the merged stream of ready + /// transaction hashes. + external_sinks: Arc>>>, + /// A set of already notified items, ensuring that each item (transaction hash) is only + /// sent out once. + already_notified_items: Arc>>, +} + +/// An asynchronous task responsible for dispatching aggregated import notifications to multiple +/// sinks (created by [`MultiViewImportNotificationSink::event_stream`]). +pub type ImportNotificationTask = Pin + Send>>; + +impl MultiViewImportNotificationSink +where + K: 'static + Clone + Send + Debug + Default + Unpin + Eq + Hash, + I: 'static + Clone + Send + Debug + Sync + PartialEq + Eq + Hash, +{ + /// Creates a new [`MultiViewImportNotificationSink`] along with its associated worker task. + /// + /// This function initializes the sink and provides the worker task that listens for events from + /// the aggregated stream, relaying them to the external sinks. The task shall be polled by + /// caller. + /// + /// Returns a tuple containing the [`MultiViewImportNotificationSink`] and the + /// [`ImportNotificationTask`]. + pub fn new_with_worker() -> (MultiViewImportNotificationSink, ImportNotificationTask) { + let (output_stream, controller) = AggregatedStreamContext::::event_stream(); + let output_stream_controller = Self { + controller, + external_sinks: Default::default(), + already_notified_items: Default::default(), + }; + let external_sinks = output_stream_controller.external_sinks.clone(); + let already_notified_items = output_stream_controller.already_notified_items.clone(); + + let import_notifcation_task = output_stream + .for_each(move |event| { + let external_sinks = external_sinks.clone(); + let already_notified_items = already_notified_items.clone(); + async move { + if already_notified_items.write().insert(event.clone()) { + external_sinks.write().retain_mut(|sink| { + trace!(target: LOG_TARGET, "[{:?}] import_sink_worker sending out imported", event); + if let Err(e) = sink.try_send(event.clone()) { + trace!(target: LOG_TARGET, "import_sink_worker sending message failed: {e}"); + false + } else { + true + } + }); + } + } + }) + .boxed(); + (output_stream_controller, import_notifcation_task) + } + + /// Adds a new stream associated with the view identified by specified key. + /// + /// The new view's stream is added to the internal aggregated stream context by sending command + /// to its `command_receiver`. + pub fn add_view(&self, key: K, view: StreamOf) { + let _ = self + .controller + .unbounded_send(Command::AddView(key.clone(), view)) + .map_err(|e| { + trace!(target: LOG_TARGET, "add_view {key:?} send message failed: {e}"); + }); + } + + /// Creates and returns a new external stream of ready transactions hashes notifications. + pub fn event_stream(&self) -> EventStream { + const CHANNEL_BUFFER_SIZE: usize = 1024; + let (sender, receiver) = channel(CHANNEL_BUFFER_SIZE); + self.external_sinks.write().push(sender); + receiver + } + + /// Removes specified items from the `already_notified_items` set. + /// + /// Intended to be called once transactions are finalized. + pub fn clean_notified_items(&self, items_to_be_removed: &[I]) { + let mut already_notified_items = self.already_notified_items.write(); + items_to_be_removed.iter().for_each(|i| { + already_notified_items.remove(i); + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + use tokio::task::JoinHandle; + + #[derive(Debug, Clone)] + struct Event { + delay: u64, + value: I, + } + + impl From<(u64, I)> for Event { + fn from(event: (u64, I)) -> Self { + Self { delay: event.0, value: event.1 } + } + } + + struct View { + scenario: Vec>, + sinks: Arc>>>, + } + + impl View { + fn new(scenario: Vec<(u64, I)>) -> Self { + Self { + scenario: scenario.into_iter().map(Into::into).collect(), + sinks: Default::default(), + } + } + + async fn event_stream(&self) -> EventStream { + let (sender, receiver) = channel(32); + self.sinks.write().push(sender); + receiver + } + + fn play(&mut self) -> JoinHandle<()> { + let mut scenario = self.scenario.clone(); + let sinks = self.sinks.clone(); + tokio::spawn(async move { + loop { + if scenario.is_empty() { + for sink in &mut *sinks.write() { + sink.close_channel(); + } + break; + }; + let x = scenario.remove(0); + tokio::time::sleep(Duration::from_millis(x.delay)).await; + for sink in &mut *sinks.write() { + sink.try_send(x.value.clone()).unwrap(); + } + } + }) + } + } + + #[tokio::test] + async fn deduplicating_works() { + sp_tracing::try_init_simple(); + + let (ctrl, runnable) = MultiViewImportNotificationSink::::new_with_worker(); + + let j0 = tokio::spawn(runnable); + + let stream = ctrl.event_stream(); + + let mut v1 = View::new(vec![(0, 1), (0, 2), (0, 3)]); + let mut v2 = View::new(vec![(0, 1), (0, 2), (0, 6)]); + let mut v3 = View::new(vec![(0, 1), (0, 2), (0, 3)]); + + let j1 = v1.play(); + let j2 = v2.play(); + let j3 = v3.play(); + + let o1 = v1.event_stream().await.boxed(); + let o2 = v2.event_stream().await.boxed(); + let o3 = v3.event_stream().await.boxed(); + + ctrl.add_view(1000, o1); + ctrl.add_view(2000, o2); + ctrl.add_view(3000, o3); + + let out = stream.take(4).collect::>().await; + assert!(out.iter().all(|v| vec![1, 2, 3, 6].contains(v))); + drop(ctrl); + + futures::future::join_all(vec![j0, j1, j2, j3]).await; + } + + #[tokio::test] + async fn dedup_filter_reset_works() { + sp_tracing::try_init_simple(); + + let (ctrl, runnable) = MultiViewImportNotificationSink::::new_with_worker(); + + let j0 = tokio::spawn(runnable); + + let stream = ctrl.event_stream(); + + let mut v1 = View::new(vec![(10, 1), (10, 2), (10, 3)]); + let mut v2 = View::new(vec![(20, 1), (20, 2), (20, 6)]); + let mut v3 = View::new(vec![(20, 1), (20, 2), (20, 3)]); + + let j1 = v1.play(); + let j2 = v2.play(); + let j3 = v3.play(); + + let o1 = v1.event_stream().await.boxed(); + let o2 = v2.event_stream().await.boxed(); + let o3 = v3.event_stream().await.boxed(); + + ctrl.add_view(1000, o1); + ctrl.add_view(2000, o2); + + let j4 = { + let ctrl = ctrl.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(70)).await; + ctrl.clean_notified_items(&vec![1, 3]); + ctrl.add_view(3000, o3.boxed()); + }) + }; + + let out = stream.take(6).collect::>().await; + assert_eq!(out, vec![1, 2, 3, 6, 1, 3]); + drop(ctrl); + + futures::future::join_all(vec![j0, j1, j2, j3, j4]).await; + } + + #[tokio::test] + async fn many_output_streams_are_supported() { + sp_tracing::try_init_simple(); + + let (ctrl, runnable) = MultiViewImportNotificationSink::::new_with_worker(); + + let j0 = tokio::spawn(runnable); + + let stream0 = ctrl.event_stream(); + let stream1 = ctrl.event_stream(); + + let mut v1 = View::new(vec![(0, 1), (0, 2), (0, 3)]); + let mut v2 = View::new(vec![(0, 1), (0, 2), (0, 6)]); + let mut v3 = View::new(vec![(0, 1), (0, 2), (0, 3)]); + + let j1 = v1.play(); + let j2 = v2.play(); + let j3 = v3.play(); + + let o1 = v1.event_stream().await.boxed(); + let o2 = v2.event_stream().await.boxed(); + let o3 = v3.event_stream().await.boxed(); + + ctrl.add_view(1000, o1); + ctrl.add_view(2000, o2); + ctrl.add_view(3000, o3); + + let out0 = stream0.take(4).collect::>().await; + let out1 = stream1.take(4).collect::>().await; + assert!(out0.iter().all(|v| vec![1, 2, 3, 6].contains(v))); + assert!(out1.iter().all(|v| vec![1, 2, 3, 6].contains(v))); + drop(ctrl); + + futures::future::join_all(vec![j0, j1, j2, j3]).await; + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs new file mode 100644 index 00000000000..73d45ac4305 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/metrics.rs @@ -0,0 +1,176 @@ +// 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 . + +//! Prometheus's metrics for a fork-aware transaction pool. + +use crate::common::metrics::{GenericMetricsLink, MetricsRegistrant}; +use prometheus_endpoint::{ + histogram_opts, linear_buckets, register, Counter, Gauge, Histogram, PrometheusError, Registry, + U64, +}; + +/// A helper alias for the Prometheus's metrics endpoint. +pub type MetricsLink = GenericMetricsLink; + +/// Transaction pool Prometheus metrics. +pub struct Metrics { + /// Total number of transactions submitted. + pub submitted_transactions: Counter, + /// Total number of currently maintained views. + pub active_views: Gauge, + /// Total number of current inactive views. + pub inactive_views: Gauge, + /// Total number of watched transactions in txpool. + pub watched_txs: Gauge, + /// Total number of unwatched transactions in txpool. + pub unwatched_txs: Gauge, + /// Total number of transactions reported as invalid. + pub removed_invalid_txs: Counter, + /// Total number of finalized transactions. + pub finalized_txs: Counter, + /// Histogram of maintain durations. + pub maintain_duration: Histogram, + /// Total number of transactions resubmitted from retracted forks. + pub resubmitted_retracted_txs: Counter, + /// Total number of transactions submitted from mempool to views. + pub submitted_from_mempool_txs: Counter, + /// Total number of transactions found as invalid during mempool revalidation. + pub mempool_revalidation_invalid_txs: Counter, + /// Total number of transactions found as invalid during view revalidation. + pub view_revalidation_invalid_txs: Counter, + /// Total number of valid transactions processed during view revalidation. + pub view_revalidation_resubmitted_txs: Counter, + /// Histogram of view revalidation durations. + pub view_revalidation_duration: Histogram, + /// Total number of the views created w/o cloning existing view. + pub non_cloned_views: Counter, +} + +impl MetricsRegistrant for Metrics { + fn register(registry: &Registry) -> Result, PrometheusError> { + Ok(Box::from(Self { + submitted_transactions: register( + Counter::new( + "substrate_sub_txpool_submitted_txs_total", + "Total number of transactions submitted", + )?, + registry, + )?, + active_views: register( + Gauge::new( + "substrate_sub_txpool_active_views", + "Total number of currently maintained views.", + )?, + registry, + )?, + inactive_views: register( + Gauge::new( + "substrate_sub_txpool_inactive_views", + "Total number of current inactive views.", + )?, + registry, + )?, + watched_txs: register( + Gauge::new( + "substrate_sub_txpool_watched_txs", + "Total number of watched transactions in txpool.", + )?, + registry, + )?, + unwatched_txs: register( + Gauge::new( + "substrate_sub_txpool_unwatched_txs", + "Total number of unwatched transactions in txpool.", + )?, + registry, + )?, + removed_invalid_txs: register( + Counter::new( + "substrate_sub_txpool_removed_invalid_txs_total", + "Total number of transactions reported as invalid.", + )?, + registry, + )?, + finalized_txs: register( + Counter::new( + "substrate_sub_txpool_finalized_txs_total", + "Total number of finalized transactions.", + )?, + registry, + )?, + maintain_duration: register( + Histogram::with_opts(histogram_opts!( + "substrate_sub_txpool_maintain_duration_seconds", + "Histogram of maintain durations.", + linear_buckets(0.0, 0.25, 13).unwrap() + ))?, + registry, + )?, + resubmitted_retracted_txs: register( + Counter::new( + "substrate_sub_txpool_resubmitted_retracted_txs_total", + "Total number of transactions resubmitted from retracted forks.", + )?, + registry, + )?, + submitted_from_mempool_txs: register( + Counter::new( + "substrate_sub_txpool_submitted_from_mempool_txs_total", + "Total number of transactions submitted from mempool to views.", + )?, + registry, + )?, + mempool_revalidation_invalid_txs: register( + Counter::new( + "substrate_sub_txpool_mempool_revalidation_invalid_txs_total", + "Total number of transactions found as invalid during mempool revalidation.", + )?, + registry, + )?, + view_revalidation_invalid_txs: register( + Counter::new( + "substrate_sub_txpool_view_revalidation_invalid_txs_total", + "Total number of transactions found as invalid during view revalidation.", + )?, + registry, + )?, + view_revalidation_resubmitted_txs: register( + Counter::new( + "substrate_sub_txpool_view_revalidation_resubmitted_txs_total", + "Total number of valid transactions processed during view revalidation.", + )?, + registry, + )?, + view_revalidation_duration: register( + Histogram::with_opts(histogram_opts!( + "substrate_sub_txpool_view_revalidation_duration_seconds", + "Histogram of view revalidation durations.", + linear_buckets(0.0, 0.25, 13).unwrap() + ))?, + registry, + )?, + non_cloned_views: register( + Counter::new( + "substrate_sub_txpool_non_cloned_views_total", + "Total number of the views created w/o cloning existing view.", + )?, + registry, + )?, + })) + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs new file mode 100644 index 00000000000..9f979e216b6 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/mod.rs @@ -0,0 +1,376 @@ +// 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 . + +//! Substrate fork aware transaction pool implementation. +//! +//! # Top level overview. +//! This documentation provides high level overview of the main structures and the main flows within +//! the fork-aware transaction pool. +//! +//! ## Structures. +//! ### View. +//! #### Purpose. +//! The main responsibility of the [`View`] is to provide the valid set of ready transactions at +//! the given block. [`ForkAwareTxPool`] keeps the number of recent views for all the blocks +//! notified since recently finalized block. +//! +//! The views associated with blocks at the tips of the forks are actively updated with all newly +//! incoming transactions, while intermediate views are not updated (they still provide transactions +//! ready to be included at that block) due to performance reasons, since every transaction +//! submitted to the view needs to be [validated][runtime_api::validate]. +//! Building upon the older blocks happens relatively rare so this does not affect blocks filling. +//! +//! The view is wrapper around [`Pool`] and exposes its functionality, including the ability +//! of [tracking][`Watcher`] the progress of every transaction. +//! +//! #### Views: active, inactive. +//! All the views are stored in [`ViewStore`] structure. In this documentation the views at the tips +//! of the forks are referred as [`active_views`], while the intermediate views as +//! [`inactive_views`]. +//! +//! +//! #### The life cycle of the [`View`]. +//! Views are created when the new [`ChainEvent`] is notified to the pool. The view that is +//! [closest][find_best_view] to the newly notified block is chosen to clone from. Once built and +//! updated the newly created view is placed in [`active_views`]. Detailed description of view +//! creation is described in [the material to follow](#handling-the-new-best-block). When the view +//! is no longer at the tip of the forks, it is moved to the [`inactive_views`]. When the block +//! number of the view is lower then the finalized block, the view is permanently removed. +//! +//! +//! *Example*: +//! The following chain: +//! ```text +//! C2 - C3 - C4 +//! / +//! B1 +//! \ +//! B2 - B3 - B4 +//! ``` +//! and the following set of events: +//! ```text +//! New best block: B1, C3, C4, B4 +//! ``` +//! will result in the following set of views within the [`ViewStore`]: +//! ```text +//! active: C4, B4 +//! inactive: B1, C3 +//! ``` +//! Please note that views are only created for the notified blocks. +//! +//! +//! ### View store. +//! [`ViewStore`] is the helper structure that provides means to perform some actions like +//! [`submit`] or [`submit_and_watch`] on every view. It keeps track of both active and inactive +//! views. +//! +//! It also keeps tracks of the `most_recent_view` which is used to implement some methods of +//! [TransactionPool API], see [API considerations](#api-considerations) section. +//! +//! ### Multi-view listeners +//! There is a number of event streams that are provided by individual views: +//! - [transaction status][`Watcher`], +//! - [ready notification][`vp::import_notification_stream`] (see [networking +//! section](#networking)), +//! - [dropped notification][`create_dropped_by_limits_stream`]. +//! +//! These streams need to be merged into a single stream exposed by transaction pool (or used +//! internally). Those aggregators are often referred as multi-view listeners and they implement +//! stream-specific or event-specific logic. +//! +//! The most important is [`MultiViewListener`] which is owned by view store. +//! More information about it is provided in [transaction +//! route](#transaction-route-submit_and_watch) section. +//! +//! +//! ### Intermediate transactions buffer: [`TxMemPool`] +//! The main purpose of an internal [`TxMemPool`] (referred to as *mempool*) is to prevent a +//! transaction from being lost, e.g. due to race condition when the new transaction submission +//! occurs just before the new view is created. This could also happen when a transaction is invalid +//! on one fork and could be valid on another which is not yet fully processed by the maintain +//! procedure. Additionally, it allows the pool to accept transactions when no blocks have been +//! reported yet. +//! +//! Since watched and non-watched transactions require a different treatment, the *mempool* keeps a +//! track on how the transaction was submitted. The [transaction source][`TransactionSource`] used +//! to submit transactions also needs to be kept in the *mempool*. The *mempool* transaction is a +//! simple [wrapper][`TxInMemPool`] around the [`Arc`] reference to the actual extrinsic body. +//! +//! Once the view is created, all transactions from *mempool* are submitted to and validated at this +//! view. +//! +//! The *mempool* removes its transactions when they get finalized. The transactions in *mempool* +//! are also periodically verified at every finalized block and removed from the *mempool* if no +//! longer valid. This is process is called [*mempool* revalidation](#mempool-pruningrevalidation). +//! +//! ## Flows +//! +//! The transaction pool internally is executing numerous tasks. This includes handling submitted +//! transactions and tracking their progress, listening to [`ChainEvent`]s and executing the +//! maintain process, which aims to provide the set of ready transactions. On the other side +//! transaction pool provides a [`ready_at`] future that resolves to the iterator of ready +//! transactions. On top of that pool performs background revalidation jobs. +//! +//! This section provides a top level overview of all flows within the fork aware transaction pool. +//! +//! ### Transaction route: [`submit`][`api_submit`] +//! This flow is simple. Transaction is added to the mempool and if it is not rejected by it (due to +//! size limits), it is also [submitted][`submit`] into every view in [`active_views`]. +//! +//! When the newly created view does not contain this transaction yet, it is +//! [re-submitted][ForkAwareTxPool::update_view_with_mempool] from [`TxMemPool`] into this view. +//! +//! ### Transaction route: [`submit_and_watch`][`api_submit_and_watch`] +//! +//! The [`submit_and_watch`] function allows to submit the transaction and track its +//! [status][`TransactionStatus`] within the pool. Every view is providing an independent +//! [stream][`View::submit_and_watch`] of events, which needs to be merged into the single stream +//! exposed to the [external listener][`TransactionStatusStreamFor`]. For majority of events simple +//! forwarding of events would not work (e.g. we could get multiple [`Ready`] events, or [`Ready`] / +//! [`Future`] mix). Some additional stateful logic is required to filter and process the views' +//! events. It is also easier to trigger some events (e.g. [`Finalized`], [`Invalid`], and +//! [`Broadcast`]) using some side-channel and simply ignoring these events from the view. All the +//! before mentioned functionality is provided by the [`MultiViewListener`]. +//! +//! When watched transaction is submitted to the pool it is added the *mempool* with watched +//! flag. The external stream for the transaction is created in a [`MultiViewListener`]. Then +//! transaction is submitted to every active [`View`] (using +//! [`submit_and_watch`][`View::submit_and_watch`]) and the resulting +//! views' stream is connected to the [`MultiViewListener`]. +//! +//! ### Maintain +//! The transaction pool exposes the [task][`notification_future`] that listens to the +//! finalized and best block streams and executes the [`maintain`] procedure. +//! +//! The [`maintain`] is the main procedure of the transaction pool. It handles incoming +//! [`ChainEvent`]s, as described in the following two sub-sections. +//! +//! #### Handling the new (best) block +//! If the new block actually needs to be handled, the following steps are +//! executed: +//! - [find][find_best_view] the best view and clone it to [create a new +//! view][crate::ForkAwareTxPool::build_new_view], +//! - [update the view][ForkAwareTxPool::update_view_with_mempool] with the transactions from the +//! *mempool* +//! - all transactions from the *mempool* (with some obvious filtering applied) are submitted to +//! the view, +//! - for all watched transactions from the *mempool* the watcher is registered in the new view, +//! and it is connected to the multi-view-listener, +//! - [update the view][ForkAwareTxPool::update_view_with_fork] with the transactions from the [tree +//! route][`TreeRoute`] (which is computed from the recent best block to newly notified one by +//! [enactment state][`EnactmentState`] helper): +//! - resubmit the transactions from the retracted blocks, +//! - prune extrinsic from the enacted blocks, and trigger [`InBlock`] events, +//! - insert the newly created and updated view into the view store. +//! +//! +//! #### Handling the finalized block +//! The following actions are taken on every finalized block: +//! - send [`Finalized`] events for every transactions on the finalized [tree route][`TreeRoute`], +//! - remove all the views (both active and inactive) that are lower then finalized block from the +//! view store, +//! - removal of finalized transaction from the *mempool*, +//! - trigger [*mempool* background revalidation](#mempool-pruningrevalidation). +//! - clean up of multi-view listeners which is required to avoid ever-growing structures, +//! +//! ### Light maintain +//! The [maintain](#maintain) procedure can sometimes be quite heavy, and it may not be accomplished +//! within the time window expected by the block builder. On top of that block builder may want to +//! build few blocks in the raw, not giving the pool enough time to accomplish possible ongoing +//! maintain process. +//! +//! To address this, there is a [light version][`ready_at_light`] of the maintain procedure. It +//! [finds the best view][find_best_view], clones it and prunes all the transactions that were +//! included in enacted part of [tree route][`TreeRoute`] from the base view to the block at which a +//! ready iterator was requested. No new [transaction validations][runtime_api::validate] are +//! required to accomplish it. +//! +//! ### Providing ready transactions: `ready_at` +//! The [`ready_at`] function returns a [future][`crate::PolledIterator`] that resolves to the +//! [ready transactions iterator][`ReadyTransactions`]. The block builder shall wait either for the +//! future to be resolved or for timeout to be hit. To avoid building empty blocks in case of +//! timeout, the waiting for timeout functionality was moved into the transaction pool, and new API +//! function was added: [`ready_at_with_timeout`]. This function also provides a fall back ready +//! iterator which is result of [light maintain](#light-maintain). +//! +//! New function internally waits either for [maintain](#maintain) process triggered for requested +//! block to be accomplished or for the timeout. If timeout hits then the result of [light +//! maintain](#light-maintain) is returned. Light maintain is always executed at the beginning of +//! [`ready_at_with_timeout`] to make sure that it is available w/ o additional delay. +//! +//! If the maintain process for the requested block was accomplished before the `ready_at` functions +//! are called both of them immediately provide the ready transactions iterator (which is simply +//! requested on the appropriate instance of the [`View`]). +//! +//! The little [`ReadyPoll`] helper contained within [`ForkAwareTxPool`] as ([`ready_poll`]) +//! implements the futures management. +//! +//! ### Background tasks +//! The [maintain](#maintain) procedure shall be as quick as possible, so heavy revalidation job is +//! delegated to the background worker. These includes view and *mempool* revalidation which are +//! both handled by the [`RevalidationQueue`] which simply sends revalidation requests to the +//! background thread. +//! +//! #### View revalidation +//! View revalidation is performed in the background thread. Revalidation is executed for every +//! view. All the transaction from the view are [revalidated][`view::revalidate`]. +//! +//! The fork-aware pool utilizes two threads to execute maintain and revalidation process +//! exclusively, ensuring maintain performance without overlapping with revalidation. +//! +//! The view revalidation process is [triggered][`start_background_revalidation`] at the very end of +//! the [maintain][`maintain`] process, and [stopped][`finish_background_revalidations`] at the +//! very beginning of the next maintenance execution (upon the next [`ChainEvent`] reception). The +//! results from the revalidation are immediately applied once the revalidation is +//! [terminated][crate::fork_aware_txpool::view::View::finish_revalidation]. +//! ```text +//! time: ----------------------> +//! maintenance thread: M----M------M--M-M--- +//! revalidation thread: -RRRR-RR-----RR-R-RRR +//! ``` +//! +//! #### Mempool pruning/revalidation +//! Transactions within *mempool* are constantly revalidated in the background. The +//! [revalidation][`mp::revalidate`] is performed in [batches][`batch_size`], and transactions that +//! were validated as latest, are revalidated first in the next iteration. The revalidation is +//! triggered on every finalized block. If a transaction is found to be invalid, the [`Invalid`] +//! event is sent and transaction is removed from the *mempool*. +//! +//! NOTE: There is one exception: if transaction is referenced by any view as ready, then it is +//! removed from the *mempool*, but not removed from the view. The [`Invalid`] event is not sent. +//! This case is not likely to happen, however it may need some extra attention. +//! +//! ### Networking +//! The pool is exposing [`ImportNotificationStream`][`import_notification_stream`], the dedicated +//! channel over which all ready transactions are notified. Internally this channel needs to merge +//! all ready events from every view. This functionality is implemented by +//! [`MultiViewImportNotificationSink`]. +//! +//! The networking module is utilizing this channel to receive info about new ready transactions +//! which later will be propagated over the network. On the other side, when a transaction is +//! received networking submits transaction to the pool using [`submit`][`api_submit`]. +//! +//! ### Handling invalid transactions +//! Refer to *mempool* revalidation [section](#mempool-pruningrevalidation). +//! +//! ## Pool limits +//! Every [`View`] has the [limits][`Options`] for the number or size of transactions it can hold. +//! Obviously the number of transactions in every view is not distributed equally, so some views +//! might be fully filled while others not. +//! +//! On the other hand the size of internal *mempool* shall also be capped, but transactions that are +//! still referenced by views should not be removed. +//! +//! When the [`View`] is at its limits, it can either reject the transaction during +//! submission process, or it can accept the transaction and drop different transaction which is +//! already in the pool during the [`enforce_limits`][`vp::enforce_limits`] process. +//! +//! The [`StreamOfDropped`] stream aggregating [per-view][`create_dropped_by_limits_stream`] streams +//! allows to monitor the transactions that were dropped by all the views (or dropped by some views +//! while not referenced by the others), what means that transaction can also be +//! [removed][`dropped_monitor_task`] from the *mempool*. +//! +//! +//! ## API Considerations +//! Refer to github issue: +//! +//! [`View`]: crate::fork_aware_txpool::view::View +//! [`view::revalidate`]: crate::fork_aware_txpool::view::View::revalidate +//! [`start_background_revalidation`]: crate::fork_aware_txpool::view::View::start_background_revalidation +//! [`View::submit_and_watch`]: crate::fork_aware_txpool::view::View::submit_and_watch +//! [`ViewStore`]: crate::fork_aware_txpool::view_store::ViewStore +//! [`finish_background_revalidations`]: crate::fork_aware_txpool::view_store::ViewStore::finish_background_revalidations +//! [find_best_view]: crate::fork_aware_txpool::view_store::ViewStore::find_best_view +//! [`active_views`]: crate::fork_aware_txpool::view_store::ViewStore::active_views +//! [`inactive_views`]: crate::fork_aware_txpool::view_store::ViewStore::inactive_views +//! [`TxMemPool`]: crate::fork_aware_txpool::tx_mem_pool::TxMemPool +//! [`mp::revalidate`]: crate::fork_aware_txpool::tx_mem_pool::TxMemPool::revalidate +//! [`batch_size`]: crate::fork_aware_txpool::tx_mem_pool::TXMEMPOOL_MAX_REVALIDATION_BATCH_SIZE +//! [`TxInMemPool`]: crate::fork_aware_txpool::tx_mem_pool::TxInMemPool +//! [`MultiViewListener`]: crate::fork_aware_txpool::multi_view_listener::MultiViewListener +//! [`Pool`]: crate::graph::Pool +//! [`Watcher`]: crate::graph::watcher::Watcher +//! [`Options`]: crate::graph::Options +//! [`vp::import_notification_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.import_notification_stream +//! [`vp::enforce_limits`]: ../graph/validated_pool/struct.ValidatedPool.html#method.enforce_limits +//! [`create_dropped_by_limits_stream`]: ../graph/validated_pool/struct.ValidatedPool.html#method.create_dropped_by_limits_stream +//! [`ChainEvent`]: sc_transaction_pool_api::ChainEvent +//! [`TransactionStatusStreamFor`]: sc_transaction_pool_api::TransactionStatusStreamFor +//! [`api_submit`]: sc_transaction_pool_api::TransactionPool::submit_at +//! [`api_submit_and_watch`]: sc_transaction_pool_api::TransactionPool::submit_and_watch +//! [`ready_at_with_timeout`]: sc_transaction_pool_api::TransactionPool::ready_at_with_timeout +//! [`TransactionSource`]: sc_transaction_pool_api::TransactionSource +//! [TransactionPool API]: sc_transaction_pool_api::TransactionPool +//! [`TransactionStatus`]:sc_transaction_pool_api::TransactionStatus +//! [`Ready`]:sc_transaction_pool_api::TransactionStatus::Ready +//! [`Future`]:sc_transaction_pool_api::TransactionStatus::Future +//! [`Broadcast`]:sc_transaction_pool_api::TransactionStatus::Broadcast +//! [`Invalid`]:sc_transaction_pool_api::TransactionStatus::Invalid +//! [`InBlock`]:sc_transaction_pool_api::TransactionStatus::InBlock +//! [`Finalized`]:sc_transaction_pool_api::TransactionStatus::Finalized +//! [`ReadyTransactions`]:sc_transaction_pool_api::ReadyTransactions +//! [`dropped_monitor_task`]: ForkAwareTxPool::dropped_monitor_task +//! [`ready_poll`]: ForkAwareTxPool::ready_poll +//! [`ready_at_light`]: ForkAwareTxPool::ready_at_light +//! [`ready_at`]: ../struct.ForkAwareTxPool.html#method.ready_at +//! [`import_notification_stream`]: ../struct.ForkAwareTxPool.html#method.import_notification_stream +//! [`maintain`]: ../struct.ForkAwareTxPool.html#method.maintain +//! [`submit`]: ../struct.ForkAwareTxPool.html#method.submit_at +//! [`submit_and_watch`]: ../struct.ForkAwareTxPool.html#method.submit_and_watch +//! [`ReadyPoll`]: ../fork_aware_txpool/fork_aware_txpool/struct.ReadyPoll.html +//! [`TreeRoute`]: sp_blockchain::TreeRoute +//! [runtime_api::validate]: sp_transaction_pool::runtime_api::TaggedTransactionQueue::validate_transaction +//! [`notification_future`]: crate::common::notification_future +//! [`EnactmentState`]: crate::common::enactment_state::EnactmentState +//! [`MultiViewImportNotificationSink`]: crate::fork_aware_txpool::import_notification_sink::MultiViewImportNotificationSink +//! [`RevalidationQueue`]: crate::fork_aware_txpool::revalidation_worker::RevalidationQueue +//! [`StreamOfDropped`]: crate::fork_aware_txpool::dropped_watcher::StreamOfDropped +//! [`Arc`]: std::sync::Arc + +mod dropped_watcher; +pub(crate) mod fork_aware_txpool; +mod import_notification_sink; +mod metrics; +mod multi_view_listener; +mod revalidation_worker; +mod tx_mem_pool; +mod view; +mod view_store; + +pub use fork_aware_txpool::{ForkAwareTxPool, ForkAwareTxPoolTask}; + +mod stream_map_util { + use futures::Stream; + use std::marker::Unpin; + use tokio_stream::StreamMap; + + pub async fn next_event( + stream_map: &mut StreamMap, + ) -> Option<(K, ::Item)> + where + K: Clone + Unpin, + V: Stream + Unpin, + { + if stream_map.is_empty() { + // yield pending to prevent busy-loop on an empty map + futures::pending!() + } + + futures::StreamExt::next(stream_map).await + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs new file mode 100644 index 00000000000..8d0e69db2e9 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -0,0 +1,736 @@ +// 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 . + +//! `MultiViewListener` and `ExternalWatcherContext` manage view streams and status updates for +//! transactions, providing control commands to manage transaction states, and create external +//! aggregated streams of transaction events. + +use crate::{ + fork_aware_txpool::stream_map_util::next_event, + graph::{self, BlockHash, ExtrinsicHash}, + LOG_TARGET, +}; +use futures::StreamExt; +use log::{debug, trace}; +use sc_transaction_pool_api::{TransactionStatus, TransactionStatusStream, TxIndex}; +use sc_utils::mpsc; +use sp_runtime::traits::Block as BlockT; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + pin::Pin, +}; +use tokio_stream::StreamMap; + +/// A side channel allowing to control the external stream instance (one per transaction) with +/// [`ControllerCommand`]. +/// +/// Set of instances of [`Controller`] lives within the [`MultiViewListener`]. +type Controller = mpsc::TracingUnboundedSender; + +/// A receiver of [`ControllerCommand`] instances allowing to control the external stream. +/// +/// Lives within the [`ExternalWatcherContext`] instance. +type CommandReceiver = mpsc::TracingUnboundedReceiver; + +/// The stream of the transaction events. +/// +/// It can represent both a single view's stream and an external watcher stream. +pub type TxStatusStream = Pin, BlockHash>>>; + +/// Commands to control the single external stream living within the multi view listener. +enum ControllerCommand { + /// Adds a new stream of transaction statuses originating in the view associated with a + /// specific block hash. + AddViewStream(BlockHash, TxStatusStream), + + /// Removes an existing view's stream associated with a specific block hash. + RemoveViewStream(BlockHash), + + /// Marks a transaction as invalidated. + /// + /// If all pre-conditions are met, an external invalid event will be sent out. + TransactionInvalidated, + + /// Notifies that a transaction was finalized in a specific block hash and transaction index. + /// + /// Send out an external finalized event. + FinalizeTransaction(BlockHash, TxIndex), + + /// Notifies that a transaction was broadcasted with a list of peer addresses. + /// + /// Sends out an external broadcasted event. + TransactionBroadcasted(Vec), + + /// Notifies that a transaction was dropped from the pool. + /// + /// If all preconditions are met, an external dropped event will be sent out. + TransactionDropped, +} + +impl std::fmt::Debug for ControllerCommand +where + ChainApi: graph::ChainApi, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ControllerCommand::AddViewStream(h, _) => write!(f, "ListenerAction::AddView({h})"), + ControllerCommand::RemoveViewStream(h) => write!(f, "ListenerAction::RemoveView({h})"), + ControllerCommand::TransactionInvalidated => { + write!(f, "ListenerAction::TransactionInvalidated") + }, + ControllerCommand::FinalizeTransaction(h, i) => { + write!(f, "ListenerAction::FinalizeTransaction({h},{i})") + }, + ControllerCommand::TransactionBroadcasted(_) => { + write!(f, "ListenerAction::TransactionBroadcasted(...)") + }, + ControllerCommand::TransactionDropped => { + write!(f, "ListenerAction::TransactionDropped") + }, + } + } +} + +/// This struct allows to create and control listener for multiple transactions. +/// +/// For every transaction the view's stream generating its own events can be added. The events are +/// flattened and sent out to the external listener. (The *external* term here means that it can be +/// exposed to [`sc_transaction_pool_api::TransactionPool`] API client e.g. over RPC.) +/// +/// The listener allows to add and remove view's stream (per transaction). +/// +/// The listener provides a side channel that allows triggering specific events (finalized, dropped, +/// invalid) independently of the view's stream. +pub struct MultiViewListener { + /// Provides the set of controllers for the events streams corresponding to individual + /// transactions identified by transaction hashes. + controllers: parking_lot::RwLock< + HashMap, Controller>>, + >, +} + +/// The external stream unfolding context. +/// +/// This context is used to unfold the external events stream for a single transaction, it +/// facilitates the logic of converting single view's events to the external events stream. +struct ExternalWatcherContext { + /// The hash of the transaction being monitored within this context. + tx_hash: ExtrinsicHash, + /// A stream map of transaction status streams coming from individual views, keyed by + /// block hash associated with view. + status_stream_map: StreamMap, TxStatusStream>, + /// A receiver for controller commands. + command_receiver: CommandReceiver>, + /// A flag indicating whether the context should terminate. + terminate: bool, + /// A flag indicating if a `Future` status has been encountered. + future_seen: bool, + /// A flag indicating if a `Ready` status has been encountered. + ready_seen: bool, + + /// A hash set of block hashes from views that consider the transaction valid. + views_keeping_tx_valid: HashSet>, +} + +impl ExternalWatcherContext +where + <::Block as BlockT>::Hash: Unpin, +{ + /// Creates new `ExternalWatcherContext` for particular transaction identified by `tx_hash` + /// + /// The `command_receiver` is a side channel for receiving controller's commands. + fn new( + tx_hash: ExtrinsicHash, + command_receiver: CommandReceiver>, + ) -> Self { + Self { + tx_hash, + status_stream_map: StreamMap::new(), + command_receiver, + terminate: false, + future_seen: false, + ready_seen: false, + views_keeping_tx_valid: Default::default(), + } + } + + /// Handles various transaction status updates and manages internal states based on the status. + /// + /// Function may set the context termination flag, which will close the stream. + /// + /// Returns `Some` with the `event` to forward or `None`. + fn handle( + &mut self, + status: TransactionStatus, BlockHash>, + hash: BlockHash, + ) -> Option, BlockHash>> { + trace!( + target: LOG_TARGET, "[{:?}] mvl handle event from {hash:?}: {status:?} views:{:?}", self.tx_hash, + self.status_stream_map.keys().collect::>() + ); + match status { + TransactionStatus::Future => { + self.views_keeping_tx_valid.insert(hash); + if self.ready_seen || self.future_seen { + None + } else { + self.future_seen = true; + Some(status) + } + }, + TransactionStatus::Ready => { + self.views_keeping_tx_valid.insert(hash); + if self.ready_seen { + None + } else { + self.ready_seen = true; + Some(status) + } + }, + TransactionStatus::Broadcast(_) => None, + TransactionStatus::InBlock((..)) => { + self.views_keeping_tx_valid.insert(hash); + if !(self.ready_seen || self.future_seen) { + self.ready_seen = true; + Some(status) + } else { + Some(status) + } + }, + TransactionStatus::Retracted(_) => None, + TransactionStatus::FinalityTimeout(_) => Some(status), + TransactionStatus::Finalized(_) => { + self.terminate = true; + Some(status) + }, + TransactionStatus::Usurped(_) | + TransactionStatus::Dropped | + TransactionStatus::Invalid => None, + } + } + + /// Handles transaction invalidation sent via side channel. + /// + /// Function may set the context termination flag, which will close the stream. + /// + /// Returns true if the event should be sent out, and false if the invalidation request should + /// be skipped. + fn handle_invalidate_transaction(&mut self) -> bool { + let keys = HashSet::>::from_iter( + self.status_stream_map.keys().map(Clone::clone), + ); + trace!( + target: LOG_TARGET, + "[{:?}] got invalidate_transaction: views:{:?}", self.tx_hash, + self.status_stream_map.keys().collect::>() + ); + if self.views_keeping_tx_valid.is_disjoint(&keys) { + self.terminate = true; + true + } else { + //todo [#5477] + // - handle corner case: this may happen when tx is invalid for mempool, but somehow + // some view still sees it as ready/future. In that case we don't send the invalid + // event, as transaction can still be included. Probably we should set some flag here + // and allow for invalid sent from the view. + // - add debug / metrics, + false + } + } + + /// Adds a new transaction status stream. + /// + /// Inserts a new view's transaction status stream associated with a specific block hash into + /// the stream map. + fn add_stream(&mut self, block_hash: BlockHash, stream: TxStatusStream) { + self.status_stream_map.insert(block_hash, stream); + trace!(target: LOG_TARGET, "[{:?}] AddView view: {:?} views:{:?}", self.tx_hash, block_hash, self.status_stream_map.keys().collect::>()); + } + + /// Removes an existing transaction status stream. + /// + /// Removes a transaction status stream associated with a specific block hash from the + /// stream map. + fn remove_view(&mut self, block_hash: BlockHash) { + self.status_stream_map.remove(&block_hash); + trace!(target: LOG_TARGET, "[{:?}] RemoveView view: {:?} views:{:?}", self.tx_hash, block_hash, self.status_stream_map.keys().collect::>()); + } +} + +impl MultiViewListener +where + ChainApi: graph::ChainApi + 'static, + <::Block as BlockT>::Hash: Unpin, +{ + /// Creates new instance of `MultiViewListener`. + pub fn new() -> Self { + Self { controllers: Default::default() } + } + + /// Creates an external aggregated stream of events for given transaction. + /// + /// This method initializes an `ExternalWatcherContext` for the provided transaction hash, sets + /// up the necessary communication channels, and unfolds an external (meaning that it can be + /// exposed to [`sc_transaction_pool_api::TransactionPool`] API client e.g. rpc) stream of + /// transaction status events. If an external watcher is already present for the given + /// transaction, it returns `None`. + pub(crate) fn create_external_watcher_for_tx( + &self, + tx_hash: ExtrinsicHash, + ) -> Option> { + let mut controllers = self.controllers.write(); + if controllers.contains_key(&tx_hash) { + return None + } + + trace!(target: LOG_TARGET, "[{:?}] create_external_watcher_for_tx", tx_hash); + + let (tx, rx) = mpsc::tracing_unbounded("txpool-multi-view-listener", 32); + controllers.insert(tx_hash, tx); + + let ctx = ExternalWatcherContext::new(tx_hash, rx); + + Some( + futures::stream::unfold(ctx, |mut ctx| async move { + if ctx.terminate { + return None + } + loop { + tokio::select! { + biased; + Some((view_hash, status)) = next_event(&mut ctx.status_stream_map) => { + if let Some(new_status) = ctx.handle(status, view_hash) { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: {new_status:?}", ctx.tx_hash); + return Some((new_status, ctx)) + } + }, + cmd = ctx.command_receiver.next() => { + log::trace!(target: LOG_TARGET, "[{:?}] select::rx views:{:?}", + ctx.tx_hash, + ctx.status_stream_map.keys().collect::>() + ); + match cmd? { + ControllerCommand::AddViewStream(h,stream) => { + ctx.add_stream(h, stream); + }, + ControllerCommand::RemoveViewStream(h) => { + ctx.remove_view(h); + }, + ControllerCommand::TransactionInvalidated => { + if ctx.handle_invalidate_transaction() { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Invalid", ctx.tx_hash); + return Some((TransactionStatus::Invalid, ctx)) + } + }, + ControllerCommand::FinalizeTransaction(block, index) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Finalized", ctx.tx_hash); + ctx.terminate = true; + return Some((TransactionStatus::Finalized((block, index)), ctx)) + }, + ControllerCommand::TransactionBroadcasted(peers) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Broadcasted", ctx.tx_hash); + return Some((TransactionStatus::Broadcast(peers), ctx)) + }, + ControllerCommand::TransactionDropped => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Dropped", ctx.tx_hash); + ctx.terminate = true; + return Some((TransactionStatus::Dropped, ctx)) + }, + } + }, + }; + } + }) + .boxed(), + ) + } + + /// Adds a view's transaction status stream for particular transaction. + /// + /// This method sends a `AddViewStream` command to the controller of each transaction to + /// remove the view's stream corresponding to the given block hash. + pub(crate) fn add_view_watcher_for_tx( + &self, + tx_hash: ExtrinsicHash, + block_hash: BlockHash, + stream: TxStatusStream, + ) { + let mut controllers = self.controllers.write(); + + if let Entry::Occupied(mut tx) = controllers.entry(tx_hash) { + if let Err(e) = tx + .get_mut() + .unbounded_send(ControllerCommand::AddViewStream(block_hash, stream)) + { + trace!(target: LOG_TARGET, "[{:?}] add_view_watcher_for_tx: send message failed: {:?}", tx_hash, e); + tx.remove(); + } + } + } + + /// Removes a view's stream associated with a specific view hash across all transactions. + /// + /// This method sends a `RemoveViewStream` command to the controller of each transaction to + /// remove the view's stream corresponding to the given block hash. + pub(crate) fn remove_view(&self, block_hash: BlockHash) { + self.controllers.write().retain(|tx_hash, sender| { + sender + .unbounded_send(ControllerCommand::RemoveViewStream(block_hash)) + .map_err(|e| { + log::trace!(target: LOG_TARGET, "[{:?}] remove_view: send message failed: {:?}", tx_hash, e); + e + }) + .is_ok() + }); + } + + /// Invalidate given transaction. + /// + /// This method sends a `TransactionInvalidated` command to the controller of each transaction + /// provided to process the invalidation request. + /// + /// The external event will be sent if no view is referencing the transaction as `Ready` or + /// `Future`. + pub(crate) fn invalidate_transactions(&self, invalid_hashes: &[ExtrinsicHash]) { + let mut controllers = self.controllers.write(); + invalid_hashes.iter().for_each(|tx_hash| { + if let Entry::Occupied(mut tx) = controllers.entry(*tx_hash) { + trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction", tx_hash); + if let Err(e) = + tx.get_mut().unbounded_send(ControllerCommand::TransactionInvalidated) + { + trace!(target: LOG_TARGET, "[{:?}] invalidate_transaction: send message failed: {:?}", tx_hash, e); + tx.remove(); + } + } + }); + } + + /// Send `Broadcasted` event to listeners of all transactions. + /// + /// This method sends a `TransactionBroadcasted` command to the controller of each transaction + /// provided prompting the external `Broadcasted` event. + pub(crate) fn transactions_broadcasted( + &self, + propagated: HashMap, Vec>, + ) { + let mut controllers = self.controllers.write(); + propagated.into_iter().for_each(|(tx_hash, peers)| { + if let Entry::Occupied(mut tx) = controllers.entry(tx_hash) { + trace!(target: LOG_TARGET, "[{:?}] transaction_broadcasted", tx_hash); + if let Err(e) = tx.get_mut().unbounded_send(ControllerCommand::TransactionBroadcasted(peers)) { + trace!(target: LOG_TARGET, "[{:?}] transactions_broadcasted: send message failed: {:?}", tx_hash, e); + tx.remove(); + } + } + }); + } + + /// Send `Dropped` event to listeners of transactions. + /// + /// This method sends a `TransactionDropped` command to the controller of each requested + /// transaction prompting and external `Broadcasted` event. + pub(crate) fn transactions_dropped(&self, dropped: &[ExtrinsicHash]) { + let mut controllers = self.controllers.write(); + debug!(target: LOG_TARGET, "mvl::transactions_dropped: {:?}", dropped); + for tx_hash in dropped { + if let Some(tx) = controllers.remove(&tx_hash) { + debug!(target: LOG_TARGET, "[{:?}] transaction_dropped", tx_hash); + if let Err(e) = tx.unbounded_send(ControllerCommand::TransactionDropped) { + trace!(target: LOG_TARGET, "[{:?}] transactions_dropped: send message failed: {:?}", tx_hash, e); + }; + } + } + } + + /// Send `Finalized` event for given transaction at given block. + /// + /// This will send `Finalized` event to the external watcher. + pub(crate) fn finalize_transaction( + &self, + tx_hash: ExtrinsicHash, + block: BlockHash, + idx: TxIndex, + ) { + let mut controllers = self.controllers.write(); + if let Some(tx) = controllers.remove(&tx_hash) { + trace!(target: LOG_TARGET, "[{:?}] finalize_transaction", tx_hash); + if let Err(e) = tx.unbounded_send(ControllerCommand::FinalizeTransaction(block, idx)) { + trace!(target: LOG_TARGET, "[{:?}] finalize_transaction: send message failed: {:?}", tx_hash, e); + } + }; + } + + /// Removes stale controllers. + pub(crate) fn remove_stale_controllers(&self) { + self.controllers.write().retain(|_, c| !c.is_closed()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::tests::TestApi; + use futures::{stream, StreamExt}; + use sp_core::H256; + + type MultiViewListener = super::MultiViewListener; + + #[tokio::test] + async fn test01() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash = H256::repeat_byte(0x01); + let events = vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash, 0)), + TransactionStatus::Finalized((block_hash, 0)), + ]; + + let tx_hash = H256::repeat_byte(0x0a); + let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); + let handle = tokio::spawn(async move { external_watcher.collect::>().await }); + + let view_stream = futures::stream::iter(events.clone()); + + listener.add_view_watcher_for_tx(tx_hash, block_hash, view_stream.boxed()); + + let out = handle.await.unwrap(); + assert_eq!(out, events); + log::debug!("out: {:#?}", out); + } + + #[tokio::test] + async fn test02() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let events0 = vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + ]; + + let block_hash1 = H256::repeat_byte(0x02); + let events1 = vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash1, 0)), + TransactionStatus::Finalized((block_hash1, 0)), + ]; + + let tx_hash = H256::repeat_byte(0x0a); + let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); + + let view_stream0 = futures::stream::iter(events0.clone()); + let view_stream1 = futures::stream::iter(events1.clone()); + + let handle = tokio::spawn(async move { external_watcher.collect::>().await }); + + listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); + listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + + let out = handle.await.unwrap(); + + log::debug!("out: {:#?}", out); + assert!(out.iter().all(|v| vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + TransactionStatus::InBlock((block_hash1, 0)), + TransactionStatus::Finalized((block_hash1, 0)), + ] + .contains(v))); + assert_eq!(out.len(), 5); + } + + #[tokio::test] + async fn test03() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let events0 = vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + ]; + + let block_hash1 = H256::repeat_byte(0x02); + let events1 = vec![TransactionStatus::Future]; + + let tx_hash = H256::repeat_byte(0x0a); + let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); + let handle = tokio::spawn(async move { external_watcher.collect::>().await }); + + let view_stream0 = futures::stream::iter(events0.clone()); + let view_stream1 = futures::stream::iter(events1.clone()); + + listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); + listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + + listener.invalidate_transactions(&[tx_hash]); + + let out = handle.await.unwrap(); + log::debug!("out: {:#?}", out); + assert!(out.iter().all(|v| vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + TransactionStatus::Invalid + ] + .contains(v))); + assert_eq!(out.len(), 4); + } + + #[tokio::test] + async fn test032() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let events0_tx0 = vec![TransactionStatus::Future]; + let events0_tx1 = vec![TransactionStatus::Ready]; + + let block_hash1 = H256::repeat_byte(0x02); + let events1_tx0 = + vec![TransactionStatus::Ready, TransactionStatus::InBlock((block_hash1, 0))]; + let events1_tx1 = vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash1, 1)), + TransactionStatus::Finalized((block_hash1, 1)), + ]; + + let tx0_hash = H256::repeat_byte(0x0a); + let tx1_hash = H256::repeat_byte(0x0b); + let external_watcher_tx0 = listener.create_external_watcher_for_tx(tx0_hash).unwrap(); + let external_watcher_tx1 = listener.create_external_watcher_for_tx(tx1_hash).unwrap(); + + let handle0 = tokio::spawn(async move { external_watcher_tx0.collect::>().await }); + let handle1 = tokio::spawn(async move { external_watcher_tx1.collect::>().await }); + + let view0_tx0_stream = futures::stream::iter(events0_tx0.clone()); + let view0_tx1_stream = futures::stream::iter(events0_tx1.clone()); + + let view1_tx0_stream = futures::stream::iter(events1_tx0.clone()); + let view1_tx1_stream = futures::stream::iter(events1_tx1.clone()); + + listener.add_view_watcher_for_tx(tx0_hash, block_hash0, view0_tx0_stream.boxed()); + listener.add_view_watcher_for_tx(tx0_hash, block_hash1, view1_tx0_stream.boxed()); + listener.add_view_watcher_for_tx(tx1_hash, block_hash0, view0_tx1_stream.boxed()); + listener.add_view_watcher_for_tx(tx1_hash, block_hash1, view1_tx1_stream.boxed()); + + listener.invalidate_transactions(&[tx0_hash]); + listener.invalidate_transactions(&[tx1_hash]); + + let out_tx0 = handle0.await.unwrap(); + let out_tx1 = handle1.await.unwrap(); + + log::debug!("out_tx0: {:#?}", out_tx0); + log::debug!("out_tx1: {:#?}", out_tx1); + assert!(out_tx0.iter().all(|v| vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash1, 0)), + TransactionStatus::Invalid + ] + .contains(v))); + + assert!(out_tx1.iter().all(|v| vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash1, 1)), + TransactionStatus::Finalized((block_hash1, 1)) + ] + .contains(v))); + assert_eq!(out_tx0.len(), 4); + assert_eq!(out_tx1.len(), 3); + } + + #[tokio::test] + async fn test04() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let events0 = vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + ]; + + let block_hash1 = H256::repeat_byte(0x02); + let events1 = vec![TransactionStatus::Future]; + + let tx_hash = H256::repeat_byte(0x0a); + let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); + + //views will keep transaction valid, invalidation shall not happen + let view_stream0 = futures::stream::iter(events0.clone()).chain(stream::pending().boxed()); + let view_stream1 = futures::stream::iter(events1.clone()).chain(stream::pending().boxed()); + + let handle = tokio::spawn(async move { + // views are still there, we need to fetch 3 events + external_watcher.take(3).collect::>().await + }); + + listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); + listener.add_view_watcher_for_tx(tx_hash, block_hash1, view_stream1.boxed()); + + listener.invalidate_transactions(&[tx_hash]); + + let out = handle.await.unwrap(); + log::debug!("out: {:#?}", out); + + // invalid shall not be sent + assert!(out.iter().all(|v| vec![ + TransactionStatus::Future, + TransactionStatus::Ready, + TransactionStatus::InBlock((block_hash0, 0)), + ] + .contains(v))); + assert_eq!(out.len(), 3); + } + + #[tokio::test] + async fn test05() { + sp_tracing::try_init_simple(); + let listener = MultiViewListener::new(); + + let block_hash0 = H256::repeat_byte(0x01); + let events0 = vec![TransactionStatus::Invalid]; + + let tx_hash = H256::repeat_byte(0x0a); + let external_watcher = listener.create_external_watcher_for_tx(tx_hash).unwrap(); + let handle = tokio::spawn(async move { external_watcher.collect::>().await }); + + let view_stream0 = futures::stream::iter(events0.clone()).chain(stream::pending().boxed()); + + // Note: this generates actual Invalid event. + // Invalid event from View's stream is intentionally ignored. + listener.invalidate_transactions(&[tx_hash]); + + listener.add_view_watcher_for_tx(tx_hash, block_hash0, view_stream0.boxed()); + + let out = handle.await.unwrap(); + log::debug!("out: {:#?}", out); + + assert!(out.iter().all(|v| vec![TransactionStatus::Invalid].contains(v))); + assert_eq!(out.len(), 1); + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs new file mode 100644 index 00000000000..9464ab3f576 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs @@ -0,0 +1,240 @@ +// 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 . + +//! The background worker for the [`View`] and [`TxMemPool`] revalidation. +//! +//! The [*Background tasks*](../index.html#background-tasks) section provides some extra details on +//! revalidation process. + +use std::{marker::PhantomData, pin::Pin, sync::Arc}; + +use crate::{graph::ChainApi, LOG_TARGET}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_blockchain::HashAndNumber; +use sp_runtime::traits::Block as BlockT; + +use super::tx_mem_pool::TxMemPool; +use futures::prelude::*; + +use super::view::{FinishRevalidationWorkerChannels, View}; + +/// Revalidation request payload sent from the queue to the worker. +enum WorkerPayload +where + Block: BlockT, + Api: ChainApi + 'static, +{ + /// Request to revalidated the given instance of the [`View`] + /// + /// Communication channels with maintain thread are also provided. + RevalidateView(Arc>, FinishRevalidationWorkerChannels), + /// Request to revalidated the given instance of the [`TxMemPool`] at provided block hash. + RevalidateMempool(Arc>, HashAndNumber), +} + +/// The background revalidation worker. +struct RevalidationWorker { + _phantom: PhantomData, +} + +impl RevalidationWorker +where + Block: BlockT, + ::Hash: Unpin, +{ + /// Create a new instance of the background worker. + fn new() -> Self { + Self { _phantom: Default::default() } + } + + /// A background worker main loop. + /// + /// Waits for and dispatches the [`WorkerPayload`] messages sent from the + /// [`RevalidationQueue`]. + pub async fn run + 'static>( + self, + from_queue: TracingUnboundedReceiver>, + ) { + let mut from_queue = from_queue.fuse(); + + loop { + let Some(payload) = from_queue.next().await else { + // R.I.P. worker! + break; + }; + match payload { + WorkerPayload::RevalidateView(view, worker_channels) => + view.revalidate(worker_channels).await, + WorkerPayload::RevalidateMempool(mempool, finalized_hash_and_number) => + mempool.revalidate(finalized_hash_and_number).await, + }; + } + } +} + +/// A Revalidation queue. +/// +/// Allows to send the revalidation requests to the [`RevalidationWorker`]. +pub struct RevalidationQueue +where + Api: ChainApi + 'static, + Block: BlockT, +{ + background: Option>>, +} + +impl RevalidationQueue +where + Api: ChainApi + 'static, + Block: BlockT, + ::Hash: Unpin, +{ + /// New revalidation queue without background worker. + /// + /// All validation requests will be blocking. + pub fn new() -> Self { + Self { background: None } + } + + /// New revalidation queue with background worker. + /// + /// All validation requests will be executed in the background. + pub fn new_with_worker() -> (Self, Pin + Send>>) { + let (to_worker, from_queue) = tracing_unbounded("mpsc_revalidation_queue", 100_000); + (Self { background: Some(to_worker) }, RevalidationWorker::new().run(from_queue).boxed()) + } + + /// Queue the view for later revalidation. + /// + /// If the queue is configured with background worker, this will return immediately. + /// If the queue is configured without background worker, this will resolve after + /// revalidation is actually done. + /// + /// Schedules execution of the [`View::revalidate`]. + pub async fn revalidate_view( + &self, + view: Arc>, + finish_revalidation_worker_channels: FinishRevalidationWorkerChannels, + ) { + log::trace!( + target: LOG_TARGET, + "revalidation_queue::revalidate_view: Sending view to revalidation queue at {}", + view.at.hash + ); + + if let Some(ref to_worker) = self.background { + if let Err(e) = to_worker.unbounded_send(WorkerPayload::RevalidateView( + view, + finish_revalidation_worker_channels, + )) { + log::warn!(target: LOG_TARGET, "revalidation_queue::revalidate_view: Failed to update background worker: {:?}", e); + } + } else { + view.revalidate(finish_revalidation_worker_channels).await + } + } + + /// Revalidates the given mempool instance. + /// + /// If queue configured with background worker, this will return immediately. + /// If queue configured without background worker, this will resolve after + /// revalidation is actually done. + /// + /// Schedules execution of the [`TxMemPool::revalidate`]. + pub async fn revalidate_mempool( + &self, + mempool: Arc>, + finalized_hash: HashAndNumber, + ) { + log::trace!( + target: LOG_TARGET, + "Sent mempool to revalidation queue at hash: {:?}", + finalized_hash + ); + + if let Some(ref to_worker) = self.background { + if let Err(e) = + to_worker.unbounded_send(WorkerPayload::RevalidateMempool(mempool, finalized_hash)) + { + log::warn!(target: LOG_TARGET, "Failed to update background worker: {:?}", e); + } + } else { + mempool.revalidate(finalized_hash).await + } + } +} + +#[cfg(test)] +//todo: add more tests [#5480] +mod tests { + use super::*; + use crate::{ + common::tests::{uxt, TestApi}, + fork_aware_txpool::view::FinishRevalidationLocalChannels, + }; + use futures::executor::block_on; + use sc_transaction_pool_api::TransactionSource; + use substrate_test_runtime::{AccountId, Transfer, H256}; + use substrate_test_runtime_client::AccountKeyring::Alice; + #[test] + fn revalidation_queue_works() { + let api = Arc::new(TestApi::default()); + let block0 = api.expect_hash_and_number(0); + + let view = Arc::new(View::new( + api.clone(), + block0, + Default::default(), + Default::default(), + false.into(), + )); + let queue = Arc::new(RevalidationQueue::new()); + + let uxt = uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }); + + let _ = block_on( + view.submit_many(TransactionSource::External, std::iter::once(uxt.clone().into())), + ); + assert_eq!(api.validation_requests().len(), 1); + + let (finish_revalidation_request_tx, finish_revalidation_request_rx) = + tokio::sync::mpsc::channel(1); + let (revalidation_result_tx, revalidation_result_rx) = tokio::sync::mpsc::channel(1); + + let finish_revalidation_worker_channels = FinishRevalidationWorkerChannels::new( + finish_revalidation_request_rx, + revalidation_result_tx, + ); + + let _finish_revalidation_local_channels = FinishRevalidationLocalChannels::new( + finish_revalidation_request_tx, + revalidation_result_rx, + ); + + block_on(queue.revalidate_view(view.clone(), finish_revalidation_worker_channels)); + + assert_eq!(api.validation_requests().len(), 2); + // number of ready + assert_eq!(view.status().ready, 1); + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs new file mode 100644 index 00000000000..86ea27dcf45 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -0,0 +1,535 @@ +// 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 . + +//! Transaction memory pool, container for watched and unwatched transactions. +//! Acts as a buffer which collect transactions before importing them to the views. Following are +//! the crucial use cases when it is needed: +//! - empty pool (no views yet) +//! - potential races between creation of view and submitting transaction (w/o intermediary buffer +//! some transactions could be lost) +//! - the transaction can be invalid on some forks (and thus the associated views may not contain +//! it), while on other forks tx can be valid. Depending on which view is chosen to be cloned, +//! such transaction could not be present in the newly created view. + +use super::{metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener}; +use crate::{ + common::log_xt::log_xt_trace, + graph, + graph::{ExtrinsicFor, ExtrinsicHash}, + LOG_TARGET, +}; +use futures::FutureExt; +use itertools::Itertools; +use parking_lot::RwLock; +use sc_transaction_pool_api::TransactionSource; +use sp_blockchain::HashAndNumber; +use sp_runtime::{ + traits::Block as BlockT, + transaction_validity::{InvalidTransaction, TransactionValidityError}, +}; +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::{atomic, atomic::AtomicU64, Arc}, + time::Instant, +}; + +/// The minimum interval between single transaction revalidations. Given in blocks. +pub(crate) const TXMEMPOOL_REVALIDATION_PERIOD: u64 = 10; + +/// The number of transactions revalidated in single revalidation batch. +pub(crate) const TXMEMPOOL_MAX_REVALIDATION_BATCH_SIZE: usize = 1000; + +/// The maximum number of transactions kept in the mem pool. Given as multiple of +/// the view's total limit. +pub const TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER: usize = 4; + +/// Represents the transaction in the intermediary buffer. +#[derive(Debug)] +pub(crate) struct TxInMemPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, +{ + //todo: add listener for updating listeners with events [#5495] + /// Is the progress of transaction watched. + /// + /// Was transaction sent with `submit_and_watch`. + watched: bool, + /// Extrinsic actual body. + tx: ExtrinsicFor, + /// Transaction source. + source: TransactionSource, + /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. + validated_at: AtomicU64, + //todo: we need to add future / ready status at finalized block. + //If future transactions are stuck in tx_mem_pool (due to limits being hit), we need a means + // to replace them somehow with newly coming transactions. + // For sure priority is one of them, but some additional criteria maybe required. + // + // The other maybe simple solution for this could be just obeying 10% limit for future in + // tx_mem_pool. Oldest future transaction could be just dropped. *(Status at finalized would + // also be needed). Probably is_future_at_finalized:Option flag will be enought +} + +impl TxInMemPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, +{ + /// Shall the progress of transaction be watched. + /// + /// Was transaction sent with `submit_and_watch`. + fn is_watched(&self) -> bool { + self.watched + } + + /// Creates a new instance of wrapper for unwatched transaction. + fn new_unwatched(source: TransactionSource, tx: ExtrinsicFor) -> Self { + Self { watched: false, tx, source, validated_at: AtomicU64::new(0) } + } + + /// Creates a new instance of wrapper for watched transaction. + fn new_watched(source: TransactionSource, tx: ExtrinsicFor) -> Self { + Self { watched: true, tx, source, validated_at: AtomicU64::new(0) } + } + + /// Provides a clone of actual transaction body. + /// + /// Operation is cheap, as the body is `Arc`. + pub(crate) fn tx(&self) -> ExtrinsicFor { + self.tx.clone() + } + + /// Returns the source of the transaction. + pub(crate) fn source(&self) -> TransactionSource { + self.source + } +} + +type InternalTxMemPoolMap = + HashMap, Arc>>; +type InternalTxMemPoolMapEntry<'a, ChainApi, Block> = + Entry<'a, ExtrinsicHash, Arc>>; + +/// An intermediary transactions buffer. +/// +/// Keeps all the transaction which are potentially valid. Transactions that were finalized or +/// transactions that are invalid at finalized blocks are removed, either while handling the +/// `Finalized` event, or during revalidation process. +/// +/// All transactions from a`TxMemPool` are submitted to the newly created views. +/// +/// All newly submitted transactions goes into the `TxMemPool`. +pub(super) struct TxMemPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, +{ + /// A shared API instance necessary for blockchain related operations. + api: Arc, + + /// A shared instance of the `MultiViewListener`. + /// + /// Provides a side-channel allowing to send per-transaction state changes notification. + //todo: could be removed after removing watched field (and adding listener into tx) [#5495] + listener: Arc>, + + /// A map that stores the transactions currently in the memory pool. + /// + /// The key is the hash of the transaction, and the value is a wrapper + /// structure, which contains the mempool specific details of the transaction. + transactions: RwLock>, + + /// Prometheus's metrics endpoint. + metrics: PrometheusMetrics, + + /// Indicates the maximum number of transactions that can be maintained in the memory pool. + max_transactions_count: usize, +} + +impl TxMemPool +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, + ::Hash: Unpin, +{ + /// Creates a new `TxMemPool` instance with the given API, listener, metrics, + /// and max transaction count. + pub(super) fn new( + api: Arc, + listener: Arc>, + metrics: PrometheusMetrics, + max_transactions_count: usize, + ) -> Self { + Self { api, listener, transactions: Default::default(), metrics, max_transactions_count } + } + + /// Creates a new `TxMemPool` instance for testing purposes. + #[allow(dead_code)] + fn new_test(api: Arc, max_transactions_count: usize) -> Self { + Self { + api, + listener: Arc::from(MultiViewListener::new()), + transactions: Default::default(), + metrics: Default::default(), + max_transactions_count, + } + } + + /// Retrieves a transaction by its hash if it exists in the memory pool. + pub(super) fn get_by_hash( + &self, + hash: ExtrinsicHash, + ) -> Option> { + self.transactions.read().get(&hash).map(|t| t.tx()) + } + + /// Returns a tuple with the count of unwatched and watched transactions in the memory pool. + pub(super) fn unwatched_and_watched_count(&self) -> (usize, usize) { + let transactions = self.transactions.read(); + let watched_count = transactions.values().filter(|t| t.is_watched()).count(); + (transactions.len() - watched_count, watched_count) + } + + /// Attempts to insert a transaction into the memory pool, ensuring it does not + /// exceed the maximum allowed transaction count. + fn try_insert( + &self, + current_len: usize, + entry: InternalTxMemPoolMapEntry<'_, ChainApi, Block>, + hash: ExtrinsicHash, + tx: TxInMemPool, + ) -> Result, ChainApi::Error> { + //todo: obey size limits [#5476] + let result = match (current_len < self.max_transactions_count, entry) { + (true, Entry::Vacant(v)) => { + v.insert(Arc::from(tx)); + Ok(hash) + }, + (_, Entry::Occupied(_)) => + Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash)).into()), + (false, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped.into()), + }; + log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result); + + result + } + + /// Adds a new unwatched transactions to the internal buffer not exceeding the limit. + /// + /// Returns the vector of results for each transaction, the order corresponds to the input + /// vector. + pub(super) fn extend_unwatched( + &self, + source: TransactionSource, + xts: Vec>, + ) -> Vec, ChainApi::Error>> { + let mut transactions = self.transactions.write(); + let result = xts + .into_iter() + .map(|xt| { + let hash = self.api.hash_and_length(&xt).0; + self.try_insert( + transactions.len(), + transactions.entry(hash), + hash, + TxInMemPool::new_unwatched(source, xt.clone()), + ) + }) + .collect::>(); + result + } + + /// Adds a new watched transaction to the memory pool if it does not exceed the maximum allowed + /// transaction count. + pub(super) fn push_watched( + &self, + source: TransactionSource, + xt: ExtrinsicFor, + ) -> Result, ChainApi::Error> { + let mut transactions = self.transactions.write(); + let hash = self.api.hash_and_length(&xt).0; + self.try_insert( + transactions.len(), + transactions.entry(hash), + hash, + TxInMemPool::new_watched(source, xt.clone()), + ) + } + + /// Removes transactions from the memory pool which are specified by the given list of hashes + /// and send the `Dropped` event to the listeners of these transactions. + pub(super) async fn remove_dropped_transactions( + &self, + to_be_removed: &[ExtrinsicHash], + ) { + log::debug!(target: LOG_TARGET, "remove_dropped_transactions count:{:?}", to_be_removed.len()); + log_xt_trace!(target: LOG_TARGET, to_be_removed, "[{:?}] mempool::remove_dropped_transactions"); + let mut transactions = self.transactions.write(); + to_be_removed.iter().for_each(|t| { + transactions.remove(t); + }); + + self.listener.transactions_dropped(to_be_removed); + } + + /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory + /// pool. + pub(super) fn clone_unwatched( + &self, + ) -> HashMap, Arc>> { + self.transactions + .read() + .iter() + .filter_map(|(hash, tx)| (!tx.is_watched()).then(|| (*hash, tx.clone()))) + .collect::>() + } + + /// Clones and returns a `HashMap` of references to all watched transactions in the memory pool. + pub(super) fn clone_watched( + &self, + ) -> HashMap, Arc>> { + self.transactions + .read() + .iter() + .filter_map(|(hash, tx)| (tx.is_watched()).then(|| (*hash, tx.clone()))) + .collect::>() + } + + /// Removes a transaction from the memory pool based on a given hash. + pub(super) fn remove(&self, hash: ExtrinsicHash) { + let _ = self.transactions.write().remove(&hash); + } + + /// Revalidates a batch of transactions against the provided finalized block. + /// + /// Returns a vector of invalid transaction hashes. + async fn revalidate_inner(&self, finalized_block: HashAndNumber) -> Vec { + log::trace!(target: LOG_TARGET, "mempool::revalidate at:{finalized_block:?}"); + let start = Instant::now(); + + let (count, input) = { + let transactions = self.transactions.read(); + + ( + transactions.len(), + transactions + .clone() + .into_iter() + .filter(|xt| { + let finalized_block_number = finalized_block.number.into().as_u64(); + xt.1.validated_at.load(atomic::Ordering::Relaxed) + + TXMEMPOOL_REVALIDATION_PERIOD < + finalized_block_number + }) + .sorted_by_key(|tx| tx.1.validated_at.load(atomic::Ordering::Relaxed)) + .take(TXMEMPOOL_MAX_REVALIDATION_BATCH_SIZE), + ) + }; + + let validations_futures = input.into_iter().map(|(xt_hash, xt)| { + self.api.validate_transaction(finalized_block.hash, xt.source, xt.tx()).map( + move |validation_result| { + xt.validated_at + .store(finalized_block.number.into().as_u64(), atomic::Ordering::Relaxed); + (xt_hash, validation_result) + }, + ) + }); + let validation_results = futures::future::join_all(validations_futures).await; + let input_len = validation_results.len(); + + let duration = start.elapsed(); + + let invalid_hashes = validation_results + .into_iter() + .filter_map(|(xt_hash, validation_result)| match validation_result { + Ok(Ok(_)) | + Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Future))) => None, + Err(_) | + Ok(Err(TransactionValidityError::Unknown(_))) | + Ok(Err(TransactionValidityError::Invalid(_))) => { + log::trace!( + target: LOG_TARGET, + "[{:?}]: Purging: invalid: {:?}", + xt_hash, + validation_result, + ); + Some(xt_hash) + }, + }) + .collect::>(); + + log::debug!( + target: LOG_TARGET, + "mempool::revalidate: at {finalized_block:?} count:{input_len}/{count} purged:{} took {duration:?}", invalid_hashes.len(), + ); + + invalid_hashes + } + + /// Removes the finalized transactions from the memory pool, using a provided list of hashes. + pub(super) async fn purge_finalized_transactions( + &self, + finalized_xts: &Vec>, + ) { + log::debug!(target: LOG_TARGET, "purge_finalized_transactions count:{:?}", finalized_xts.len()); + log_xt_trace!(target: LOG_TARGET, finalized_xts, "[{:?}] purged finalized transactions"); + let mut transactions = self.transactions.write(); + finalized_xts.iter().for_each(|t| { + transactions.remove(t); + }); + } + + /// Revalidates transactions in the memory pool against a given finalized block and removes + /// invalid ones. + pub(super) async fn revalidate(&self, finalized_block: HashAndNumber) { + log::trace!(target: LOG_TARGET, "purge_transactions at:{:?}", finalized_block); + let invalid_hashes = self.revalidate_inner(finalized_block.clone()).await; + + self.metrics.report(|metrics| { + metrics.mempool_revalidation_invalid_txs.inc_by(invalid_hashes.len() as _) + }); + + let mut transactions = self.transactions.write(); + invalid_hashes.iter().for_each(|i| { + transactions.remove(i); + }); + self.listener.invalidate_transactions(&invalid_hashes); + } +} + +#[cfg(test)] +mod tx_mem_pool_tests { + use super::*; + use crate::common::tests::TestApi; + use substrate_test_runtime::{AccountId, Extrinsic, Transfer, H256}; + use substrate_test_runtime_client::AccountKeyring::*; + fn uxt(nonce: u64) -> Extrinsic { + crate::common::tests::uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce, + }) + } + + #[test] + fn extend_unwatched_obeys_limit() { + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api, max); + + let xts = (0..max + 1).map(|x| Arc::from(uxt(x as _))).collect::>(); + + let results = mempool.extend_unwatched(TransactionSource::External, xts); + assert!(results.iter().take(max).all(Result::is_ok)); + assert!(matches!( + results.into_iter().last().unwrap().unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } + + #[test] + fn extend_unwatched_detects_already_imported() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api, max); + + let mut xts = (0..max - 1).map(|x| Arc::from(uxt(x as _))).collect::>(); + xts.push(xts.iter().last().unwrap().clone()); + + let results = mempool.extend_unwatched(TransactionSource::External, xts); + assert!(results.iter().take(max - 1).all(Result::is_ok)); + assert!(matches!( + results.into_iter().last().unwrap().unwrap_err(), + sc_transaction_pool_api::error::Error::AlreadyImported(_) + )); + } + + #[test] + fn push_obeys_limit() { + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api, max); + + let xts = (0..max).map(|x| Arc::from(uxt(x as _))).collect::>(); + + let results = mempool.extend_unwatched(TransactionSource::External, xts); + assert!(results.iter().all(Result::is_ok)); + + let xt = Arc::from(uxt(98)); + let result = mempool.push_watched(TransactionSource::External, xt); + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + let xt = Arc::from(uxt(99)); + let mut result = mempool.extend_unwatched(TransactionSource::External, vec![xt]); + assert!(matches!( + result.pop().unwrap().unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } + + #[test] + fn push_detects_already_imported() { + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api, 2 * max); + + let xts = (0..max).map(|x| Arc::from(uxt(x as _))).collect::>(); + let xt0 = xts.iter().last().unwrap().clone(); + let xt1 = xts.iter().next().unwrap().clone(); + + let results = mempool.extend_unwatched(TransactionSource::External, xts); + assert!(results.iter().all(Result::is_ok)); + + let result = mempool.push_watched(TransactionSource::External, xt0); + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::AlreadyImported(_) + )); + let mut result = mempool.extend_unwatched(TransactionSource::External, vec![xt1]); + assert!(matches!( + result.pop().unwrap().unwrap_err(), + sc_transaction_pool_api::error::Error::AlreadyImported(_) + )); + } + + #[test] + fn count_works() { + let max = 100; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api, max); + + let xts0 = (0..10).map(|x| Arc::from(uxt(x as _))).collect::>(); + + let results = mempool.extend_unwatched(TransactionSource::External, xts0); + assert!(results.iter().all(Result::is_ok)); + + let xts1 = (0..5).map(|x| Arc::from(uxt(2 * x))).collect::>(); + let results = xts1 + .into_iter() + .map(|t| mempool.push_watched(TransactionSource::External, t)) + .collect::>(); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.unwatched_and_watched_count(), (10, 5)); + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs new file mode 100644 index 00000000000..fd5bfa8312c --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -0,0 +1,415 @@ +// 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 . + +//! Transaction pool view. +//! +//! The View represents the state of the transaction pool at given block. The view is created when +//! new block is notified to transaction pool. Views are removed on finalization. +//! +//! Refer to [*View*](../index.html#view) section for more details. + +use super::metrics::MetricsLink as PrometheusMetrics; +use crate::{ + common::log_xt::log_xt_trace, + graph::{ + self, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, IsValidator, ValidatedTransaction, + ValidatedTransactionFor, + }, + LOG_TARGET, +}; +use parking_lot::Mutex; +use sc_transaction_pool_api::{PoolStatus, TransactionSource}; +use sp_blockchain::HashAndNumber; +use sp_runtime::{ + traits::Block as BlockT, transaction_validity::TransactionValidityError, SaturatedConversion, +}; +use std::{collections::HashMap, sync::Arc, time::Instant}; + +pub(super) struct RevalidationResult { + revalidated: HashMap, ValidatedTransactionFor>, + invalid_hashes: Vec>, +} + +/// Used to obtain result from RevalidationWorker on View side. +pub(super) type RevalidationResultReceiver = + tokio::sync::mpsc::Receiver>; + +/// Used to send revalidation result from RevalidationWorker to View. +pub(super) type RevalidationResultSender = + tokio::sync::mpsc::Sender>; + +/// Used to receive finish-revalidation-request from View on RevalidationWorker side. +pub(super) type FinishRevalidationRequestReceiver = tokio::sync::mpsc::Receiver<()>; + +/// Used to send finish-revalidation-request from View to RevalidationWorker. +pub(super) type FinishRevalidationRequestSender = tokio::sync::mpsc::Sender<()>; + +/// Endpoints of channels used on View side (maintain thread) +pub(super) struct FinishRevalidationLocalChannels { + /// Used to send finish revalidation request. + finish_revalidation_request_tx: Option, + /// Used to receive revalidation results. + revalidation_result_rx: RevalidationResultReceiver, +} + +impl FinishRevalidationLocalChannels { + /// Creates a new instance of endpoints for channels used on View side + pub fn new( + finish_revalidation_request_tx: FinishRevalidationRequestSender, + revalidation_result_rx: RevalidationResultReceiver, + ) -> Self { + Self { + finish_revalidation_request_tx: Some(finish_revalidation_request_tx), + revalidation_result_rx, + } + } + + /// Removes a finish revalidation sender + /// + /// Should be called when revalidation was already terminated and finish revalidation message is + /// no longer expected. + fn remove_sender(&mut self) { + self.finish_revalidation_request_tx = None; + } +} + +/// Endpoints of channels used on `RevalidationWorker` side (background thread) +pub(super) struct FinishRevalidationWorkerChannels { + /// Used to receive finish revalidation request. + finish_revalidation_request_rx: FinishRevalidationRequestReceiver, + /// Used to send revalidation results. + revalidation_result_tx: RevalidationResultSender, +} + +impl FinishRevalidationWorkerChannels { + /// Creates a new instance of endpoints for channels used on `RevalidationWorker` side + pub fn new( + finish_revalidation_request_rx: FinishRevalidationRequestReceiver, + revalidation_result_tx: RevalidationResultSender, + ) -> Self { + Self { finish_revalidation_request_rx, revalidation_result_tx } + } +} + +/// Represents the state of transaction pool for given block. +/// +/// Refer to [*View*](../index.html#view) section for more details on the purpose and life cycle of +/// the `View`. +pub(super) struct View { + /// The internal pool keeping the set of ready and future transaction at the given block. + pub(super) pool: graph::Pool, + /// The hash and number of the block with which this view is associated. + pub(super) at: HashAndNumber, + /// Endpoints of communication channel with background worker. + revalidation_worker_channels: Mutex>>, + /// Prometheus's metrics endpoint. + metrics: PrometheusMetrics, +} + +impl View +where + ChainApi: graph::ChainApi, + ::Hash: Unpin, +{ + /// Creates a new empty view. + pub(super) fn new( + api: Arc, + at: HashAndNumber, + options: graph::Options, + metrics: PrometheusMetrics, + is_validator: IsValidator, + ) -> Self { + metrics.report(|metrics| metrics.non_cloned_views.inc()); + Self { + pool: graph::Pool::new(options, is_validator, api), + at, + revalidation_worker_channels: Mutex::from(None), + metrics, + } + } + + /// Creates a copy of the other view. + pub(super) fn new_from_other(&self, at: &HashAndNumber) -> Self { + View { + at: at.clone(), + pool: self.pool.deep_clone(), + revalidation_worker_channels: Mutex::from(None), + metrics: self.metrics.clone(), + } + } + + /// Imports many unvalidated extrinsics into the view. + pub(super) async fn submit_many( + &self, + source: TransactionSource, + xts: impl IntoIterator>, + ) -> Vec, ChainApi::Error>> { + if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) { + let xts = xts.into_iter().collect::>(); + log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); + self.pool.submit_at(&self.at, source, xts).await + } else { + self.pool.submit_at(&self.at, source, xts).await + } + } + + /// Import a single extrinsic and starts to watch its progress in the view. + pub(super) async fn submit_and_watch( + &self, + source: TransactionSource, + xt: ExtrinsicFor, + ) -> Result, ExtrinsicHash>, ChainApi::Error> { + log::trace!(target: LOG_TARGET, "[{:?}] view::submit_and_watch at:{}", self.pool.validated_pool().api().hash_and_length(&xt).0, self.at.hash); + self.pool.submit_and_watch(&self.at, source, xt).await + } + + /// Status of the pool associated with the view. + pub(super) fn status(&self) -> PoolStatus { + self.pool.validated_pool().status() + } + + /// Creates a watcher for given transaction. + /// + /// Intended to be called for the transaction that already exists in the pool + pub(super) fn create_watcher( + &self, + tx_hash: ExtrinsicHash, + ) -> Watcher, ExtrinsicHash> { + //todo(minor): some assert could be added here - to make sure that transaction actually + // exists in the view. + self.pool.validated_pool().create_watcher(tx_hash) + } + + /// Revalidates some part of transaction from the internal pool. + /// + /// Intended to be called from the revalidation worker. The revalidation process can be + /// terminated by sending a message to the `rx` channel provided within + /// `finish_revalidation_worker_channels`. Revalidation results are sent back over the `tx` + /// channels and shall be applied in maintain thread. + /// + /// View revalidation currently is not throttled, and until not terminated it will revalidate + /// all the transactions. Note: this can be improved if CPU usage due to revalidation becomes a + /// problem. + pub(super) async fn revalidate( + &self, + finish_revalidation_worker_channels: FinishRevalidationWorkerChannels, + ) { + let FinishRevalidationWorkerChannels { + mut finish_revalidation_request_rx, + revalidation_result_tx, + } = finish_revalidation_worker_channels; + + log::trace!(target:LOG_TARGET, "view::revalidate: at {} starting", self.at.hash); + let start = Instant::now(); + let validated_pool = self.pool.validated_pool(); + let api = validated_pool.api(); + + let batch: Vec<_> = validated_pool.ready().collect(); + let batch_len = batch.len(); + + //todo: sort batch by revalidation timestamp | maybe not needed at all? xts will be getting + //out of the view... + //todo: revalidate future, remove if invalid [#5496] + + let mut invalid_hashes = Vec::new(); + let mut revalidated = HashMap::new(); + + let mut validation_results = vec![]; + let mut batch_iter = batch.into_iter(); + loop { + let mut should_break = false; + tokio::select! { + _ = finish_revalidation_request_rx.recv() => { + log::trace!(target: LOG_TARGET, "view::revalidate: finish revalidation request received at {}.", self.at.hash); + break + } + _ = async { + if let Some(tx) = batch_iter.next() { + let validation_result = (api.validate_transaction(self.at.hash, tx.source, tx.data.clone()).await, tx.hash, tx); + validation_results.push(validation_result); + } else { + { + self.revalidation_worker_channels.lock().as_mut().map(|ch| ch.remove_sender()); + } + should_break = true; + } + } => {} + } + + if should_break { + break; + } + } + + let revalidation_duration = start.elapsed(); + self.metrics.report(|metrics| { + metrics.view_revalidation_duration.observe(revalidation_duration.as_secs_f64()); + }); + log::debug!( + target:LOG_TARGET, + "view::revalidate: at {:?} count: {}/{} took {:?}", + self.at.hash, + validation_results.len(), + batch_len, + revalidation_duration + ); + log_xt_trace!(data:tuple, target:LOG_TARGET, validation_results.iter().map(|x| (x.1, &x.0)), "[{:?}] view::revalidateresult: {:?}"); + + for (validation_result, tx_hash, tx) in validation_results { + match validation_result { + Ok(Err(TransactionValidityError::Invalid(_))) => { + invalid_hashes.push(tx_hash); + }, + Ok(Ok(validity)) => { + revalidated.insert( + tx_hash, + ValidatedTransaction::valid_at( + self.at.number.saturated_into::(), + tx_hash, + tx.source, + tx.data.clone(), + api.hash_and_length(&tx.data).1, + validity, + ), + ); + }, + Ok(Err(TransactionValidityError::Unknown(e))) => { + log::trace!( + target: LOG_TARGET, + "[{:?}]: Removing. Cannot determine transaction validity: {:?}", + tx_hash, + e + ); + invalid_hashes.push(tx_hash); + }, + Err(validation_err) => { + log::trace!( + target: LOG_TARGET, + "[{:?}]: Removing due to error during revalidation: {}", + tx_hash, + validation_err + ); + invalid_hashes.push(tx_hash); + }, + } + } + + log::trace!(target:LOG_TARGET, "view::revalidate: sending revalidation result at {}", self.at.hash); + if let Err(e) = revalidation_result_tx + .send(RevalidationResult { invalid_hashes, revalidated }) + .await + { + log::trace!(target:LOG_TARGET, "view::revalidate: sending revalidation_result at {} failed {:?}", self.at.hash, e); + } + } + + /// Sends revalidation request to the background worker. + /// + /// Creates communication channels required to stop revalidation request and receive the + /// revalidation results and sends the revalidation request to the background worker. + /// + /// Intended to be called from maintain thread, at the very end of the maintain process. + /// + /// Refer to [*View revalidation*](../index.html#view-revalidation) for more details. + pub(super) async fn start_background_revalidation( + view: Arc, + revalidation_queue: Arc< + super::revalidation_worker::RevalidationQueue, + >, + ) { + log::trace!(target:LOG_TARGET,"view::start_background_revalidation: at {}", view.at.hash); + let (finish_revalidation_request_tx, finish_revalidation_request_rx) = + tokio::sync::mpsc::channel(1); + let (revalidation_result_tx, revalidation_result_rx) = tokio::sync::mpsc::channel(1); + + let finish_revalidation_worker_channels = FinishRevalidationWorkerChannels::new( + finish_revalidation_request_rx, + revalidation_result_tx, + ); + + let finish_revalidation_local_channels = FinishRevalidationLocalChannels::new( + finish_revalidation_request_tx, + revalidation_result_rx, + ); + + *view.revalidation_worker_channels.lock() = Some(finish_revalidation_local_channels); + revalidation_queue + .revalidate_view(view.clone(), finish_revalidation_worker_channels) + .await; + } + + /// Terminates a background view revalidation. + /// + /// Receives the results from the background worker and applies them to the internal pool. + /// Intended to be called from the maintain thread, at the very beginning of the maintain + /// process, before the new view is cloned and updated. Applying results before cloning ensures + /// that view contains up-to-date set of revalidated transactions. + /// + /// Refer to [*View revalidation*](../index.html#view-revalidation) for more details. + pub(super) async fn finish_revalidation(&self) { + log::trace!(target:LOG_TARGET,"view::finish_revalidation: at {}", self.at.hash); + let Some(revalidation_worker_channels) = self.revalidation_worker_channels.lock().take() + else { + log::trace!(target:LOG_TARGET, "view::finish_revalidation: no finish_revalidation_request_tx"); + return + }; + + let FinishRevalidationLocalChannels { + finish_revalidation_request_tx, + mut revalidation_result_rx, + } = revalidation_worker_channels; + + if let Some(finish_revalidation_request_tx) = finish_revalidation_request_tx { + if let Err(e) = finish_revalidation_request_tx.send(()).await { + log::trace!(target:LOG_TARGET, "view::finish_revalidation: sending cancellation request at {} failed {:?}", self.at.hash, e); + } + } + + if let Some(revalidation_result) = revalidation_result_rx.recv().await { + let start = Instant::now(); + let revalidated_len = revalidation_result.revalidated.len(); + let validated_pool = self.pool.validated_pool(); + validated_pool.remove_invalid(&revalidation_result.invalid_hashes); + if revalidated_len > 0 { + self.pool.resubmit(revalidation_result.revalidated); + } + + self.metrics.report(|metrics| { + let _ = ( + revalidation_result + .invalid_hashes + .len() + .try_into() + .map(|v| metrics.view_revalidation_invalid_txs.inc_by(v)), + revalidated_len + .try_into() + .map(|v| metrics.view_revalidation_resubmitted_txs.inc_by(v)), + ); + }); + + log::debug!( + target:LOG_TARGET, + "view::finish_revalidation: applying revalidation result invalid: {} revalidated: {} at {:?} took {:?}", + revalidation_result.invalid_hashes.len(), + revalidated_len, + self.at.hash, + start.elapsed() + ); + } + } +} diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs new file mode 100644 index 00000000000..953d6d86033 --- /dev/null +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs @@ -0,0 +1,468 @@ +// 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 . + +//! Transaction pool view store. Basically block hash to view map with some utility methods. + +use super::{ + multi_view_listener::{MultiViewListener, TxStatusStream}, + view::View, +}; +use crate::{ + fork_aware_txpool::dropped_watcher::MultiViewDroppedWatcherController, + graph, + graph::{base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, TransactionFor}, + ReadyIteratorFor, LOG_TARGET, +}; +use futures::prelude::*; +use parking_lot::RwLock; +use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus, TransactionSource}; +use sp_blockchain::TreeRoute; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use std::{collections::HashMap, sync::Arc, time::Instant}; + +/// The helper structure encapsulates all the views. +pub(super) struct ViewStore +where + Block: BlockT, + ChainApi: graph::ChainApi, +{ + /// The blockchain api. + pub(super) api: Arc, + /// Active views at tips of the forks. + /// + /// Active views are updated with incoming transactions. + pub(super) active_views: RwLock>>>, + /// Inactive views at intermediary blocks that are no longer tips of the forks. + /// + /// Inactive views are not updated with incoming transactions, while they can still be used to + /// build new blocks upon them. + pub(super) inactive_views: RwLock>>>, + /// Listener for controlling external watchers of transactions. + /// + /// Provides a side-channel allowing to send per-transaction state changes notification. + pub(super) listener: Arc>, + /// Most recent block processed by tx-pool. Used in the API functions that were not changed to + /// add `at` parameter. + pub(super) most_recent_view: RwLock>, + /// The controller of multi view dropped stream. + pub(super) dropped_stream_controller: MultiViewDroppedWatcherController, +} + +impl ViewStore +where + Block: BlockT, + ChainApi: graph::ChainApi + 'static, + ::Hash: Unpin, +{ + /// Creates a new empty view store. + pub(super) fn new( + api: Arc, + listener: Arc>, + dropped_stream_controller: MultiViewDroppedWatcherController, + ) -> Self { + Self { + api, + active_views: Default::default(), + inactive_views: Default::default(), + listener, + most_recent_view: RwLock::from(None), + dropped_stream_controller, + } + } + + /// Imports a bunch of unverified extrinsics to every active view. + pub(super) async fn submit( + &self, + source: TransactionSource, + xts: impl IntoIterator> + Clone, + xts_hashes: impl IntoIterator> + Clone, + ) -> HashMap, ChainApi::Error>>> { + let submit_futures = { + let active_views = self.active_views.read(); + active_views + .iter() + .map(|(_, view)| { + let view = view.clone(); + let xts = xts.clone(); + self.dropped_stream_controller + .add_initial_views(xts_hashes.clone(), view.at.hash); + async move { (view.at.hash, view.submit_many(source, xts.clone()).await) } + }) + .collect::>() + }; + let results = futures::future::join_all(submit_futures).await; + + HashMap::<_, _>::from_iter(results.into_iter()) + } + + /// Import a single extrinsic and starts to watch its progress in the pool. + /// + /// The extrinsic is imported to every view, and the individual streams providing the progress + /// of this transaction within every view are added to the multi view listener. + /// + /// The external stream of aggregated/processed events provided by the `MultiViewListener` + /// instance is returned. + pub(super) async fn submit_and_watch( + &self, + _at: Block::Hash, + source: TransactionSource, + xt: ExtrinsicFor, + ) -> Result, (ChainApi::Error, Option>)> { + let tx_hash = self.api.hash_and_length(&xt).0; + let Some(external_watcher) = self.listener.create_external_watcher_for_tx(tx_hash) else { + return Err((PoolError::AlreadyImported(Box::new(tx_hash)).into(), None)) + }; + let submit_and_watch_futures = { + let active_views = self.active_views.read(); + active_views + .iter() + .map(|(_, view)| { + let view = view.clone(); + let xt = xt.clone(); + self.dropped_stream_controller + .add_initial_views(std::iter::once(tx_hash), view.at.hash); + async move { + match view.submit_and_watch(source, xt).await { + Ok(watcher) => { + self.listener.add_view_watcher_for_tx( + tx_hash, + view.at.hash, + watcher.into_stream().boxed(), + ); + Ok(()) + }, + Err(e) => Err(e), + } + } + }) + .collect::>() + }; + let maybe_error = futures::future::join_all(submit_and_watch_futures) + .await + .into_iter() + .reduce(|mut r, v| { + if r.is_err() && v.is_ok() { + r = v; + } + r + }); + if let Some(Err(err)) = maybe_error { + log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); + return Err((err, Some(external_watcher))); + }; + + Ok(external_watcher) + } + + /// Returns the pool status for every active view. + pub(super) fn status(&self) -> HashMap { + self.active_views.read().iter().map(|(h, v)| (*h, v.status())).collect() + } + + /// Returns true if there are no active views. + pub(super) fn is_empty(&self) -> bool { + self.active_views.read().is_empty() && self.inactive_views.read().is_empty() + } + + /// Finds the best existing active view to clone from along the path. + /// + /// ```text + /// Tree route from R1 to E2. + /// <- R3 <- R2 <- R1 + /// / + /// C + /// \-> E1 -> E2 + /// ``` + /// ```text + /// Search path is: + /// [E1, C, R3, R2, R1] + /// ``` + pub(super) fn find_best_view( + &self, + tree_route: &TreeRoute, + ) -> Option>> { + let active_views = self.active_views.read(); + let best_view = { + tree_route + .retracted() + .iter() + .chain(std::iter::once(tree_route.common_block())) + .chain(tree_route.enacted().iter()) + .rev() + .find(|block| active_views.contains_key(&block.hash)) + }; + best_view.map(|h| { + active_views + .get(&h.hash) + .expect("hash was just found in the map's keys. qed") + .clone() + }) + } + + /// Returns an iterator for ready transactions for the most recently notified best block. + /// + /// The iterator for future transactions is returned if the most recently notified best block, + /// for which maintain process was accomplished, exists. + pub(super) fn ready(&self) -> ReadyIteratorFor { + let ready_iterator = self + .most_recent_view + .read() + .map(|at| self.get_view_at(at, true)) + .flatten() + .map(|(v, _)| v.pool.validated_pool().ready()); + + if let Some(ready_iterator) = ready_iterator { + return Box::new(ready_iterator) + } else { + return Box::new(std::iter::empty()) + } + } + + /// Returns a list of future transactions for the most recently notified best block. + /// + /// The set of future transactions is returned if the most recently notified best block, for + /// which maintain process was accomplished, exists. + pub(super) fn futures( + &self, + ) -> Vec, ExtrinsicFor>> { + self.most_recent_view + .read() + .map(|at| self.get_view_at(at, true)) + .flatten() + .map(|(v, _)| v.pool.validated_pool().pool.read().futures().cloned().collect()) + .unwrap_or_default() + } + + /// Collects all the transactions included in the blocks on the provided `tree_route` and + /// triggers finalization event for them. + /// + /// The finalization event is sent using side-channel of the multi view `listener`. + /// + /// Returns the list of finalized transactions hashes. + pub(super) async fn finalize_route( + &self, + finalized_hash: Block::Hash, + tree_route: &[Block::Hash], + ) -> Vec> { + log::trace!(target: LOG_TARGET, "finalize_route finalized_hash:{finalized_hash:?} tree_route: {tree_route:?}"); + + let mut finalized_transactions = Vec::new(); + + for block in tree_route.iter().chain(std::iter::once(&finalized_hash)) { + let extrinsics = self + .api + .block_body(*block) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Finalize route: error request: {}", e); + None + }) + .unwrap_or_default() + .iter() + .map(|e| self.api.hash_and_length(&e).0) + .collect::>(); + + extrinsics + .iter() + .enumerate() + .for_each(|(i, tx_hash)| self.listener.finalize_transaction(*tx_hash, *block, i)); + + finalized_transactions.extend(extrinsics); + } + + finalized_transactions + } + + /// Return specific ready transaction by hash, if there is one. + /// + /// Currently the ready transaction is returned if it exists for the most recently notified best + /// block (for which maintain process was accomplished). + pub(super) fn ready_transaction( + &self, + at: Block::Hash, + tx_hash: &ExtrinsicHash, + ) -> Option> { + self.active_views + .read() + .get(&at) + .and_then(|v| v.pool.validated_pool().ready_by_hash(tx_hash)) + } + + /// Inserts new view into the view store. + /// + /// All the views associated with the blocks which are on enacted path (including common + /// ancestor) will be: + /// - moved to the inactive views set (`inactive_views`), + /// - removed from the multi view listeners. + /// + /// The `most_recent_view` is update with the reference to the newly inserted view. + pub(super) async fn insert_new_view( + &self, + view: Arc>, + tree_route: &TreeRoute, + ) { + //note: most_recent_view must be synced with changes in in/active_views. + { + let mut most_recent_view_lock = self.most_recent_view.write(); + let mut active_views = self.active_views.write(); + let mut inactive_views = self.inactive_views.write(); + + std::iter::once(tree_route.common_block()) + .chain(tree_route.enacted().iter()) + .map(|block| block.hash) + .for_each(|hash| { + active_views.remove(&hash).map(|view| { + inactive_views.insert(hash, view); + }); + }); + active_views.insert(view.at.hash, view.clone()); + most_recent_view_lock.replace(view.at.hash); + }; + log::trace!(target:LOG_TARGET,"insert_new_view: inactive_views: {:?}", self.inactive_views.read().keys()); + } + + /// Returns an optional reference to the view at given hash. + /// + /// If `allow_retracted` flag is set, inactive views are also searched. + /// + /// If the view at provided hash does not exist `None` is returned. + pub(super) fn get_view_at( + &self, + at: Block::Hash, + allow_inactive: bool, + ) -> Option<(Arc>, bool)> { + if let Some(view) = self.active_views.read().get(&at) { + return Some((view.clone(), false)); + } + if allow_inactive { + if let Some(view) = self.inactive_views.read().get(&at) { + return Some((view.clone(), true)) + } + }; + None + } + + /// The pre-finalization event handle for the view store. + /// + /// This function removes the references to the views that will be removed during finalization + /// from the dropped stream controller. This will allow for correct dispatching of `Dropped` + /// events. + pub(crate) async fn handle_pre_finalized(&self, finalized_hash: Block::Hash) { + let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); + let mut removed_views = vec![]; + + { + self.active_views + .read() + .iter() + .filter(|(hash, v)| !match finalized_number { + Err(_) | Ok(None) => **hash == finalized_hash, + Ok(Some(n)) if v.at.number == n => **hash == finalized_hash, + Ok(Some(n)) => v.at.number > n, + }) + .map(|(_, v)| removed_views.push(v.at.hash)) + .for_each(drop); + } + + { + self.inactive_views + .read() + .iter() + .filter(|(_, v)| !match finalized_number { + Err(_) | Ok(None) => false, + Ok(Some(n)) => v.at.number >= n, + }) + .map(|(_, v)| removed_views.push(v.at.hash)) + .for_each(drop); + } + + log::trace!(target:LOG_TARGET,"handle_pre_finalized: removed_views: {:?}", removed_views); + + removed_views.iter().for_each(|view| { + self.dropped_stream_controller.remove_view(*view); + }); + } + + /// The finalization event handle for the view store. + /// + /// Views that have associated block number less than finalized block number are removed from + /// both active and inactive set. + /// + /// Note: the views with the associated number greater than finalized block number on the forks + /// that are not finalized will stay in the view store. They will be removed in the future, once + /// new finalized blocks will be notified. This is to avoid scanning for common ancestors. + /// + /// All watched transactions in the blocks from the tree_route will be notified with `Finalized` + /// event. + /// + /// Returns the list of hashes of all finalized transactions along the provided `tree_route`. + pub(crate) async fn handle_finalized( + &self, + finalized_hash: Block::Hash, + tree_route: &[Block::Hash], + ) -> Vec> { + let finalized_xts = self.finalize_route(finalized_hash, tree_route).await; + let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); + + //clean up older then finalized + { + let mut active_views = self.active_views.write(); + active_views.retain(|hash, v| match finalized_number { + Err(_) | Ok(None) => *hash == finalized_hash, + Ok(Some(n)) if v.at.number == n => *hash == finalized_hash, + Ok(Some(n)) => v.at.number > n, + }); + } + + { + let mut inactive_views = self.inactive_views.write(); + inactive_views.retain(|_, v| match finalized_number { + Err(_) | Ok(None) => false, + Ok(Some(n)) => v.at.number >= n, + }); + + log::trace!(target:LOG_TARGET,"handle_finalized: inactive_views: {:?}", inactive_views.keys()); + } + + self.listener.remove_view(finalized_hash); + self.listener.remove_stale_controllers(); + self.dropped_stream_controller.remove_finalized_txs(finalized_xts.clone()); + + finalized_xts + } + + /// Terminates all the ongoing background views revalidations triggered at the end of maintain + /// process. + /// + /// Refer to [*View revalidation*](../index.html#view-revalidation) for more details. + pub(crate) async fn finish_background_revalidations(&self) { + let start = Instant::now(); + let finish_revalidation_futures = { + let active_views = self.active_views.read(); + active_views + .iter() + .map(|(_, view)| { + let view = view.clone(); + async move { view.finish_revalidation().await } + }) + .collect::>() + }; + futures::future::join_all(finish_revalidation_futures).await; + log::trace!(target:LOG_TARGET,"finish_background_revalidations took {:?}", start.elapsed()); + } +} diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index 32885622da4..e4c3a6c425a 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -23,7 +23,7 @@ use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc}; use crate::LOG_TARGET; -use log::{debug, trace, warn}; +use log::{trace, warn}; use sc_transaction_pool_api::{error, InPoolTransaction, PoolStatus}; use serde::Serialize; use sp_core::hexdisplay::HexDisplay; @@ -207,7 +207,7 @@ const RECENTLY_PRUNED_TAGS: usize = 2; /// as-is for the second time will fail or produce unwanted results. /// Most likely it is required to revalidate them and recompute set of /// required tags. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct BasePool { reject_future_transactions: bool, future: FutureTransactions, @@ -238,6 +238,12 @@ impl BasePool BasePool BasePool if first { - debug!(target: LOG_TARGET, "[{:?}] Error importing: {:?}", current_hash, e); + trace!(target: LOG_TARGET, "[{:?}] Error importing: {:?}", current_hash, e); return Err(e) } else { failed.push(current_hash); @@ -347,7 +353,7 @@ impl BasePool BasePool Vec>> { let mut removed = self.ready.remove_subtree(hashes); @@ -463,8 +469,8 @@ impl BasePool) -> PruneStatus { @@ -474,6 +480,9 @@ impl BasePool>(); + let futures_removed = self.future.prune_tags(&tags); + for tag in tags { // make sure to promote any future transactions that could be unlocked to_import.append(&mut self.future.satisfy_tags(std::iter::once(&tag))); @@ -485,6 +494,10 @@ impl BasePool> = Transaction { - data: vec![], - bytes: 1, - hash: 1u64, - priority: 5u64, - valid_till: 64u64, - requires: vec![], - provides: vec![], - propagate: true, - source: Source::External, - }; + fn default_tx() -> Transaction> { + Transaction { + data: vec![], + bytes: 1, + hash: 1u64, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![], + propagate: true, + source: Source::External, + } + } + + #[test] + fn prune_for_ready_works() { + // given + let mut pool = pool(); + + // when + pool.import(Transaction { + data: vec![1u8].into(), + provides: vec![vec![2]], + ..default_tx().clone() + }) + .unwrap(); + + // then + assert_eq!(pool.ready().count(), 1); + assert_eq!(pool.ready.len(), 1); + + let result = pool.prune_tags(vec![vec![2]]); + assert_eq!(pool.ready().count(), 0); + assert_eq!(pool.ready.len(), 0); + assert_eq!(result.pruned.len(), 1); + assert_eq!(result.failed.len(), 0); + assert_eq!(result.promoted.len(), 0); + } + + #[test] + fn prune_for_future_works() { + // given + let mut pool = pool(); + + // when + pool.import(Transaction { + data: vec![1u8].into(), + requires: vec![vec![1]], + provides: vec![vec![2]], + hash: 0xaa, + ..default_tx().clone() + }) + .unwrap(); + + // then + assert_eq!(pool.futures().count(), 1); + assert_eq!(pool.future.len(), 1); + + let result = pool.prune_tags(vec![vec![2]]); + assert_eq!(pool.ready().count(), 0); + assert_eq!(pool.ready.len(), 0); + assert_eq!(pool.futures().count(), 0); + assert_eq!(pool.future.len(), 0); + + assert_eq!(result.pruned.len(), 0); + assert_eq!(result.failed.len(), 1); + assert_eq!(result.failed[0], 0xaa); + assert_eq!(result.promoted.len(), 0); + } #[test] fn should_import_transaction_to_ready() { @@ -557,8 +628,12 @@ mod tests { let mut pool = pool(); // when - pool.import(Transaction { data: vec![1u8], provides: vec![vec![1]], ..DEFAULT_TX.clone() }) - .unwrap(); + pool.import(Transaction { + data: vec![1u8].into(), + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap(); // then assert_eq!(pool.ready().count(), 1); @@ -571,10 +646,18 @@ mod tests { let mut pool = pool(); // when - pool.import(Transaction { data: vec![1u8], provides: vec![vec![1]], ..DEFAULT_TX.clone() }) - .unwrap(); - pool.import(Transaction { data: vec![1u8], provides: vec![vec![1]], ..DEFAULT_TX.clone() }) - .unwrap_err(); + pool.import(Transaction { + data: vec![1u8].into(), + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap(); + pool.import(Transaction { + data: vec![1u8].into(), + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap_err(); // then assert_eq!(pool.ready().count(), 1); @@ -588,19 +671,19 @@ mod tests { // when pool.import(Transaction { - data: vec![1u8], + data: vec![1u8].into(), requires: vec![vec![0]], provides: vec![vec![1]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); assert_eq!(pool.ready().count(), 0); assert_eq!(pool.ready.len(), 0); pool.import(Transaction { - data: vec![2u8], + data: vec![2u8].into(), hash: 2, provides: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -616,33 +699,33 @@ mod tests { // when pool.import(Transaction { - data: vec![1u8], + data: vec![1u8].into(), requires: vec![vec![0]], provides: vec![vec![1]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![3u8], + data: vec![3u8].into(), hash: 3, requires: vec![vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![2u8], + data: vec![2u8].into(), hash: 2, requires: vec![vec![1]], provides: vec![vec![3], vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); assert_eq!(pool.ready().count(), 0); @@ -650,10 +733,10 @@ mod tests { let res = pool .import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, provides: vec![vec![0], vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -682,18 +765,18 @@ mod tests { // given let mut pool = pool(); pool.import(Transaction { - data: vec![1u8], + data: vec![1u8].into(), requires: vec![vec![0]], provides: vec![vec![1]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![3u8], + data: vec![3u8].into(), hash: 3, requires: vec![vec![1]], provides: vec![vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); assert_eq!(pool.ready().count(), 0); @@ -701,11 +784,11 @@ mod tests { // when pool.import(Transaction { - data: vec![2u8], + data: vec![2u8].into(), hash: 2, requires: vec![vec![2]], provides: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -720,11 +803,11 @@ mod tests { // let's close the cycle with one additional transaction let res = pool .import(Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 50u64, provides: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); @@ -744,18 +827,18 @@ mod tests { // given let mut pool = pool(); pool.import(Transaction { - data: vec![1u8], + data: vec![1u8].into(), requires: vec![vec![0]], provides: vec![vec![1]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![3u8], + data: vec![3u8].into(), hash: 3, requires: vec![vec![1]], provides: vec![vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); assert_eq!(pool.ready().count(), 0); @@ -763,11 +846,11 @@ mod tests { // when pool.import(Transaction { - data: vec![2u8], + data: vec![2u8].into(), hash: 2, requires: vec![vec![2]], provides: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -782,11 +865,11 @@ mod tests { // let's close the cycle with one additional transaction let err = pool .import(Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1u64, // lower priority than Tx(2) provides: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap_err(); let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); @@ -804,49 +887,49 @@ mod tests { // given let mut pool = pool(); pool.import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, provides: vec![vec![0], vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![1u8], + data: vec![1u8].into(), requires: vec![vec![0]], provides: vec![vec![1]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![3u8], + data: vec![3u8].into(), hash: 3, requires: vec![vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![2u8], + data: vec![2u8].into(), hash: 2, requires: vec![vec![1]], provides: vec![vec![3], vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); // future pool.import(Transaction { - data: vec![6u8], + data: vec![6u8].into(), hash: 6, priority: 1_000u64, requires: vec![vec![11]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); assert_eq!(pool.ready().count(), 5); @@ -866,39 +949,43 @@ mod tests { let mut pool = pool(); // future (waiting for 0) pool.import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, requires: vec![vec![0]], provides: vec![vec![100]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); // ready - pool.import(Transaction { data: vec![1u8], provides: vec![vec![1]], ..DEFAULT_TX.clone() }) - .unwrap(); pool.import(Transaction { - data: vec![2u8], + data: vec![1u8].into(), + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap(); + pool.import(Transaction { + data: vec![2u8].into(), hash: 2, requires: vec![vec![2]], provides: vec![vec![3]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![3u8], + data: vec![3u8].into(), hash: 3, requires: vec![vec![1]], provides: vec![vec![2]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); pool.import(Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![2]], provides: vec![vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -927,12 +1014,12 @@ mod tests { format!( "{:?}", Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![2]], provides: vec![vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() } ), "Transaction { \ @@ -946,12 +1033,12 @@ source: TransactionSource::External, requires: [03, 02], provides: [04], data: [ fn transaction_propagation() { assert_eq!( Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![2]], provides: vec![vec![4]], - ..DEFAULT_TX.clone() + ..default_tx().clone() } .is_propagable(), true @@ -959,13 +1046,13 @@ source: TransactionSource::External, requires: [03, 02], provides: [04], data: [ assert_eq!( Transaction { - data: vec![4u8], + data: vec![4u8].into(), hash: 4, priority: 1_000u64, requires: vec![vec![3], vec![2]], provides: vec![vec![4]], propagate: false, - ..DEFAULT_TX.clone() + ..default_tx().clone() } .is_propagable(), false @@ -982,10 +1069,10 @@ source: TransactionSource::External, requires: [03, 02], provides: [04], data: [ // then let err = pool.import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, requires: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }); if let Err(error::Error::RejectedFutureTransaction) = err { @@ -1001,10 +1088,10 @@ source: TransactionSource::External, requires: [03, 02], provides: [04], data: [ // when pool.import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, requires: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); @@ -1027,10 +1114,10 @@ source: TransactionSource::External, requires: [03, 02], provides: [04], data: [ // when let flag_value = pool.with_futures_enabled(|pool, flag| { pool.import(Transaction { - data: vec![5u8], + data: vec![5u8].into(), hash: 5, requires: vec![vec![0]], - ..DEFAULT_TX.clone() + ..default_tx().clone() }) .unwrap(); diff --git a/substrate/client/transaction-pool/src/graph/future.rs b/substrate/client/transaction-pool/src/graph/future.rs index bad46631848..2c1e64c04b7 100644 --- a/substrate/client/transaction-pool/src/graph/future.rs +++ b/substrate/client/transaction-pool/src/graph/future.rs @@ -27,6 +27,7 @@ use sp_runtime::transaction_validity::TransactionTag as Tag; use std::time::Instant; use super::base_pool::Transaction; +use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; /// Transaction with partially satisfied dependencies. pub struct WaitingTransaction { @@ -105,11 +106,11 @@ impl WaitingTransaction { /// A pool of transactions that are not yet ready to be included in the block. /// -/// Contains transactions that are still awaiting for some other transactions that +/// Contains transactions that are still awaiting some other transactions that /// could provide a tag that they require. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct FutureTransactions { - /// tags that are not yet provided by any transaction and we await for them + /// tags that are not yet provided by any transaction, and we await for them wanted_tags: HashMap>, /// Transactions waiting for a particular other transaction waiting: HashMap>, @@ -128,7 +129,9 @@ every hash from `wanted_tags` is always present in `waiting`; qed #"; -impl FutureTransactions { +impl + FutureTransactions +{ /// Import transaction to Future queue. /// /// Only transactions that don't have all their tags satisfied should occupy @@ -165,10 +168,30 @@ impl FutureTransactions { .collect() } + /// Removes transactions that provide any of tags in the given list. + /// + /// Returns list of removed transactions. + pub fn prune_tags(&mut self, tags: &Vec) -> Vec>> { + let pruned = self + .waiting + .values() + .filter_map(|tx| { + tx.transaction + .provides + .iter() + .any(|provided_tag| tags.contains(provided_tag)) + .then(|| tx.transaction.hash.clone()) + }) + .collect::>(); + + log_xt_trace!(target: LOG_TARGET, &pruned, "[{:?}] FutureTransactions: removed while pruning tags."); + self.remove(&pruned) + } + /// Satisfies provided tags in transactions that are waiting for them. /// /// Returns (and removes) transactions that became ready after their last tag got - /// satisfied and now we can remove them from Future and move to Ready queue. + /// satisfied, and now we can remove them from Future and move to Ready queue. pub fn satisfy_tags>( &mut self, tags: impl IntoIterator, @@ -218,6 +241,7 @@ impl FutureTransactions { removed.push(waiting_tx.transaction) } } + removed } diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index 46b7957e0b3..a5593920eec 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -18,18 +18,31 @@ use std::{collections::HashMap, fmt::Debug, hash}; -use crate::LOG_TARGET; use linked_hash_map::LinkedHashMap; -use log::{debug, trace}; +use log::trace; +use sc_transaction_pool_api::TransactionStatus; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use serde::Serialize; use sp_runtime::traits; use super::{watcher, BlockHash, ChainApi, ExtrinsicHash}; +static LOG_TARGET: &str = "txpool::watcher"; + +/// Single event used in dropped by limits stream. It is one of Ready/Future/Dropped. +pub type DroppedByLimitsEvent = (H, TransactionStatus); +/// Stream of events used to determine if a transaction was dropped. +pub type DroppedByLimitsStream = TracingUnboundedReceiver>; + /// Extrinsic pool default listener. pub struct Listener { - watchers: HashMap>>, + watchers: HashMap>>, finality_watchers: LinkedHashMap, Vec>, + + /// The sink used to notify dropped-by-enforcing-limits transactions. Also ready and future + /// statuses are reported via this channel to allow consumer of the stream tracking actual + /// drops. + dropped_by_limits_sink: Option>>>, } /// Maximum number of blocks awaiting finality at any time. @@ -37,11 +50,15 @@ const MAX_FINALITY_WATCHERS: usize = 512; impl Default for Listener { fn default() -> Self { - Self { watchers: Default::default(), finality_watchers: Default::default() } + Self { + watchers: Default::default(), + finality_watchers: Default::default(), + dropped_by_limits_sink: None, + } } } -impl Listener { +impl Listener { fn fire(&mut self, hash: &H, fun: F) where F: FnOnce(&mut watcher::Sender>), @@ -66,6 +83,15 @@ impl Listener { sender.new_watcher(hash) } + /// Creates a new single stream for entire pool. + /// + /// The stream can be used to subscribe to life-cycle events of all extrinsics in the pool. + pub fn create_dropped_by_limits_stream(&mut self) -> DroppedByLimitsStream> { + let (sender, single_stream) = tracing_unbounded("mpsc_txpool_watcher", 100_000); + self.dropped_by_limits_sink = Some(sender); + single_stream + } + /// Notify the listeners about extrinsic broadcast. pub fn broadcasted(&mut self, hash: &H, peers: Vec) { trace!(target: LOG_TARGET, "[{:?}] Broadcasted", hash); @@ -79,32 +105,55 @@ impl Listener { if let Some(old) = old { self.fire(old, |watcher| watcher.usurped(tx.clone())); } + + if let Some(ref sink) = self.dropped_by_limits_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Ready)) { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink/ready: send message failed: {:?}", tx, e); + } + } } /// New transaction was added to the future pool. pub fn future(&mut self, tx: &H) { trace!(target: LOG_TARGET, "[{:?}] Future", tx); self.fire(tx, |watcher| watcher.future()); + if let Some(ref sink) = self.dropped_by_limits_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Future)) { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink/future: send message failed: {:?}", tx, e); + } + } } /// Transaction was dropped from the pool because of the limit. - pub fn dropped(&mut self, tx: &H, by: Option<&H>) { + /// + /// If the function was actually called due to enforcing limits, the `limits_enforced` flag + /// shall be set to true. + pub fn dropped(&mut self, tx: &H, by: Option<&H>, limits_enforced: bool) { trace!(target: LOG_TARGET, "[{:?}] Dropped (replaced with {:?})", tx, by); self.fire(tx, |watcher| match by { Some(t) => watcher.usurped(t.clone()), None => watcher.dropped(), - }) + }); + + //note: LimitEnforced could be introduced as new status to get rid of this flag. + if limits_enforced { + if let Some(ref sink) = self.dropped_by_limits_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Dropped)) { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink/future: send message failed: {:?}", tx, e); + } + } + } } /// Transaction was removed as invalid. pub fn invalid(&mut self, tx: &H) { - debug!(target: LOG_TARGET, "[{:?}] Extrinsic invalid", tx); + trace!(target: LOG_TARGET, "[{:?}] Extrinsic invalid", tx); self.fire(tx, |watcher| watcher.invalid()); } /// Transaction was pruned from the pool. pub fn pruned(&mut self, block_hash: BlockHash, tx: &H) { - debug!(target: LOG_TARGET, "[{:?}] Pruned at {:?}", tx, block_hash); + trace!(target: LOG_TARGET, "[{:?}] Pruned at {:?}", tx, block_hash); // Get the transactions included in the given block hash. let txs = self.finality_watchers.entry(block_hash).or_insert(vec![]); txs.push(tx.clone()); @@ -135,7 +184,7 @@ impl Listener { pub fn finalized(&mut self, block_hash: BlockHash) { if let Some(hashes) = self.finality_watchers.remove(&block_hash) { for (tx_index, hash) in hashes.into_iter().enumerate() { - log::debug!( + log::trace!( target: LOG_TARGET, "[{:?}] Sent finalization event (block {:?})", hash, @@ -145,4 +194,9 @@ impl Listener { } } } + + /// Provides hashes of all watched transactions. + pub fn watched_transactions(&self) -> impl Iterator { + self.watchers.keys() + } } diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index 484a6d6cf9f..c1225d7356d 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -37,8 +37,10 @@ mod validated_pool; pub mod base_pool; pub mod watcher; -pub use self::{ - base_pool::Transaction, - pool::{BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool}, +pub use self::pool::{ + BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool, RawExtrinsicFor, + TransactionFor, ValidatedTransactionFor, }; pub use validated_pool::{IsValidator, ValidatedTransaction}; + +pub(crate) use listener::DroppedByLimitsEvent; diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index 5305b5f1c12..6d08a0f0b93 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -16,12 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::HashMap, sync::Arc, time::Duration}; - -use crate::LOG_TARGET; +use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::{channel::mpsc::Receiver, Future}; +use indexmap::IndexMap; use sc_transaction_pool_api::error; -use sp_blockchain::TreeRoute; +use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_runtime::{ generic::BlockId, traits::{self, Block as BlockT, SaturatedConversion}, @@ -29,7 +28,11 @@ use sp_runtime::{ TransactionSource, TransactionTag as Tag, TransactionValidity, TransactionValidityError, }, }; -use std::time::Instant; +use std::{ + collections::HashMap, + sync::Arc, + time::{Duration, Instant}, +}; use super::{ base_pool as base, @@ -44,8 +47,10 @@ pub type EventStream = Receiver; pub type BlockHash = <::Block as traits::Block>::Hash; /// Extrinsic hash type for a pool. pub type ExtrinsicHash = <::Block as traits::Block>::Hash; -/// Extrinsic type for a pool. -pub type ExtrinsicFor = <::Block as traits::Block>::Extrinsic; +/// Extrinsic type for a pool (reference counted). +pub type ExtrinsicFor = Arc<<::Block as traits::Block>::Extrinsic>; +/// Extrinsic type for a pool (raw data). +pub type RawExtrinsicFor = <::Block as traits::Block>::Extrinsic; /// Block number type for the ChainApi pub type NumberFor = traits::NumberFor<::Block>; /// A type of transaction stored in the pool @@ -89,7 +94,7 @@ pub trait ChainApi: Send + Sync { ) -> Result::Hash>, Self::Error>; /// Returns hash and encoding length of the extrinsic. - fn hash_and_length(&self, uxt: &ExtrinsicFor) -> (ExtrinsicHash, usize); + fn hash_and_length(&self, uxt: &RawExtrinsicFor) -> (ExtrinsicHash, usize); /// Returns a block body given the block. fn block_body(&self, at: ::Hash) -> Self::BodyFuture; @@ -106,6 +111,16 @@ pub trait ChainApi: Send + Sync { from: ::Hash, to: ::Hash, ) -> Result, Self::Error>; + + /// Resolves block number by id. + fn resolve_block_number( + &self, + at: ::Hash, + ) -> Result, Self::Error> { + self.block_id_to_number(&BlockId::Hash(at)).and_then(|number| { + number.ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)).into()) + }) + } } /// Pool configuration options. @@ -154,13 +169,13 @@ impl Pool { /// Imports a bunch of unverified extrinsics to the pool pub async fn submit_at( &self, - at: ::Hash, + at: &HashAndNumber, source: TransactionSource, xts: impl IntoIterator>, - ) -> Result, B::Error>>, B::Error> { + ) -> Vec, B::Error>> { let xts = xts.into_iter().map(|xt| (source, xt)); - let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await?; - Ok(self.validated_pool.submit(validated_transactions.into_values())) + let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await; + self.validated_pool.submit(validated_transactions.into_values()) } /// Resubmit the given extrinsics to the pool. @@ -168,36 +183,35 @@ impl Pool { /// This does not check if a transaction is banned, before we verify it again. pub async fn resubmit_at( &self, - at: ::Hash, + at: &HashAndNumber, source: TransactionSource, xts: impl IntoIterator>, - ) -> Result, B::Error>>, B::Error> { + ) -> Vec, B::Error>> { let xts = xts.into_iter().map(|xt| (source, xt)); - let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await?; - Ok(self.validated_pool.submit(validated_transactions.into_values())) + let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await; + self.validated_pool.submit(validated_transactions.into_values()) } /// Imports one unverified extrinsic to the pool pub async fn submit_one( &self, - at: ::Hash, + at: &HashAndNumber, source: TransactionSource, xt: ExtrinsicFor, ) -> Result, B::Error> { - let res = self.submit_at(at, source, std::iter::once(xt)).await?.pop(); + let res = self.submit_at(at, source, std::iter::once(xt)).await.pop(); res.expect("One extrinsic passed; one result returned; qed") } /// Import a single extrinsic and starts to watch its progress in the pool. pub async fn submit_and_watch( &self, - at: ::Hash, + at: &HashAndNumber, source: TransactionSource, xt: ExtrinsicFor, ) -> Result, ExtrinsicHash>, B::Error> { - let block_number = self.resolve_block_number(&BlockId::Hash(at))?; let (_, tx) = self - .verify_one(at, block_number, source, xt, CheckBannedBeforeVerify::Yes) + .verify_one(at.hash, at.number, source, xt, CheckBannedBeforeVerify::Yes) .await; self.validated_pool.submit_and_watch(tx) } @@ -209,7 +223,7 @@ impl Pool { ) { let now = Instant::now(); self.validated_pool.resubmit(revalidated_transactions); - log::debug!( + log::trace!( target: LOG_TARGET, "Resubmitted. Took {} ms. Status: {:?}", now.elapsed().as_millis(), @@ -222,34 +236,30 @@ impl Pool { /// Used to clear the pool from transactions that were part of recently imported block. /// The main difference from the `prune` is that we do not revalidate any transactions /// and ignore unknown passed hashes. - pub fn prune_known( - &self, - at: &BlockId, - hashes: &[ExtrinsicHash], - ) -> Result<(), B::Error> { + pub fn prune_known(&self, at: &HashAndNumber, hashes: &[ExtrinsicHash]) { // Get details of all extrinsics that are already in the pool let in_pool_tags = self.validated_pool.extrinsics_tags(hashes).into_iter().flatten().flatten(); // Prune all transactions that provide given tags - let prune_status = self.validated_pool.prune_tags(in_pool_tags)?; + let prune_status = self.validated_pool.prune_tags(in_pool_tags); let pruned_transactions = hashes.iter().cloned().chain(prune_status.pruned.iter().map(|tx| tx.hash)); - self.validated_pool.fire_pruned(at, pruned_transactions) + self.validated_pool.fire_pruned(at, pruned_transactions); } /// Prunes ready transactions. /// /// Used to clear the pool from transactions that were part of recently imported block. /// To perform pruning we need the tags that each extrinsic provides and to avoid calling - /// into runtime too often we first lookup all extrinsics that are in the pool and get + /// into runtime too often we first look up all extrinsics that are in the pool and get /// their provided tags from there. Otherwise we query the runtime at the `parent` block. pub async fn prune( &self, - at: ::Hash, + at: &HashAndNumber, parent: ::Hash, - extrinsics: &[ExtrinsicFor], - ) -> Result<(), B::Error> { + extrinsics: &[RawExtrinsicFor], + ) { log::debug!( target: LOG_TARGET, "Starting pruning of block {:?} (extrinsics: {})", @@ -264,6 +274,7 @@ impl Pool { // Zip the ones from the pool with the full list (we get pairs `(Extrinsic, // Option>)`) let all = extrinsics.iter().zip(in_pool_tags.into_iter()); + let mut validated_counter: usize = 0; let mut future_tags = Vec::new(); for (extrinsic, in_pool_tags) in all { @@ -275,16 +286,19 @@ impl Pool { None => { // Avoid validating block txs if the pool is empty if !self.validated_pool.status().is_empty() { + validated_counter = validated_counter + 1; let validity = self .validated_pool .api() .validate_transaction( parent, TransactionSource::InBlock, - extrinsic.clone(), + Arc::from(extrinsic.clone()), ) .await; + log::trace!(target: LOG_TARGET,"[{:?}] prune::revalidated {:?}", self.validated_pool.api().hash_and_length(&extrinsic.clone()).0, validity); + if let Ok(Ok(validity)) = validity { future_tags.extend(validity.provides); } @@ -298,6 +312,8 @@ impl Pool { } } + log::trace!(target: LOG_TARGET,"prune: validated_counter:{validated_counter}"); + self.prune_tags(at, future_tags, in_pool_hashes).await } @@ -324,13 +340,13 @@ impl Pool { /// prevent importing them in the (near) future. pub async fn prune_tags( &self, - at: ::Hash, + at: &HashAndNumber, tags: impl IntoIterator, known_imported_hashes: impl IntoIterator> + Clone, - ) -> Result<(), B::Error> { - log::debug!(target: LOG_TARGET, "Pruning at {:?}", at); + ) { + log::trace!(target: LOG_TARGET, "Pruning at {:?}", at); // Prune all transactions that provide given tags - let prune_status = self.validated_pool.prune_tags(tags)?; + let prune_status = self.validated_pool.prune_tags(tags); // Make sure that we don't revalidate extrinsics that were part of the recently // imported block. This is especially important for UTXO-like chains cause the @@ -340,18 +356,20 @@ impl Pool { // Try to re-validate pruned transactions since some of them might be still valid. // note that `known_imported_hashes` will be rejected here due to temporary ban. - let pruned_hashes = prune_status.pruned.iter().map(|tx| tx.hash).collect::>(); let pruned_transactions = prune_status.pruned.into_iter().map(|tx| (tx.source, tx.data.clone())); let reverified_transactions = - self.verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes).await?; + self.verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes).await; - log::trace!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions.", at); - // And finally - submit reverified transactions back to the pool + let pruned_hashes = reverified_transactions.keys().map(Clone::clone).collect(); + + log::trace!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions: {}", &at, reverified_transactions.len()); + log_xt_trace!(data: tuple, target: LOG_TARGET, &reverified_transactions, "[{:?}] Resubmitting transaction: {:?}"); + // And finally - submit reverified transactions back to the pool self.validated_pool.resubmit_pruned( - &BlockId::Hash(at), + &at, known_imported_hashes, pruned_hashes, reverified_transactions.into_values().collect(), @@ -359,36 +377,28 @@ impl Pool { } /// Returns transaction hash - pub fn hash_of(&self, xt: &ExtrinsicFor) -> ExtrinsicHash { + pub fn hash_of(&self, xt: &RawExtrinsicFor) -> ExtrinsicHash { self.validated_pool.api().hash_and_length(xt).0 } - /// Resolves block number by id. - fn resolve_block_number(&self, at: &BlockId) -> Result, B::Error> { - self.validated_pool.api().block_id_to_number(at).and_then(|number| { - number.ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)).into()) - }) - } - /// Returns future that validates a bunch of transactions at given block. async fn verify( &self, - at: ::Hash, + at: &HashAndNumber, xts: impl IntoIterator)>, check: CheckBannedBeforeVerify, - ) -> Result, ValidatedTransactionFor>, B::Error> { - // we need a block number to compute tx validity - let block_number = self.resolve_block_number(&BlockId::Hash(at))?; + ) -> IndexMap, ValidatedTransactionFor> { + let HashAndNumber { number, hash } = *at; let res = futures::future::join_all( xts.into_iter() - .map(|(source, xt)| self.verify_one(at, block_number, source, xt, check)), + .map(|(source, xt)| self.verify_one(hash, number, source, xt, check)), ) .await .into_iter() - .collect::>(); + .collect::>(); - Ok(res) + res } /// Returns future that validates single transaction at given block. @@ -441,22 +451,31 @@ impl Pool { (hash, validity) } - /// get a reference to the underlying validated pool. + /// Get a reference to the underlying validated pool. pub fn validated_pool(&self) -> &ValidatedPool { &self.validated_pool } + + /// Clears the recently pruned transactions in validated pool. + pub fn clear_recently_pruned(&mut self) { + self.validated_pool.pool.write().clear_recently_pruned(); + } } -impl Clone for Pool { - fn clone(&self) -> Self { - Self { validated_pool: self.validated_pool.clone() } +impl Pool { + /// Deep clones the pool. + /// + /// Must be called on purpose: it duplicates all the internal structures. + pub fn deep_clone(&self) -> Self { + let other: ValidatedPool = (*self.validated_pool).clone(); + Self { validated_pool: Arc::from(other) } } } #[cfg(test)] mod tests { use super::{super::base_pool::Limit, *}; - use crate::tests::{pool, uxt, TestApi, INVALID_NONCE}; + use crate::common::tests::{pool, uxt, TestApi, INVALID_NONCE}; use assert_matches::assert_matches; use codec::Encode; use futures::executor::block_on; @@ -475,22 +494,58 @@ mod tests { let (pool, api) = pool(); // when - let hash = block_on(pool.submit_one( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let hash = block_on( + pool.submit_one( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); // then assert_eq!(pool.validated_pool().ready().map(|v| v.hash).collect::>(), vec![hash]); } + #[test] + fn submit_at_preserves_order() { + sp_tracing::try_init_simple(); + // given + let (pool, api) = pool(); + + let txs = (0..10) + .map(|i| { + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(i)), + amount: 5, + nonce: i, + }) + .into() + }) + .collect::>(); + + let initial_hashes = txs.iter().map(|t| api.hash_and_length(t).0).collect::>(); + + // when + let txs = txs.into_iter().map(|x| Arc::from(x)).collect::>(); + let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), SOURCE, txs)); + log::debug!("--> {hashes:#?}"); + + // then + hashes.into_iter().zip(initial_hashes.into_iter()).for_each( + |(result_hash, initial_hash)| { + assert_eq!(result_hash.unwrap(), initial_hash); + }, + ); + } + #[test] fn should_reject_if_temporarily_banned() { // given @@ -504,7 +559,7 @@ mod tests { // when pool.validated_pool.ban(&Instant::now(), vec![pool.hash_of(&uxt)]); - let res = block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt)); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -527,7 +582,7 @@ mod tests { let uxt = ExtrinsicBuilder::new_include_data(vec![42]).build(); // when - let res = block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt)); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); // then assert_matches!(res.unwrap_err(), error::Error::Unactionable); @@ -538,43 +593,52 @@ mod tests { let (stream, hash0, hash1) = { // given let (pool, api) = pool(); - let hash_of_block0 = api.expect_hash_from_number(0); + let han_of_block0 = api.expect_hash_and_number(0); let stream = pool.validated_pool().import_notification_stream(); // when - let hash0 = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let hash0 = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); - let hash1 = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 1, - }), - )) + let hash1 = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 1, + }) + .into(), + ), + ) .unwrap(); // future doesn't count - let _hash = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 3, - }), - )) + let _hash = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 3, + }) + .into(), + ), + ) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 2); @@ -594,43 +658,52 @@ mod tests { fn should_clear_stale_transactions() { // given let (pool, api) = pool(); - let hash_of_block0 = api.expect_hash_from_number(0); - let hash1 = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let han_of_block0 = api.expect_hash_and_number(0); + let hash1 = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); - let hash2 = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 1, - }), - )) + let hash2 = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 1, + }) + .into(), + ), + ) .unwrap(); - let hash3 = block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 3, - }), - )) + let hash3 = block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 3, + }) + .into(), + ), + ) .unwrap(); // when - pool.validated_pool.clear_stale(&BlockId::Number(5)).unwrap(); + pool.validated_pool.clear_stale(&api.expect_hash_and_number(5)); // then assert_eq!(pool.validated_pool().ready().count(), 0); @@ -646,21 +719,23 @@ mod tests { fn should_ban_mined_transactions() { // given let (pool, api) = pool(); - let hash1 = block_on(pool.submit_one( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let hash1 = block_on( + pool.submit_one( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); // when - block_on(pool.prune_tags(api.expect_hash_from_number(1), vec![vec![0]], vec![hash1])) - .unwrap(); + block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![0]], vec![hash1])); // then assert!(pool.validated_pool.is_banned(&hash1)); @@ -685,20 +760,24 @@ mod tests { let api = Arc::new(TestApi::default()); let pool = Pool::new(options, true.into(), api.clone()); - let hash1 = block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, xt)).unwrap(); + let hash1 = + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())).unwrap(); assert_eq!(pool.validated_pool().status().future, 1); // when - let hash2 = block_on(pool.submit_one( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Bob.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 10, - }), - )) + let hash2 = block_on( + pool.submit_one( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Bob.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 10, + }) + .into(), + ), + ) .unwrap(); // then @@ -718,16 +797,19 @@ mod tests { let pool = Pool::new(options, true.into(), api.clone()); // when - block_on(pool.submit_one( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 1, - }), - )) + block_on( + pool.submit_one( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 1, + }) + .into(), + ), + ) .unwrap_err(); // then @@ -741,16 +823,19 @@ mod tests { let (pool, api) = pool(); // when - let err = block_on(pool.submit_one( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: INVALID_NONCE, - }), - )) + let err = block_on( + pool.submit_one( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: INVALID_NONCE, + }) + .into(), + ), + ) .unwrap_err(); // then @@ -766,96 +851,113 @@ mod tests { fn should_trigger_ready_and_finalized() { // given let (pool, api) = pool(); - let watcher = block_on(pool.submit_and_watch( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let watcher = block_on( + pool.submit_and_watch( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); - let hash_of_block2 = api.expect_hash_from_number(2); + let han_of_block2 = api.expect_hash_and_number(2); // when - block_on(pool.prune_tags(hash_of_block2, vec![vec![0u8]], vec![])).unwrap(); + block_on(pool.prune_tags(&han_of_block2, vec![vec![0u8]], vec![])); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); // then let mut stream = futures::executor::block_on_stream(watcher.into_stream()); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock((hash_of_block2.into(), 0))),); + assert_eq!( + stream.next(), + Some(TransactionStatus::InBlock((han_of_block2.hash.into(), 0))), + ); } #[test] fn should_trigger_ready_and_finalized_when_pruning_via_hash() { // given let (pool, api) = pool(); - let watcher = block_on(pool.submit_and_watch( - api.expect_hash_from_number(0), - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + let watcher = block_on( + pool.submit_and_watch( + &api.expect_hash_and_number(0), + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); - let hash_of_block2 = api.expect_hash_from_number(2); + let han_of_block2 = api.expect_hash_and_number(2); // when - block_on(pool.prune_tags(hash_of_block2, vec![vec![0u8]], vec![*watcher.hash()])) - .unwrap(); + block_on(pool.prune_tags(&han_of_block2, vec![vec![0u8]], vec![*watcher.hash()])); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); // then let mut stream = futures::executor::block_on_stream(watcher.into_stream()); assert_eq!(stream.next(), Some(TransactionStatus::Ready)); - assert_eq!(stream.next(), Some(TransactionStatus::InBlock((hash_of_block2.into(), 0))),); + assert_eq!( + stream.next(), + Some(TransactionStatus::InBlock((han_of_block2.hash.into(), 0))), + ); } #[test] fn should_trigger_future_and_ready_after_promoted() { // given let (pool, api) = pool(); - let hash_of_block0 = api.expect_hash_from_number(0); - - let watcher = block_on(pool.submit_and_watch( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 1, - }), - )) + let han_of_block0 = api.expect_hash_and_number(0); + + let watcher = block_on( + pool.submit_and_watch( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 1, + }) + .into(), + ), + ) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 1); // when - block_on(pool.submit_one( - hash_of_block0, - SOURCE, - uxt(Transfer { - from: Alice.into(), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 0, - }), - )) + block_on( + pool.submit_one( + &han_of_block0, + SOURCE, + uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 0, + }) + .into(), + ), + ) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 2); @@ -876,7 +978,7 @@ mod tests { nonce: 0, }); let watcher = - block_on(pool.submit_and_watch(api.expect_hash_from_number(0), SOURCE, uxt)) + block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); @@ -901,7 +1003,7 @@ mod tests { nonce: 0, }); let watcher = - block_on(pool.submit_and_watch(api.expect_hash_from_number(0), SOURCE, uxt)) + block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); @@ -934,7 +1036,7 @@ mod tests { nonce: 0, }); let watcher = - block_on(pool.submit_and_watch(api.expect_hash_from_number(0), SOURCE, xt)) + block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, xt.into())) .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); @@ -945,7 +1047,7 @@ mod tests { amount: 4, nonce: 1, }); - block_on(pool.submit_one(api.expect_hash_from_number(1), SOURCE, xt)).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(1), SOURCE, xt.into())).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // then @@ -968,7 +1070,8 @@ mod tests { // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, xt)).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())) + .unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // then @@ -980,7 +1083,8 @@ mod tests { amount: 4, nonce: 1, }); - let result = block_on(pool.submit_one(api.expect_hash_from_number(1), SOURCE, xt)); + let result = + block_on(pool.submit_one(&api.expect_hash_and_number(1), SOURCE, xt.into())); assert!(matches!( result, Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped) @@ -995,12 +1099,12 @@ mod tests { let api = Arc::new(TestApi::default()); let pool = Pool::new(options, true.into(), api.clone()); - let hash_of_block0 = api.expect_hash_from_number(0); + let han_of_block0 = api.expect_hash_and_number(0); // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); - block_on(pool.submit_and_watch(hash_of_block0, SOURCE, xt)).unwrap(); + block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // after validation `Transfer` will have priority set to 4 (validate_transaction @@ -1011,14 +1115,16 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = block_on(pool.submit_and_watch(hash_of_block0, SOURCE, xt)).unwrap(); + let watcher = + block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); assert_eq!(pool.validated_pool().status().ready, 2); // when // after validation `Store` will have priority set to 9001 (validate_transaction // mock) let xt = ExtrinsicBuilder::new_indexed_call(Vec::new()).build(); - block_on(pool.submit_one(api.expect_hash_from_number(1), SOURCE, xt)).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(1), SOURCE, xt.into())) + .unwrap(); assert_eq!(pool.validated_pool().status().ready, 2); // then @@ -1038,7 +1144,7 @@ mod tests { let api = Arc::new(api); let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); - let hash_of_block0 = api.expect_hash_from_number(0); + let han_of_block0 = api.expect_hash_and_number(0); // when let xt = uxt(Transfer { @@ -1050,9 +1156,12 @@ mod tests { // This transaction should go to future, since we use `nonce: 1` let pool2 = pool.clone(); - std::thread::spawn(move || { - block_on(pool2.submit_one(hash_of_block0, SOURCE, xt)).unwrap(); - ready.send(()).unwrap(); + std::thread::spawn({ + let hash_of_block0 = han_of_block0.clone(); + move || { + block_on(pool2.submit_one(&hash_of_block0, SOURCE, xt.into())).unwrap(); + ready.send(()).unwrap(); + } }); // But now before the previous one is imported we import @@ -1065,13 +1174,12 @@ mod tests { }); // The tag the above transaction provides (TestApi is using just nonce as u8) let provides = vec![0_u8]; - block_on(pool.submit_one(hash_of_block0, SOURCE, xt)).unwrap(); + block_on(pool.submit_one(&han_of_block0, SOURCE, xt.into())).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // Now block import happens before the second transaction is able to finish // verification. - block_on(pool.prune_tags(api.expect_hash_from_number(1), vec![provides], vec![])) - .unwrap(); + block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![provides], vec![])); assert_eq!(pool.validated_pool().status().ready, 0); // so when we release the verification of the previous one it will have diff --git a/substrate/client/transaction-pool/src/graph/ready.rs b/substrate/client/transaction-pool/src/graph/ready.rs index b4a5d9e3ba7..860bcff0bac 100644 --- a/substrate/client/transaction-pool/src/graph/ready.rs +++ b/substrate/client/transaction-pool/src/graph/ready.rs @@ -24,7 +24,7 @@ use std::{ }; use crate::LOG_TARGET; -use log::{debug, trace}; +use log::trace; use sc_transaction_pool_api::error; use serde::Serialize; use sp_runtime::{traits::Member, transaction_validity::TransactionTag as Tag}; @@ -84,7 +84,7 @@ pub struct ReadyTx { /// How many required tags are provided inherently /// /// Some transactions might be already pruned from the queue, - /// so when we compute ready set we may consider this transactions ready earlier. + /// so when we compute ready set we may consider these transactions ready earlier. pub requires_offset: usize, } @@ -106,7 +106,7 @@ qed "#; /// Validated transactions that are block ready with all their dependencies met. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ReadyTransactions { /// Next free insertion id (used to indicate when a transaction was inserted into the pool). insertion_id: u64, @@ -521,9 +521,9 @@ impl BestIterator { /// When invoked on a fully drained iterator it has no effect either. pub fn report_invalid(&mut self, tx: &Arc>) { if let Some(to_report) = self.all.get(&tx.hash) { - debug!( + trace!( target: LOG_TARGET, - "[{:?}] Reported as invalid. Will skip sub-chains while iterating.", + "[{:?}] best-iterator: Reported as invalid. Will skip sub-chains while iterating.", to_report.transaction.transaction.hash ); for hash in &to_report.unlocks { @@ -544,7 +544,7 @@ impl Iterator for BestIterator { // Check if the transaction was marked invalid. if self.invalid.contains(hash) { - debug!( + trace!( target: LOG_TARGET, "[{:?}] Skipping invalid child transaction while iterating.", hash, ); @@ -703,7 +703,7 @@ mod tests { tx6.requires = vec![tx5.provides[0].clone()]; tx6.provides = vec![]; let tx7 = Transaction { - data: vec![7], + data: vec![7].into(), bytes: 1, hash: 7, priority: 1, diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 44c2c738ab1..9e92dffc9f9 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -46,6 +46,20 @@ impl Default for TrackedMap { } } +impl Clone for TrackedMap +where + K: Clone, + V: Clone, +{ + fn clone(&self) -> Self { + Self { + index: Arc::from(RwLock::from(self.index.read().clone())), + bytes: self.bytes.load(AtomicOrdering::Relaxed).into(), + length: self.length.load(AtomicOrdering::Relaxed).into(), + } + } +} + impl TrackedMap { /// Current tracked length of the content. pub fn len(&self) -> usize { diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 3d7cfeb46b0..d7f55198a40 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -22,13 +22,13 @@ use std::{ sync::Arc, }; -use crate::LOG_TARGET; +use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; use serde::Serialize; +use sp_blockchain::HashAndNumber; use sp_runtime::{ - generic::BlockId, traits::{self, SaturatedConversion}, transaction_validity::{TransactionSource, TransactionTag as Tag, ValidTransaction}, }; @@ -86,17 +86,18 @@ pub type ValidatedTransactionFor = ValidatedTransaction, ExtrinsicFor, ::Error>; /// A closure that returns true if the local node is a validator that can author blocks. -pub struct IsValidator(Box bool + Send + Sync>); +#[derive(Clone)] +pub struct IsValidator(Arc bool + Send + Sync>>); impl From for IsValidator { fn from(is_validator: bool) -> Self { - Self(Box::new(move || is_validator)) + Self(Arc::new(Box::new(move || is_validator))) } } impl From bool + Send + Sync>> for IsValidator { fn from(is_validator: Box bool + Send + Sync>) -> Self { - Self(is_validator) + Self(Arc::new(is_validator)) } } @@ -111,6 +112,20 @@ pub struct ValidatedPool { rotator: PoolRotator>, } +impl Clone for ValidatedPool { + fn clone(&self) -> Self { + Self { + api: self.api.clone(), + is_validator: self.is_validator.clone(), + options: self.options.clone(), + listener: Default::default(), + pool: RwLock::from(self.pool.read().clone()), + import_notification_sinks: Default::default(), + rotator: PoolRotator::default(), + } + } +} + impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { @@ -187,6 +202,7 @@ impl ValidatedPool { fn submit_one(&self, tx: ValidatedTransactionFor) -> Result, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { + log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one", tx.hash); if !tx.propagate && !(self.is_validator.0)() { return Err(error::Error::Unactionable.into()) } @@ -216,10 +232,12 @@ impl ValidatedPool { Ok(*imported.hash()) }, ValidatedTransaction::Invalid(hash, err) => { + log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one invalid: {:?}", hash, err); self.rotator.ban(&Instant::now(), std::iter::once(hash)); Err(err) }, ValidatedTransaction::Unknown(hash, err) => { + log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one unknown {:?}", hash, err); self.listener.write().invalid(&hash); Err(err) }, @@ -231,7 +249,6 @@ impl ValidatedPool { let ready_limit = &self.options.ready; let future_limit = &self.options.future; - log::debug!(target: LOG_TARGET, "Pool Status: {:?}", status); if ready_limit.is_exceeded(status.ready, status.ready_bytes) || future_limit.is_exceeded(status.future, status.future_bytes) { @@ -257,13 +274,13 @@ impl ValidatedPool { removed }; if !removed.is_empty() { - log::debug!(target: LOG_TARGET, "Enforcing limits: {} dropped", removed.len()); + log::trace!(target: LOG_TARGET, "Enforcing limits: {} dropped", removed.len()); } // run notifications let mut listener = self.listener.write(); for h in &removed { - listener.dropped(h, None); + listener.dropped(h, None, true); } removed @@ -280,7 +297,7 @@ impl ValidatedPool { match tx { ValidatedTransaction::Valid(tx) => { let hash = self.api.hash_and_length(&tx.data).0; - let watcher = self.listener.write().create_watcher(hash); + let watcher = self.create_watcher(hash); self.submit(std::iter::once(ValidatedTransaction::Valid(tx))) .pop() .expect("One extrinsic passed; one result returned; qed") @@ -294,6 +311,19 @@ impl ValidatedPool { } } + /// Creates a new watcher for given extrinsic. + pub fn create_watcher( + &self, + tx_hash: ExtrinsicHash, + ) -> Watcher, ExtrinsicHash> { + self.listener.write().create_watcher(tx_hash) + } + + /// Provides a list of hashes for all watched transactions in the pool. + pub fn watched_transactions(&self) -> Vec> { + self.listener.read().watched_transactions().map(Clone::clone).collect() + } + /// Resubmits revalidated transactions back to the pool. /// /// Removes and then submits passed transactions and all dependent transactions. @@ -351,7 +381,7 @@ impl ValidatedPool { initial_statuses.insert(removed_hash, Status::Ready); txs_to_resubmit.push((removed_hash, tx_to_resubmit)); } - // make sure to remove the hash even if it's not present in the pool any more. + // make sure to remove the hash even if it's not present in the pool anymore. updated_transactions.remove(&hash); } @@ -423,7 +453,7 @@ impl ValidatedPool { match final_status { Status::Future => listener.future(&hash), Status::Ready => listener.ready(&hash, None), - Status::Dropped => listener.dropped(&hash, None), + Status::Dropped => listener.dropped(&hash, None, false), Status::Failed => listener.invalid(&hash), } } @@ -451,7 +481,7 @@ impl ValidatedPool { pub fn prune_tags( &self, tags: impl IntoIterator, - ) -> Result, ExtrinsicFor>, B::Error> { + ) -> PruneStatus, ExtrinsicFor> { // Perform tag-based pruning in the base pool let status = self.pool.write().prune_tags(tags); // Notify event listeners of all transactions @@ -462,21 +492,21 @@ impl ValidatedPool { fire_events(&mut *listener, promoted); } for f in &status.failed { - listener.dropped(f, None); + listener.dropped(f, None, false); } } - Ok(status) + status } /// Resubmit transactions that have been revalidated after prune_tags call. pub fn resubmit_pruned( &self, - at: &BlockId, + at: &HashAndNumber, known_imported_hashes: impl IntoIterator> + Clone, pruned_hashes: Vec>, pruned_xts: Vec>, - ) -> Result<(), B::Error> { + ) { debug_assert_eq!(pruned_hashes.len(), pruned_xts.len()); // Resubmit pruned transactions @@ -493,35 +523,29 @@ impl ValidatedPool { // Fire `pruned` notifications for collected hashes and make sure to include // `known_imported_hashes` since they were just imported as part of the block. let hashes = hashes.chain(known_imported_hashes.into_iter()); - self.fire_pruned(at, hashes)?; + self.fire_pruned(at, hashes); // perform regular cleanup of old transactions in the pool // and update temporary bans. - self.clear_stale(at)?; - Ok(()) + self.clear_stale(at); } /// Fire notifications for pruned transactions. pub fn fire_pruned( &self, - at: &BlockId, + at: &HashAndNumber, hashes: impl Iterator>, - ) -> Result<(), B::Error> { - let header_hash = self - .api - .block_id_to_hash(at)? - .ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)))?; + ) { let mut listener = self.listener.write(); let mut set = HashSet::with_capacity(hashes.size_hint().0); for h in hashes { // `hashes` has possibly duplicate hashes. // we'd like to send out the `InBlock` notification only once. if !set.contains(&h) { - listener.pruned(header_hash, &h); + listener.pruned(at.hash, &h); set.insert(h); } } - Ok(()) } /// Removes stale transactions from the pool. @@ -529,16 +553,13 @@ impl ValidatedPool { /// Stale transactions are transaction beyond their longevity period. /// Note this function does not remove transactions that are already included in the chain. /// See `prune_tags` if you want this. - pub fn clear_stale(&self, at: &BlockId) -> Result<(), B::Error> { - let block_number = self - .api - .block_id_to_number(at)? - .ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)))? - .saturated_into::(); + pub fn clear_stale(&self, at: &HashAndNumber) { + let HashAndNumber { number, .. } = *at; + let number = number.saturated_into::(); let now = Instant::now(); let to_remove = { self.ready() - .filter(|tx| self.rotator.ban_if_stale(&now, block_number, tx)) + .filter(|tx| self.rotator.ban_if_stale(&now, number, tx)) .map(|tx| tx.hash) .collect::>() }; @@ -546,7 +567,7 @@ impl ValidatedPool { let p = self.pool.read(); let mut hashes = Vec::new(); for tx in p.futures() { - if self.rotator.ban_if_stale(&now, block_number, tx) { + if self.rotator.ban_if_stale(&now, number, tx) { hashes.push(tx.hash); } } @@ -557,8 +578,6 @@ impl ValidatedPool { self.remove_invalid(&futures_to_remove); // clear banned transactions timeouts self.rotator.clear_timeouts(&now); - - Ok(()) } /// Get api reference. @@ -598,14 +617,15 @@ impl ValidatedPool { return vec![] } - log::debug!(target: LOG_TARGET, "Removing invalid transactions: {:?}", hashes); + log::trace!(target: LOG_TARGET, "Removing invalid transactions: {:?}", hashes.len()); // temporarily ban invalid transactions self.rotator.ban(&Instant::now(), hashes.iter().cloned()); let invalid = self.pool.write().remove_subtree(hashes); - log::debug!(target: LOG_TARGET, "Removed invalid transactions: {:?}", invalid); + log::trace!(target: LOG_TARGET, "Removed invalid transactions: {:?}", invalid.len()); + log_xt_trace!(target: LOG_TARGET, invalid.iter().map(|t| t.hash), "{:?} Removed invalid transaction"); let mut listener = self.listener.write(); for tx in &invalid { @@ -645,6 +665,12 @@ impl ValidatedPool { pub fn on_block_retracted(&self, block_hash: BlockHash) { self.listener.write().retracted(block_hash) } + + pub fn create_dropped_by_limits_stream( + &self, + ) -> super::listener::DroppedByLimitsStream, BlockHash> { + self.listener.write().create_dropped_by_limits_stream() + } } fn fire_events(listener: &mut Listener, imported: &base::Imported) @@ -656,7 +682,7 @@ where base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { listener.ready(hash, None); failed.iter().for_each(|f| listener.invalid(f)); - removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash))); + removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash), false)); promoted.iter().for_each(|p| listener.ready(p, None)); }, base::Imported::Future { ref hash } => listener.future(hash), diff --git a/substrate/client/transaction-pool/src/graph/watcher.rs b/substrate/client/transaction-pool/src/graph/watcher.rs index fc440771d7b..fb7cf99d4dc 100644 --- a/substrate/client/transaction-pool/src/graph/watcher.rs +++ b/substrate/client/transaction-pool/src/graph/watcher.rs @@ -123,7 +123,7 @@ impl Sender { self.send(TransactionStatus::Broadcast(peers)) } - /// Returns true if the are no more listeners for this extrinsic or it was finalized. + /// Returns true if there are no more listeners for this extrinsic, or it was finalized. pub fn is_done(&self) -> bool { self.is_finalized || self.receivers.is_empty() } diff --git a/substrate/client/transaction-pool/src/lib.rs b/substrate/client/transaction-pool/src/lib.rs index 64b301e6bf3..888d25d3a0d 100644 --- a/substrate/client/transaction-pool/src/lib.rs +++ b/substrate/client/transaction-pool/src/lib.rs @@ -22,776 +22,38 @@ #![warn(missing_docs)] #![warn(unused_extern_crates)] -mod api; -mod enactment_state; -pub mod error; +mod builder; +mod common; +mod fork_aware_txpool; mod graph; -mod metrics; -mod revalidation; -#[cfg(test)] -mod tests; - -pub use crate::api::FullChainApi; -use async_trait::async_trait; -use enactment_state::{EnactmentAction, EnactmentState}; -use futures::{ - channel::oneshot, - future::{self, ready}, - prelude::*, -}; -pub use graph::{ - base_pool::Limit as PoolLimit, ChainApi, Options, Pool, Transaction, ValidatedTransaction, -}; -use parking_lot::Mutex; -use std::{ - collections::{HashMap, HashSet}, - pin::Pin, - sync::Arc, -}; - -use graph::{ExtrinsicHash, IsValidator}; -use sc_transaction_pool_api::{ - error::Error as TxPoolError, ChainEvent, ImportNotificationStream, MaintainedTransactionPool, - PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, TransactionPool, TransactionSource, - TransactionStatusStreamFor, TxHash, -}; -use sp_core::traits::SpawnEssentialNamed; -use sp_runtime::{ - generic::BlockId, - traits::{AtLeast32Bit, Block as BlockT, Extrinsic, Header as HeaderT, NumberFor, Zero}, -}; -use std::time::Instant; - -use crate::metrics::MetricsLink as PrometheusMetrics; -use prometheus_endpoint::Registry as PrometheusRegistry; - -use sp_blockchain::{HashAndNumber, TreeRoute}; - -pub(crate) const LOG_TARGET: &str = "txpool"; - -type BoxedReadyIterator = - Box>> + Send>; +mod single_state_txpool; +mod transaction_pool_wrapper; + +use common::{api, enactment_state}; +use std::{future::Future, pin::Pin, sync::Arc}; + +pub use api::FullChainApi; +pub use builder::{Builder, TransactionPoolHandle, TransactionPoolOptions, TransactionPoolType}; +pub use common::notification_future; +pub use fork_aware_txpool::{ForkAwareTxPool, ForkAwareTxPoolTask}; +pub use graph::{base_pool::Limit as PoolLimit, ChainApi, Options, Pool}; +use single_state_txpool::prune_known_txs_for_block; +pub use single_state_txpool::{BasicPool, RevalidationType}; +pub use transaction_pool_wrapper::TransactionPoolWrapper; + +type BoxedReadyIterator = Box< + dyn sc_transaction_pool_api::ReadyTransactions< + Item = Arc>, + > + Send, +>; type ReadyIteratorFor = BoxedReadyIterator, graph::ExtrinsicFor>; type PolledIterator = Pin> + Send>>; -/// A transaction pool for a full node. -pub type FullPool = BasicPool, Block>; - -/// Basic implementation of transaction pool that can be customized by providing PoolApi. -pub struct BasicPool -where - Block: BlockT, - PoolApi: graph::ChainApi, -{ - pool: Arc>, - api: Arc, - revalidation_strategy: Arc>>>, - revalidation_queue: Arc>, - ready_poll: Arc, Block>>>, - metrics: PrometheusMetrics, - enactment_state: Arc>>, -} - -struct ReadyPoll { - updated_at: NumberFor, - pollers: Vec<(NumberFor, oneshot::Sender)>, -} - -impl Default for ReadyPoll { - fn default() -> Self { - Self { updated_at: NumberFor::::zero(), pollers: Default::default() } - } -} - -impl ReadyPoll { - fn new(best_block_number: NumberFor) -> Self { - Self { updated_at: best_block_number, pollers: Default::default() } - } - - fn trigger(&mut self, number: NumberFor, iterator_factory: impl Fn() -> T) { - self.updated_at = number; - - let mut idx = 0; - while idx < self.pollers.len() { - if self.pollers[idx].0 <= number { - let poller_sender = self.pollers.swap_remove(idx); - log::debug!(target: LOG_TARGET, "Sending ready signal at block {}", number); - let _ = poller_sender.1.send(iterator_factory()); - } else { - idx += 1; - } - } - } - - fn add(&mut self, number: NumberFor) -> oneshot::Receiver { - let (sender, receiver) = oneshot::channel(); - self.pollers.push((number, sender)); - receiver - } - - fn updated_at(&self) -> NumberFor { - self.updated_at - } -} - -/// Type of revalidation. -pub enum RevalidationType { - /// Light revalidation type. - /// - /// During maintenance, transaction pool makes periodic revalidation - /// of all transactions depending on number of blocks or time passed. - /// Also this kind of revalidation does not resubmit transactions from - /// retracted blocks, since it is too expensive. - Light, - - /// Full revalidation type. - /// - /// During maintenance, transaction pool revalidates some fixed amount of - /// transactions from the pool of valid transactions. - Full, -} - -impl BasicPool -where - Block: BlockT, - PoolApi: graph::ChainApi + 'static, -{ - /// Create new basic transaction pool with provided api, for tests. - pub fn new_test( - pool_api: Arc, - best_block_hash: Block::Hash, - finalized_hash: Block::Hash, - options: graph::Options, - ) -> (Self, Pin + Send>>) { - let pool = Arc::new(graph::Pool::new(options, true.into(), pool_api.clone())); - let (revalidation_queue, background_task) = revalidation::RevalidationQueue::new_background( - pool_api.clone(), - pool.clone(), - finalized_hash, - ); - ( - Self { - api: pool_api, - pool, - revalidation_queue: Arc::new(revalidation_queue), - revalidation_strategy: Arc::new(Mutex::new(RevalidationStrategy::Always)), - ready_poll: Default::default(), - metrics: Default::default(), - enactment_state: Arc::new(Mutex::new(EnactmentState::new( - best_block_hash, - finalized_hash, - ))), - }, - background_task, - ) - } - - /// Create new basic transaction pool with provided api and custom - /// revalidation type. - pub fn with_revalidation_type( - options: graph::Options, - is_validator: IsValidator, - pool_api: Arc, - prometheus: Option<&PrometheusRegistry>, - revalidation_type: RevalidationType, - spawner: impl SpawnEssentialNamed, - best_block_number: NumberFor, - best_block_hash: Block::Hash, - finalized_hash: Block::Hash, - ) -> Self { - let pool = Arc::new(graph::Pool::new(options, is_validator, pool_api.clone())); - let (revalidation_queue, background_task) = match revalidation_type { - RevalidationType::Light => - (revalidation::RevalidationQueue::new(pool_api.clone(), pool.clone()), None), - RevalidationType::Full => { - let (queue, background) = revalidation::RevalidationQueue::new_background( - pool_api.clone(), - pool.clone(), - finalized_hash, - ); - (queue, Some(background)) - }, - }; - - if let Some(background_task) = background_task { - spawner.spawn_essential("txpool-background", Some("transaction-pool"), background_task); - } - - Self { - api: pool_api, - pool, - revalidation_queue: Arc::new(revalidation_queue), - revalidation_strategy: Arc::new(Mutex::new(match revalidation_type { - RevalidationType::Light => - RevalidationStrategy::Light(RevalidationStatus::NotScheduled), - RevalidationType::Full => RevalidationStrategy::Always, - })), - ready_poll: Arc::new(Mutex::new(ReadyPoll::new(best_block_number))), - metrics: PrometheusMetrics::new(prometheus), - enactment_state: Arc::new(Mutex::new(EnactmentState::new( - best_block_hash, - finalized_hash, - ))), - } - } - - /// Gets shared reference to the underlying pool. - pub fn pool(&self) -> &Arc> { - &self.pool - } - - /// Get access to the underlying api - pub fn api(&self) -> &PoolApi { - &self.api - } -} - -impl TransactionPool for BasicPool -where - Block: BlockT, - PoolApi: 'static + graph::ChainApi, -{ - type Block = PoolApi::Block; - type Hash = graph::ExtrinsicHash; - type InPoolTransaction = graph::base_pool::Transaction, TransactionFor>; - type Error = PoolApi::Error; - - fn submit_at( - &self, - at: ::Hash, - source: TransactionSource, - xts: Vec>, - ) -> PoolFuture, Self::Error>>, Self::Error> { - let pool = self.pool.clone(); - - self.metrics - .report(|metrics| metrics.submitted_transactions.inc_by(xts.len() as u64)); - - async move { pool.submit_at(at, source, xts).await }.boxed() - } - - fn submit_one( - &self, - at: ::Hash, - source: TransactionSource, - xt: TransactionFor, - ) -> PoolFuture, Self::Error> { - let pool = self.pool.clone(); - - self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - - async move { pool.submit_one(at, source, xt).await }.boxed() - } - - fn submit_and_watch( - &self, - at: ::Hash, - source: TransactionSource, - xt: TransactionFor, - ) -> PoolFuture>>, Self::Error> { - let pool = self.pool.clone(); - - self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - - async move { - let watcher = pool.submit_and_watch(at, source, xt).await?; - - Ok(watcher.into_stream().boxed()) - } - .boxed() - } - - fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { - let removed = self.pool.validated_pool().remove_invalid(hashes); - self.metrics - .report(|metrics| metrics.validations_invalid.inc_by(removed.len() as u64)); - removed - } - - fn status(&self) -> PoolStatus { - self.pool.validated_pool().status() - } - - fn import_notification_stream(&self) -> ImportNotificationStream> { - self.pool.validated_pool().import_notification_stream() - } - - fn hash_of(&self, xt: &TransactionFor) -> TxHash { - self.pool.hash_of(xt) - } - - fn on_broadcasted(&self, propagations: HashMap, Vec>) { - self.pool.validated_pool().on_broadcasted(propagations) - } - - fn ready_transaction(&self, hash: &TxHash) -> Option> { - self.pool.validated_pool().ready_by_hash(hash) - } - - fn ready_at(&self, at: NumberFor) -> PolledIterator { - let status = self.status(); - // If there are no transactions in the pool, it is fine to return early. - // - // There could be transaction being added because of some re-org happening at the relevant - // block, but this is relative unlikely. - if status.ready == 0 && status.future == 0 { - return async { Box::new(std::iter::empty()) as Box<_> }.boxed() - } - - if self.ready_poll.lock().updated_at() >= at { - log::trace!(target: LOG_TARGET, "Transaction pool already processed block #{}", at); - let iterator: ReadyIteratorFor = Box::new(self.pool.validated_pool().ready()); - return async move { iterator }.boxed() - } - - self.ready_poll - .lock() - .add(at) - .map(|received| { - received.unwrap_or_else(|e| { - log::warn!("Error receiving pending set: {:?}", e); - Box::new(std::iter::empty()) - }) - }) - .boxed() - } - - fn ready(&self) -> ReadyIteratorFor { - Box::new(self.pool.validated_pool().ready()) - } - - fn futures(&self) -> Vec { - let pool = self.pool.validated_pool().pool.read(); - - pool.futures().cloned().collect::>() - } -} - -impl FullPool -where - Block: BlockT, - Client: sp_api::ProvideRuntimeApi - + sc_client_api::BlockBackend - + sc_client_api::blockchain::HeaderBackend - + sp_runtime::traits::BlockIdTo - + sc_client_api::ExecutorProvider - + sc_client_api::UsageProvider - + sp_blockchain::HeaderMetadata - + Send - + Sync - + 'static, - Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, -{ - /// Create new basic transaction pool for a full node with the provided api. - pub fn new_full( - options: graph::Options, - is_validator: IsValidator, - prometheus: Option<&PrometheusRegistry>, - spawner: impl SpawnEssentialNamed, - client: Arc, - ) -> Arc { - let pool_api = Arc::new(FullChainApi::new(client.clone(), prometheus, &spawner)); - let pool = Arc::new(Self::with_revalidation_type( - options, - is_validator, - pool_api, - prometheus, - RevalidationType::Full, - spawner, - client.usage_info().chain.best_number, - client.usage_info().chain.best_hash, - client.usage_info().chain.finalized_hash, - )); - - pool - } -} - -impl sc_transaction_pool_api::LocalTransactionPool - for BasicPool, Block> -where - Block: BlockT, - Client: sp_api::ProvideRuntimeApi - + sc_client_api::BlockBackend - + sc_client_api::blockchain::HeaderBackend - + sp_runtime::traits::BlockIdTo - + sp_blockchain::HeaderMetadata, - Client: Send + Sync + 'static, - Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, -{ - type Block = Block; - type Hash = graph::ExtrinsicHash>; - type Error = as graph::ChainApi>::Error; - - fn submit_local( - &self, - at: Block::Hash, - xt: sc_transaction_pool_api::LocalTransactionFor, - ) -> Result { - use sp_runtime::{ - traits::SaturatedConversion, transaction_validity::TransactionValidityError, - }; - - let validity = self - .api - .validate_transaction_blocking(at, TransactionSource::Local, xt.clone())? - .map_err(|e| { - Self::Error::Pool(match e { - TransactionValidityError::Invalid(i) => TxPoolError::InvalidTransaction(i), - TransactionValidityError::Unknown(u) => TxPoolError::UnknownTransaction(u), - }) - })?; - - let (hash, bytes) = self.pool.validated_pool().api().hash_and_length(&xt); - let block_number = self - .api - .block_id_to_number(&BlockId::hash(at))? - .ok_or_else(|| error::Error::BlockIdConversion(format!("{:?}", at)))?; - - let validated = ValidatedTransaction::valid_at( - block_number.saturated_into::(), - hash, - TransactionSource::Local, - xt, - bytes, - validity, - ); - - self.pool.validated_pool().submit(vec![validated]).remove(0) - } -} - -#[cfg_attr(test, derive(Debug))] -enum RevalidationStatus { - /// The revalidation has never been completed. - NotScheduled, - /// The revalidation is scheduled. - Scheduled(Option, Option), - /// The revalidation is in progress. - InProgress, -} - -enum RevalidationStrategy { - Always, - Light(RevalidationStatus), -} - -struct RevalidationAction { - revalidate: bool, - resubmit: bool, -} - -impl RevalidationStrategy { - pub fn clear(&mut self) { - if let Self::Light(status) = self { - status.clear() - } - } - - pub fn next( - &mut self, - block: N, - revalidate_time_period: Option, - revalidate_block_period: Option, - ) -> RevalidationAction { - match self { - Self::Light(status) => RevalidationAction { - revalidate: status.next_required( - block, - revalidate_time_period, - revalidate_block_period, - ), - resubmit: false, - }, - Self::Always => RevalidationAction { revalidate: true, resubmit: true }, - } - } -} - -impl RevalidationStatus { - /// Called when revalidation is completed. - pub fn clear(&mut self) { - *self = Self::NotScheduled; - } - - /// Returns true if revalidation is required. - pub fn next_required( - &mut self, - block: N, - revalidate_time_period: Option, - revalidate_block_period: Option, - ) -> bool { - match *self { - Self::NotScheduled => { - *self = Self::Scheduled( - revalidate_time_period.map(|period| Instant::now() + period), - revalidate_block_period.map(|period| block + period), - ); - false - }, - Self::Scheduled(revalidate_at_time, revalidate_at_block) => { - let is_required = - revalidate_at_time.map(|at| Instant::now() >= at).unwrap_or(false) || - revalidate_at_block.map(|at| block >= at).unwrap_or(false); - if is_required { - *self = Self::InProgress; - } - is_required - }, - Self::InProgress => false, - } - } -} - -/// Prune the known txs for the given block. -async fn prune_known_txs_for_block>( - block_hash: Block::Hash, - api: &Api, - pool: &graph::Pool, -) -> Vec> { - let extrinsics = api - .block_body(block_hash) - .await - .unwrap_or_else(|e| { - log::warn!("Prune known transactions: error request: {}", e); - None - }) - .unwrap_or_default(); - - let hashes = extrinsics.iter().map(|tx| pool.hash_of(tx)).collect::>(); - - log::trace!(target: LOG_TARGET, "Pruning transactions: {:?}", hashes); - - let header = match api.block_header(block_hash) { - Ok(Some(h)) => h, - Ok(None) => { - log::debug!(target: LOG_TARGET, "Could not find header for {:?}.", block_hash); - return hashes - }, - Err(e) => { - log::debug!(target: LOG_TARGET, "Error retrieving header for {:?}: {}", block_hash, e); - return hashes - }, - }; - - if let Err(e) = pool.prune(block_hash, *header.parent_hash(), &extrinsics).await { - log::error!("Cannot prune known in the pool: {}", e); - } - - hashes -} - -impl BasicPool -where - Block: BlockT, - PoolApi: 'static + graph::ChainApi, -{ - /// Handles enactment and retraction of blocks, prunes stale transactions - /// (that have already been enacted) and resubmits transactions that were - /// retracted. - async fn handle_enactment(&self, tree_route: TreeRoute) { - log::trace!(target: LOG_TARGET, "handle_enactment tree_route: {tree_route:?}"); - let pool = self.pool.clone(); - let api = self.api.clone(); - - let (hash, block_number) = match tree_route.last() { - Some(HashAndNumber { hash, number }) => (hash, number), - None => { - log::warn!( - target: LOG_TARGET, - "Skipping ChainEvent - no last block in tree route {:?}", - tree_route, - ); - return - }, - }; - - let next_action = self.revalidation_strategy.lock().next( - *block_number, - Some(std::time::Duration::from_secs(60)), - Some(20u32.into()), - ); - - // We keep track of everything we prune so that later we won't add - // transactions with those hashes from the retracted blocks. - let mut pruned_log = HashSet::>::new(); - - // If there is a tree route, we use this to prune known tx based on the enacted - // blocks. Before pruning enacted transactions, we inform the listeners about - // retracted blocks and their transactions. This order is important, because - // if we enact and retract the same transaction at the same time, we want to - // send first the retract and than the prune event. - for retracted in tree_route.retracted() { - // notify txs awaiting finality that it has been retracted - pool.validated_pool().on_block_retracted(retracted.hash); - } - - future::join_all( - tree_route - .enacted() - .iter() - .map(|h| prune_known_txs_for_block(h.hash, &*api, &*pool)), - ) - .await - .into_iter() - .for_each(|enacted_log| { - pruned_log.extend(enacted_log); - }); - - self.metrics - .report(|metrics| metrics.block_transactions_pruned.inc_by(pruned_log.len() as u64)); - - if next_action.resubmit { - let mut resubmit_transactions = Vec::new(); - - for retracted in tree_route.retracted() { - let hash = retracted.hash; - - let block_transactions = api - .block_body(hash) - .await - .unwrap_or_else(|e| { - log::warn!("Failed to fetch block body: {}", e); - None - }) - .unwrap_or_default() - .into_iter() - .filter(|tx| tx.is_signed().unwrap_or(true)); - - let mut resubmitted_to_report = 0; - - resubmit_transactions.extend(block_transactions.into_iter().filter(|tx| { - let tx_hash = pool.hash_of(tx); - let contains = pruned_log.contains(&tx_hash); - - // need to count all transactions, not just filtered, here - resubmitted_to_report += 1; - - if !contains { - log::debug!( - target: LOG_TARGET, - "[{:?}]: Resubmitting from retracted block {:?}", - tx_hash, - hash, - ); - } - !contains - })); - - self.metrics.report(|metrics| { - metrics.block_transactions_resubmitted.inc_by(resubmitted_to_report) - }); - } - - if let Err(e) = pool - .resubmit_at( - *hash, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await - { - log::debug!( - target: LOG_TARGET, - "[{:?}] Error re-submitting transactions: {}", - hash, - e, - ) - } - } - - let extra_pool = pool.clone(); - // After #5200 lands, this arguably might be moved to the - // handler of "all blocks notification". - self.ready_poll - .lock() - .trigger(*block_number, move || Box::new(extra_pool.validated_pool().ready())); - - if next_action.revalidate { - let hashes = pool.validated_pool().ready().map(|tx| tx.hash).collect(); - self.revalidation_queue.revalidate_later(*hash, hashes).await; - - self.revalidation_strategy.lock().clear(); - } - } -} - -#[async_trait] -impl MaintainedTransactionPool for BasicPool -where - Block: BlockT, - PoolApi: 'static + graph::ChainApi, -{ - async fn maintain(&self, event: ChainEvent) { - let prev_finalized_block = self.enactment_state.lock().recent_finalized_block(); - let compute_tree_route = |from, to| -> Result, String> { - match self.api.tree_route(from, to) { - Ok(tree_route) => Ok(tree_route), - Err(e) => - return Err(format!( - "Error occurred while computing tree_route from {from:?} to {to:?}: {e}" - )), - } - }; - let block_id_to_number = - |hash| self.api.block_id_to_number(&BlockId::Hash(hash)).map_err(|e| format!("{}", e)); - - let result = - self.enactment_state - .lock() - .update(&event, &compute_tree_route, &block_id_to_number); - - match result { - Err(msg) => { - log::debug!(target: LOG_TARGET, "{msg}"); - self.enactment_state.lock().force_update(&event); - }, - Ok(EnactmentAction::Skip) => return, - Ok(EnactmentAction::HandleFinalization) => {}, - Ok(EnactmentAction::HandleEnactment(tree_route)) => { - self.handle_enactment(tree_route).await; - }, - }; - - if let ChainEvent::Finalized { hash, tree_route } = event { - log::trace!( - target: LOG_TARGET, - "on-finalized enacted: {tree_route:?}, previously finalized: \ - {prev_finalized_block:?}", - ); - - for hash in tree_route.iter().chain(std::iter::once(&hash)) { - if let Err(e) = self.pool.validated_pool().on_block_finalized(*hash).await { - log::warn!( - target: LOG_TARGET, - "Error occurred while attempting to notify watchers about finalization {}: {}", - hash, e - ) - } - } - } - } -} - -/// Inform the transaction pool about imported and finalized blocks. -pub async fn notification_future(client: Arc, txpool: Arc) -where - Block: BlockT, - Client: sc_client_api::BlockchainEvents, - Pool: MaintainedTransactionPool, -{ - let import_stream = client - .import_notification_stream() - .filter_map(|n| ready(n.try_into().ok())) - .fuse(); - let finality_stream = client.finality_notification_stream().map(Into::into).fuse(); - - futures::stream::select(import_stream, finality_stream) - .for_each(|evt| txpool.maintain(evt)) - .await -} +/// Log target for transaction pool. +/// +/// It can be used by other components for logging functionality strictly related to txpool (e.g. +/// importing transaction). +pub const LOG_TARGET: &str = "txpool"; diff --git a/substrate/client/transaction-pool/src/single_state_txpool/metrics.rs b/substrate/client/transaction-pool/src/single_state_txpool/metrics.rs new file mode 100644 index 00000000000..28a0f66e7ed --- /dev/null +++ b/substrate/client/transaction-pool/src/single_state_txpool/metrics.rs @@ -0,0 +1,67 @@ +// 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 . + +//! Transaction pool Prometheus metrics for single-state transaction pool. + +use crate::common::metrics::{GenericMetricsLink, MetricsRegistrant}; +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; + +pub type MetricsLink = GenericMetricsLink; + +/// Transaction pool Prometheus metrics. +pub struct Metrics { + pub submitted_transactions: Counter, + pub validations_invalid: Counter, + pub block_transactions_pruned: Counter, + pub block_transactions_resubmitted: Counter, +} + +impl MetricsRegistrant for Metrics { + fn register(registry: &Registry) -> Result, PrometheusError> { + Ok(Box::from(Self { + submitted_transactions: register( + Counter::new( + "substrate_sub_txpool_submitted_transactions", + "Total number of transactions submitted", + )?, + registry, + )?, + validations_invalid: register( + Counter::new( + "substrate_sub_txpool_validations_invalid", + "Total number of transactions that were removed from the pool as invalid", + )?, + registry, + )?, + block_transactions_pruned: register( + Counter::new( + "substrate_sub_txpool_block_transactions_pruned", + "Total number of transactions that was requested to be pruned by block events", + )?, + registry, + )?, + block_transactions_resubmitted: register( + Counter::new( + "substrate_sub_txpool_block_transactions_resubmitted", + "Total number of transactions that was requested to be resubmitted by block events", + )?, + registry, + )?, + })) + } +} diff --git a/substrate/client/transaction-pool/src/single_state_txpool/mod.rs b/substrate/client/transaction-pool/src/single_state_txpool/mod.rs new file mode 100644 index 00000000000..d7ebb8c01ce --- /dev/null +++ b/substrate/client/transaction-pool/src/single_state_txpool/mod.rs @@ -0,0 +1,26 @@ +// 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 . + +//! Substrate single state transaction pool implementation. + +mod metrics; +mod revalidation; +pub(crate) mod single_state_txpool; + +pub(crate) use single_state_txpool::prune_known_txs_for_block; +pub use single_state_txpool::{BasicPool, RevalidationType}; diff --git a/substrate/client/transaction-pool/src/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs similarity index 91% rename from substrate/client/transaction-pool/src/revalidation.rs rename to substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 488ab19d8ea..5ef726c9f7d 100644 --- a/substrate/client/transaction-pool/src/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -24,10 +24,7 @@ use std::{ sync::Arc, }; -use crate::{ - graph::{BlockHash, ChainApi, ExtrinsicHash, Pool, ValidatedTransaction}, - LOG_TARGET, -}; +use crate::graph::{BlockHash, ChainApi, ExtrinsicHash, Pool, ValidatedTransaction}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::{ generic::BlockId, traits::SaturatedConversion, transaction_validity::TransactionValidityError, @@ -40,6 +37,8 @@ const BACKGROUND_REVALIDATION_INTERVAL: Duration = Duration::from_millis(200); const MIN_BACKGROUND_REVALIDATION_BATCH_SIZE: usize = 20; +const LOG_TARGET: &str = "txpool::revalidation"; + /// Payload from queue to worker. struct WorkerPayload { at: BlockHash, @@ -75,11 +74,11 @@ async fn batch_revalidate( let block_number = match api.block_id_to_number(&BlockId::Hash(at)) { Ok(Some(n)) => n, Ok(None) => { - log::debug!(target: LOG_TARGET, "revalidation skipped at block {at:?}, could not get block number."); + log::trace!(target: LOG_TARGET, "revalidation skipped at block {at:?}, could not get block number."); return }, Err(e) => { - log::debug!(target: LOG_TARGET, "revalidation skipped at block {at:?}: {e:?}."); + log::trace!(target: LOG_TARGET, "revalidation skipped at block {at:?}: {e:?}."); return }, }; @@ -98,7 +97,7 @@ async fn batch_revalidate( for (validation_result, ext_hash, ext) in validation_results { match validation_result { Ok(Err(TransactionValidityError::Invalid(err))) => { - log::debug!( + log::trace!( target: LOG_TARGET, "[{:?}]: Revalidation: invalid {:?}", ext_hash, @@ -130,7 +129,7 @@ async fn batch_revalidate( ); }, Err(validation_err) => { - log::debug!( + log::trace!( target: LOG_TARGET, "[{:?}]: Removing due to error during revalidation: {}", ext_hash, @@ -256,7 +255,7 @@ impl RevalidationWorker { batch_revalidate(this.pool.clone(), this.api.clone(), this.best_block, next_batch).await; if batch_len > 0 || this.len() > 0 { - log::debug!( + log::trace!( target: LOG_TARGET, "Revalidated {} transactions. Left in the queue for revalidation: {}.", batch_len, @@ -273,7 +272,7 @@ impl RevalidationWorker { this.push(worker_payload); if this.members.len() > 0 { - log::debug!( + log::trace!( target: LOG_TARGET, "Updated revalidation queue at {:?}. Transactions: {:?}", this.best_block, @@ -359,6 +358,10 @@ where log::warn!(target: LOG_TARGET, "Failed to update background worker: {:?}", e); } } else { + log::debug!( + target: LOG_TARGET, + "batch_revalidate direct call" + ); let pool = self.pool.clone(); let api = self.api.clone(); batch_revalidate(pool, api, at, transactions).await @@ -370,8 +373,8 @@ where mod tests { use super::*; use crate::{ + common::tests::{uxt, TestApi}, graph::Pool, - tests::{uxt, TestApi}, }; use futures::executor::block_on; use sc_transaction_pool_api::TransactionSource; @@ -391,13 +394,16 @@ mod tests { nonce: 0, }); - let hash_of_block0 = api.expect_hash_from_number(0); + let han_of_block0 = api.expect_hash_and_number(0); - let uxt_hash = - block_on(pool.submit_one(hash_of_block0, TransactionSource::External, uxt.clone())) - .expect("Should be valid"); + let uxt_hash = block_on(pool.submit_one( + &han_of_block0, + TransactionSource::External, + uxt.clone().into(), + )) + .expect("Should be valid"); - block_on(queue.revalidate_later(hash_of_block0, vec![uxt_hash])); + block_on(queue.revalidate_later(han_of_block0.hash, vec![uxt_hash])); // revalidated in sync offload 2nd time assert_eq!(api.validation_requests().len(), 2); @@ -424,21 +430,23 @@ mod tests { nonce: 1, }); - let hash_of_block0 = api.expect_hash_from_number(0); + let han_of_block0 = api.expect_hash_and_number(0); let unknown_block = H256::repeat_byte(0x13); - let uxt_hashes = - block_on(pool.submit_at(hash_of_block0, TransactionSource::External, vec![uxt0, uxt1])) - .expect("Should be valid") - .into_iter() - .map(|r| r.expect("Should be valid")) - .collect::>(); + let uxt_hashes = block_on(pool.submit_at( + &han_of_block0, + TransactionSource::External, + vec![uxt0.into(), uxt1.into()], + )) + .into_iter() + .map(|r| r.expect("Should be valid")) + .collect::>(); assert_eq!(api.validation_requests().len(), 2); assert_eq!(pool.validated_pool().status().ready, 2); // revalidation works fine for block 0: - block_on(queue.revalidate_later(hash_of_block0, uxt_hashes.clone())); + block_on(queue.revalidate_later(han_of_block0.hash, uxt_hashes.clone())); assert_eq!(api.validation_requests().len(), 4); assert_eq!(pool.validated_pool().status().ready, 2); diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs new file mode 100644 index 00000000000..6b4feca44bf --- /dev/null +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -0,0 +1,790 @@ +// 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 . + +//! Substrate transaction pool implementation. + +use super::{metrics::MetricsLink as PrometheusMetrics, revalidation}; +pub use crate::{ + api::FullChainApi, + graph::{ChainApi, ValidatedTransaction}, +}; +use crate::{ + common::{ + enactment_state::{EnactmentAction, EnactmentState}, + error, + log_xt::log_xt_trace, + }, + graph, + graph::{ExtrinsicHash, IsValidator}, + PolledIterator, ReadyIteratorFor, LOG_TARGET, +}; +use async_trait::async_trait; +use futures::{channel::oneshot, future, prelude::*, Future, FutureExt}; +use parking_lot::Mutex; +use prometheus_endpoint::Registry as PrometheusRegistry; +use sc_transaction_pool_api::{ + error::Error as TxPoolError, ChainEvent, ImportNotificationStream, MaintainedTransactionPool, + PoolFuture, PoolStatus, TransactionFor, TransactionPool, TransactionSource, + TransactionStatusStreamFor, TxHash, +}; +use sp_blockchain::{HashAndNumber, TreeRoute}; +use sp_core::traits::SpawnEssentialNamed; +use sp_runtime::{ + generic::BlockId, + traits::{AtLeast32Bit, Block as BlockT, Extrinsic, Header as HeaderT, NumberFor, Zero}, +}; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + sync::Arc, + time::Instant, +}; +use tokio::select; + +/// Basic implementation of transaction pool that can be customized by providing PoolApi. +pub struct BasicPool +where + Block: BlockT, + PoolApi: graph::ChainApi, +{ + pool: Arc>, + api: Arc, + revalidation_strategy: Arc>>>, + revalidation_queue: Arc>, + ready_poll: Arc, Block>>>, + metrics: PrometheusMetrics, + enactment_state: Arc>>, +} + +struct ReadyPoll { + updated_at: NumberFor, + pollers: Vec<(NumberFor, oneshot::Sender)>, +} + +impl Default for ReadyPoll { + fn default() -> Self { + Self { updated_at: NumberFor::::zero(), pollers: Default::default() } + } +} + +impl ReadyPoll { + fn new(best_block_number: NumberFor) -> Self { + Self { updated_at: best_block_number, pollers: Default::default() } + } + + fn trigger(&mut self, number: NumberFor, iterator_factory: impl Fn() -> T) { + self.updated_at = number; + + let mut idx = 0; + while idx < self.pollers.len() { + if self.pollers[idx].0 <= number { + let poller_sender = self.pollers.swap_remove(idx); + log::trace!(target: LOG_TARGET, "Sending ready signal at block {}", number); + let _ = poller_sender.1.send(iterator_factory()); + } else { + idx += 1; + } + } + } + + fn add(&mut self, number: NumberFor) -> oneshot::Receiver { + let (sender, receiver) = oneshot::channel(); + self.pollers.push((number, sender)); + receiver + } + + fn updated_at(&self) -> NumberFor { + self.updated_at + } +} + +/// Type of revalidation. +pub enum RevalidationType { + /// Light revalidation type. + /// + /// During maintenance, transaction pool makes periodic revalidation + /// of all transactions depending on number of blocks or time passed. + /// Also this kind of revalidation does not resubmit transactions from + /// retracted blocks, since it is too expensive. + Light, + + /// Full revalidation type. + /// + /// During maintenance, transaction pool revalidates some fixed amount of + /// transactions from the pool of valid transactions. + Full, +} + +impl BasicPool +where + Block: BlockT, + PoolApi: graph::ChainApi + 'static, +{ + /// Create new basic transaction pool with provided api, for tests. + pub fn new_test( + pool_api: Arc, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + options: graph::Options, + ) -> (Self, Pin + Send>>) { + let pool = Arc::new(graph::Pool::new(options, true.into(), pool_api.clone())); + let (revalidation_queue, background_task) = revalidation::RevalidationQueue::new_background( + pool_api.clone(), + pool.clone(), + finalized_hash, + ); + ( + Self { + api: pool_api, + pool, + revalidation_queue: Arc::new(revalidation_queue), + revalidation_strategy: Arc::new(Mutex::new(RevalidationStrategy::Always)), + ready_poll: Default::default(), + metrics: Default::default(), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), + }, + background_task, + ) + } + + /// Create new basic transaction pool with provided api and custom + /// revalidation type. + pub fn with_revalidation_type( + options: graph::Options, + is_validator: IsValidator, + pool_api: Arc, + prometheus: Option<&PrometheusRegistry>, + revalidation_type: RevalidationType, + spawner: impl SpawnEssentialNamed, + best_block_number: NumberFor, + best_block_hash: Block::Hash, + finalized_hash: Block::Hash, + ) -> Self { + let pool = Arc::new(graph::Pool::new(options, is_validator, pool_api.clone())); + let (revalidation_queue, background_task) = match revalidation_type { + RevalidationType::Light => + (revalidation::RevalidationQueue::new(pool_api.clone(), pool.clone()), None), + RevalidationType::Full => { + let (queue, background) = revalidation::RevalidationQueue::new_background( + pool_api.clone(), + pool.clone(), + finalized_hash, + ); + (queue, Some(background)) + }, + }; + + if let Some(background_task) = background_task { + spawner.spawn_essential("txpool-background", Some("transaction-pool"), background_task); + } + + Self { + api: pool_api, + pool, + revalidation_queue: Arc::new(revalidation_queue), + revalidation_strategy: Arc::new(Mutex::new(match revalidation_type { + RevalidationType::Light => + RevalidationStrategy::Light(RevalidationStatus::NotScheduled), + RevalidationType::Full => RevalidationStrategy::Always, + })), + ready_poll: Arc::new(Mutex::new(ReadyPoll::new(best_block_number))), + metrics: PrometheusMetrics::new(prometheus), + enactment_state: Arc::new(Mutex::new(EnactmentState::new( + best_block_hash, + finalized_hash, + ))), + } + } + + /// Gets shared reference to the underlying pool. + pub fn pool(&self) -> &Arc> { + &self.pool + } + + /// Get access to the underlying api + pub fn api(&self) -> &PoolApi { + &self.api + } + + fn ready_at_with_timeout_internal( + &self, + at: Block::Hash, + timeout: std::time::Duration, + ) -> PolledIterator { + let timeout = futures_timer::Delay::new(timeout); + let ready_maintained = self.ready_at(at); + let ready_current = self.ready(); + + let ready = async { + select! { + ready = ready_maintained => ready, + _ = timeout => ready_current + } + }; + + Box::pin(ready) + } +} + +impl TransactionPool for BasicPool +where + Block: BlockT, + PoolApi: 'static + graph::ChainApi, +{ + type Block = PoolApi::Block; + type Hash = graph::ExtrinsicHash; + type InPoolTransaction = + graph::base_pool::Transaction, graph::ExtrinsicFor>; + type Error = PoolApi::Error; + + fn submit_at( + &self, + at: ::Hash, + source: TransactionSource, + xts: Vec>, + ) -> PoolFuture, Self::Error>>, Self::Error> { + let pool = self.pool.clone(); + let xts = xts.into_iter().map(Arc::from).collect::>(); + + self.metrics + .report(|metrics| metrics.submitted_transactions.inc_by(xts.len() as u64)); + + let number = self.api.resolve_block_number(at); + async move { + let at = HashAndNumber { hash: at, number: number? }; + Ok(pool.submit_at(&at, source, xts).await) + } + .boxed() + } + + fn submit_one( + &self, + at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture, Self::Error> { + let pool = self.pool.clone(); + let xt = Arc::from(xt); + + self.metrics.report(|metrics| metrics.submitted_transactions.inc()); + + let number = self.api.resolve_block_number(at); + async move { + let at = HashAndNumber { hash: at, number: number? }; + pool.submit_one(&at, source, xt).await + } + .boxed() + } + + fn submit_and_watch( + &self, + at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture>>, Self::Error> { + let pool = self.pool.clone(); + let xt = Arc::from(xt); + + self.metrics.report(|metrics| metrics.submitted_transactions.inc()); + + let number = self.api.resolve_block_number(at); + + async move { + let at = HashAndNumber { hash: at, number: number? }; + let watcher = pool.submit_and_watch(&at, source, xt).await?; + + Ok(watcher.into_stream().boxed()) + } + .boxed() + } + + fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { + let removed = self.pool.validated_pool().remove_invalid(hashes); + self.metrics + .report(|metrics| metrics.validations_invalid.inc_by(removed.len() as u64)); + removed + } + + fn status(&self) -> PoolStatus { + self.pool.validated_pool().status() + } + + fn import_notification_stream(&self) -> ImportNotificationStream> { + self.pool.validated_pool().import_notification_stream() + } + + fn hash_of(&self, xt: &TransactionFor) -> TxHash { + self.pool.hash_of(xt) + } + + fn on_broadcasted(&self, propagations: HashMap, Vec>) { + self.pool.validated_pool().on_broadcasted(propagations) + } + + fn ready_transaction(&self, hash: &TxHash) -> Option> { + self.pool.validated_pool().ready_by_hash(hash) + } + + fn ready_at(&self, at: ::Hash) -> PolledIterator { + let Ok(at) = self.api.resolve_block_number(at) else { + return async { Box::new(std::iter::empty()) as Box<_> }.boxed() + }; + + let status = self.status(); + // If there are no transactions in the pool, it is fine to return early. + // + // There could be transaction being added because of some re-org happening at the relevant + // block, but this is relative unlikely. + if status.ready == 0 && status.future == 0 { + return async { Box::new(std::iter::empty()) as Box<_> }.boxed() + } + + if self.ready_poll.lock().updated_at() >= at { + log::trace!(target: LOG_TARGET, "Transaction pool already processed block #{}", at); + let iterator: ReadyIteratorFor = Box::new(self.pool.validated_pool().ready()); + return async move { iterator }.boxed() + } + + self.ready_poll + .lock() + .add(at) + .map(|received| { + received.unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Error receiving pending set: {:?}", e); + Box::new(std::iter::empty()) + }) + }) + .boxed() + } + + fn ready(&self) -> ReadyIteratorFor { + Box::new(self.pool.validated_pool().ready()) + } + + fn futures(&self) -> Vec { + let pool = self.pool.validated_pool().pool.read(); + pool.futures().cloned().collect::>() + } + + fn ready_at_with_timeout( + &self, + at: ::Hash, + timeout: std::time::Duration, + ) -> PolledIterator { + self.ready_at_with_timeout_internal(at, timeout) + } +} + +impl BasicPool, Block> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sc_client_api::ExecutorProvider + + sc_client_api::UsageProvider + + sp_blockchain::HeaderMetadata + + Send + + Sync + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + /// Create new basic transaction pool for a full node with the provided api. + pub fn new_full( + options: graph::Options, + is_validator: IsValidator, + prometheus: Option<&PrometheusRegistry>, + spawner: impl SpawnEssentialNamed, + client: Arc, + ) -> Self { + let pool_api = Arc::new(FullChainApi::new(client.clone(), prometheus, &spawner)); + let pool = Self::with_revalidation_type( + options, + is_validator, + pool_api, + prometheus, + RevalidationType::Full, + spawner, + client.usage_info().chain.best_number, + client.usage_info().chain.best_hash, + client.usage_info().chain.finalized_hash, + ); + + pool + } +} + +impl sc_transaction_pool_api::LocalTransactionPool + for BasicPool, Block> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata, + Client: Send + Sync + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + type Block = Block; + type Hash = graph::ExtrinsicHash>; + type Error = as graph::ChainApi>::Error; + + fn submit_local( + &self, + at: Block::Hash, + xt: sc_transaction_pool_api::LocalTransactionFor, + ) -> Result { + use sp_runtime::{ + traits::SaturatedConversion, transaction_validity::TransactionValidityError, + }; + + let validity = self + .api + .validate_transaction_blocking(at, TransactionSource::Local, Arc::from(xt.clone()))? + .map_err(|e| { + Self::Error::Pool(match e { + TransactionValidityError::Invalid(i) => TxPoolError::InvalidTransaction(i), + TransactionValidityError::Unknown(u) => TxPoolError::UnknownTransaction(u), + }) + })?; + + let (hash, bytes) = self.pool.validated_pool().api().hash_and_length(&xt); + let block_number = self + .api + .block_id_to_number(&BlockId::hash(at))? + .ok_or_else(|| error::Error::BlockIdConversion(format!("{:?}", at)))?; + + let validated = ValidatedTransaction::valid_at( + block_number.saturated_into::(), + hash, + TransactionSource::Local, + Arc::from(xt), + bytes, + validity, + ); + + self.pool.validated_pool().submit(vec![validated]).remove(0) + } +} + +#[cfg_attr(test, derive(Debug))] +enum RevalidationStatus { + /// The revalidation has never been completed. + NotScheduled, + /// The revalidation is scheduled. + Scheduled(Option, Option), + /// The revalidation is in progress. + InProgress, +} + +enum RevalidationStrategy { + Always, + Light(RevalidationStatus), +} + +struct RevalidationAction { + revalidate: bool, + resubmit: bool, +} + +impl RevalidationStrategy { + pub fn clear(&mut self) { + if let Self::Light(status) = self { + status.clear() + } + } + + pub fn next( + &mut self, + block: N, + revalidate_time_period: Option, + revalidate_block_period: Option, + ) -> RevalidationAction { + match self { + Self::Light(status) => RevalidationAction { + revalidate: status.next_required( + block, + revalidate_time_period, + revalidate_block_period, + ), + resubmit: false, + }, + Self::Always => RevalidationAction { revalidate: true, resubmit: true }, + } + } +} + +impl RevalidationStatus { + /// Called when revalidation is completed. + pub fn clear(&mut self) { + *self = Self::NotScheduled; + } + + /// Returns true if revalidation is required. + pub fn next_required( + &mut self, + block: N, + revalidate_time_period: Option, + revalidate_block_period: Option, + ) -> bool { + match *self { + Self::NotScheduled => { + *self = Self::Scheduled( + revalidate_time_period.map(|period| Instant::now() + period), + revalidate_block_period.map(|period| block + period), + ); + false + }, + Self::Scheduled(revalidate_at_time, revalidate_at_block) => { + let is_required = + revalidate_at_time.map(|at| Instant::now() >= at).unwrap_or(false) || + revalidate_at_block.map(|at| block >= at).unwrap_or(false); + if is_required { + *self = Self::InProgress; + } + is_required + }, + Self::InProgress => false, + } + } +} + +/// Prune the known txs for the given block. +pub async fn prune_known_txs_for_block>( + at: &HashAndNumber, + api: &Api, + pool: &graph::Pool, +) -> Vec> { + let extrinsics = api + .block_body(at.hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Prune known transactions: error request: {}", e); + None + }) + .unwrap_or_default(); + + let hashes = extrinsics.iter().map(|tx| pool.hash_of(tx)).collect::>(); + + let header = match api.block_header(at.hash) { + Ok(Some(h)) => h, + Ok(None) => { + log::trace!(target: LOG_TARGET, "Could not find header for {:?}.", at.hash); + return hashes + }, + Err(e) => { + log::trace!(target: LOG_TARGET, "Error retrieving header for {:?}: {}", at.hash, e); + return hashes + }, + }; + + log_xt_trace!(target: LOG_TARGET, &hashes, "[{:?}] Pruning transaction."); + + pool.prune(at, *header.parent_hash(), &extrinsics).await; + hashes +} + +impl BasicPool +where + Block: BlockT, + PoolApi: 'static + graph::ChainApi, +{ + /// Handles enactment and retraction of blocks, prunes stale transactions + /// (that have already been enacted) and resubmits transactions that were + /// retracted. + async fn handle_enactment(&self, tree_route: TreeRoute) { + log::trace!(target: LOG_TARGET, "handle_enactment tree_route: {tree_route:?}"); + let pool = self.pool.clone(); + let api = self.api.clone(); + + let hash_and_number = match tree_route.last() { + Some(hash_and_number) => hash_and_number, + None => { + log::warn!( + target: LOG_TARGET, + "Skipping ChainEvent - no last block in tree route {:?}", + tree_route, + ); + return + }, + }; + + let next_action = self.revalidation_strategy.lock().next( + hash_and_number.number, + Some(std::time::Duration::from_secs(60)), + Some(20u32.into()), + ); + + // We keep track of everything we prune so that later we won't add + // transactions with those hashes from the retracted blocks. + let mut pruned_log = HashSet::>::new(); + + // If there is a tree route, we use this to prune known tx based on the enacted + // blocks. Before pruning enacted transactions, we inform the listeners about + // retracted blocks and their transactions. This order is important, because + // if we enact and retract the same transaction at the same time, we want to + // send first the retract and then the prune event. + for retracted in tree_route.retracted() { + // notify txs awaiting finality that it has been retracted + pool.validated_pool().on_block_retracted(retracted.hash); + } + + future::join_all( + tree_route.enacted().iter().map(|h| prune_known_txs_for_block(h, &*api, &*pool)), + ) + .await + .into_iter() + .for_each(|enacted_log| { + pruned_log.extend(enacted_log); + }); + + self.metrics + .report(|metrics| metrics.block_transactions_pruned.inc_by(pruned_log.len() as u64)); + + if next_action.resubmit { + let mut resubmit_transactions = Vec::new(); + + for retracted in tree_route.retracted() { + let hash = retracted.hash; + + let block_transactions = api + .block_body(hash) + .await + .unwrap_or_else(|e| { + log::warn!(target: LOG_TARGET, "Failed to fetch block body: {}", e); + None + }) + .unwrap_or_default() + .into_iter() + .filter(|tx| tx.is_signed().unwrap_or(true)); + + let mut resubmitted_to_report = 0; + + resubmit_transactions.extend( + //todo: arctx - we need to get ref from somewhere + block_transactions.into_iter().map(Arc::from).filter(|tx| { + let tx_hash = pool.hash_of(tx); + let contains = pruned_log.contains(&tx_hash); + + // need to count all transactions, not just filtered, here + resubmitted_to_report += 1; + + if !contains { + log::trace!( + target: LOG_TARGET, + "[{:?}]: Resubmitting from retracted block {:?}", + tx_hash, + hash, + ); + } + !contains + }), + ); + + self.metrics.report(|metrics| { + metrics.block_transactions_resubmitted.inc_by(resubmitted_to_report) + }); + } + + pool.resubmit_at( + &hash_and_number, + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TransactionSource::External, + resubmit_transactions, + ) + .await; + } + + let extra_pool = pool.clone(); + // After #5200 lands, this arguably might be moved to the + // handler of "all blocks notification". + self.ready_poll + .lock() + .trigger(hash_and_number.number, move || Box::new(extra_pool.validated_pool().ready())); + + if next_action.revalidate { + let hashes = pool.validated_pool().ready().map(|tx| tx.hash).collect(); + self.revalidation_queue.revalidate_later(hash_and_number.hash, hashes).await; + + self.revalidation_strategy.lock().clear(); + } + } +} + +#[async_trait] +impl MaintainedTransactionPool for BasicPool +where + Block: BlockT, + PoolApi: 'static + graph::ChainApi, +{ + async fn maintain(&self, event: ChainEvent) { + let prev_finalized_block = self.enactment_state.lock().recent_finalized_block(); + let compute_tree_route = |from, to| -> Result, String> { + match self.api.tree_route(from, to) { + Ok(tree_route) => Ok(tree_route), + Err(e) => + return Err(format!( + "Error occurred while computing tree_route from {from:?} to {to:?}: {e}" + )), + } + }; + let block_id_to_number = + |hash| self.api.block_id_to_number(&BlockId::Hash(hash)).map_err(|e| format!("{}", e)); + + let result = + self.enactment_state + .lock() + .update(&event, &compute_tree_route, &block_id_to_number); + + match result { + Err(msg) => { + log::trace!(target: LOG_TARGET, "{msg}"); + self.enactment_state.lock().force_update(&event); + }, + Ok(EnactmentAction::Skip) => return, + Ok(EnactmentAction::HandleFinalization) => {}, + Ok(EnactmentAction::HandleEnactment(tree_route)) => { + self.handle_enactment(tree_route).await; + }, + }; + + if let ChainEvent::Finalized { hash, tree_route } = event { + log::trace!( + target: LOG_TARGET, + "on-finalized enacted: {tree_route:?}, previously finalized: \ + {prev_finalized_block:?}", + ); + + for hash in tree_route.iter().chain(std::iter::once(&hash)) { + if let Err(e) = self.pool.validated_pool().on_block_finalized(*hash).await { + log::warn!( + target: LOG_TARGET, + "Error occurred while attempting to notify watchers about finalization {}: {}", + hash, e + ) + } + } + } + } +} diff --git a/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs new file mode 100644 index 00000000000..4e1b53833b8 --- /dev/null +++ b/substrate/client/transaction-pool/src/transaction_pool_wrapper.rs @@ -0,0 +1,198 @@ +// 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 . + +//! Transaction pool wrapper. Provides a type for wrapping object providing actual implementation of +//! transaction pool. + +use crate::{ + builder::FullClientTransactionPool, + graph::{base_pool::Transaction, ExtrinsicFor, ExtrinsicHash}, + ChainApi, FullChainApi, +}; +use async_trait::async_trait; +use sc_transaction_pool_api::{ + ChainEvent, ImportNotificationStream, LocalTransactionFor, LocalTransactionPool, + MaintainedTransactionPool, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, + TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, +}; +use sp_runtime::traits::Block as BlockT; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; + +/// The wrapper for actual object providing implementation of TransactionPool. +/// +/// This wraps actual implementation of the TransactionPool, e.g. fork-aware or single-state. +pub struct TransactionPoolWrapper( + pub Box>, +) +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue; + +impl TransactionPool for TransactionPoolWrapper +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + type Block = Block; + type Hash = ExtrinsicHash>; + type InPoolTransaction = Transaction< + ExtrinsicHash>, + ExtrinsicFor>, + >; + type Error = as ChainApi>::Error; + + fn submit_at( + &self, + at: ::Hash, + source: TransactionSource, + xts: Vec>, + ) -> PoolFuture, Self::Error>>, Self::Error> { + self.0.submit_at(at, source, xts) + } + + fn submit_one( + &self, + at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture, Self::Error> { + self.0.submit_one(at, source, xt) + } + + fn submit_and_watch( + &self, + at: ::Hash, + source: TransactionSource, + xt: TransactionFor, + ) -> PoolFuture>>, Self::Error> { + self.0.submit_and_watch(at, source, xt) + } + + fn ready_at( + &self, + at: ::Hash, + ) -> Pin< + Box< + dyn Future< + Output = Box> + Send>, + > + Send, + >, + > { + self.0.ready_at(at) + } + + fn ready(&self) -> Box> + Send> { + self.0.ready() + } + + fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { + self.0.remove_invalid(hashes) + } + + fn futures(&self) -> Vec { + self.0.futures() + } + + fn status(&self) -> PoolStatus { + self.0.status() + } + + fn import_notification_stream(&self) -> ImportNotificationStream> { + self.0.import_notification_stream() + } + + fn on_broadcasted(&self, propagations: HashMap, Vec>) { + self.0.on_broadcasted(propagations) + } + + fn hash_of(&self, xt: &TransactionFor) -> TxHash { + self.0.hash_of(xt) + } + + fn ready_transaction(&self, hash: &TxHash) -> Option> { + self.0.ready_transaction(hash) + } + + fn ready_at_with_timeout( + &self, + at: ::Hash, + timeout: std::time::Duration, + ) -> Pin< + Box< + dyn Future< + Output = Box> + Send>, + > + Send + + '_, + >, + > { + self.0.ready_at_with_timeout(at, timeout) + } +} + +#[async_trait] +impl MaintainedTransactionPool for TransactionPoolWrapper +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + async fn maintain(&self, event: ChainEvent) { + self.0.maintain(event).await; + } +} + +impl LocalTransactionPool for TransactionPoolWrapper +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::BlockBackend + + sc_client_api::blockchain::HeaderBackend + + sp_runtime::traits::BlockIdTo + + sp_blockchain::HeaderMetadata + + 'static, + Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, +{ + type Block = Block; + type Hash = ExtrinsicHash>; + type Error = as ChainApi>::Error; + + fn submit_local( + &self, + at: ::Hash, + xt: LocalTransactionFor, + ) -> Result { + self.0.submit_local(at, xt) + } +} diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs new file mode 100644 index 00000000000..9f343a9bd02 --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -0,0 +1,2617 @@ +// 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 . + +//! Tests for fork-aware transaction pool. + +use fatp_common::{ + finalized_block_event, invalid_hash, new_best_block_event, pool, pool_with_api, + test_chain_with_forks, LOG_TARGET, SOURCE, +}; +use futures::{executor::block_on, task::Poll, FutureExt, StreamExt}; +use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::{ + error::{Error as TxPoolError, IntoPoolError}, + ChainEvent, MaintainedTransactionPool, TransactionPool, TransactionStatus, +}; +use sp_runtime::transaction_validity::InvalidTransaction; +use std::{sync::Arc, time::Duration}; +use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_transaction_pool::uxt; + +pub mod fatp_common; + +// Some ideas for tests: +// - view.ready iterator +// - stale transaction submission when there is single view only (expect error) +// - stale transaction submission when there are more views (expect ok if tx is ok for at least one +// view) +// - view count (e.g. same new block notified twice) +// - invalid with many views (different cases) +// +// review (from old pool) and maybe re-use: +// fn import_notification_to_pool_maintain_works() +// fn prune_tags_should_work() +// fn should_ban_invalid_transactions() +// fn should_correctly_prune_transactions_providing_more_than_one_tag() + +#[test] +fn fatp_no_view_future_and_ready_submit_one_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(header.hash(), SOURCE, xt0.clone()), + pool.submit_one(header.hash(), SOURCE, xt1.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + + assert!(results.iter().all(|r| { r.is_ok() })); +} + +#[test] +fn fatp_no_view_future_and_ready_submit_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + + let xts0 = (200..205).map(|i| uxt(Alice, i)).collect::>(); + let xts1 = (205..210).map(|i| uxt(Alice, i)).collect::>(); + let xts2 = (215..220).map(|i| uxt(Alice, i)).collect::>(); + + let submissions = vec![ + pool.submit_at(header.hash(), SOURCE, xts0.clone()), + pool.submit_at(header.hash(), SOURCE, xts1.clone()), + pool.submit_at(header.hash(), SOURCE, xts2.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + + assert!(results.into_iter().flat_map(|x| x.unwrap()).all(|r| { r.is_ok() })); +} + +#[test] +fn fatp_no_view_submit_already_imported_reports_error() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + + let xts0 = (215..220).map(|i| uxt(Alice, i)).collect::>(); + let xts1 = xts0.clone(); + + let submission_ok = pool.submit_at(header.hash(), SOURCE, xts0.clone()); + let results = block_on(submission_ok); + assert!(results.unwrap().into_iter().all(|r| r.is_ok())); + + let submission_failing = pool.submit_at(header.hash(), SOURCE, xts1.clone()); + let results = block_on(submission_failing); + + assert!(results + .unwrap() + .into_iter() + .all(|r| { matches!(r.unwrap_err().0, TxPoolError::AlreadyImported(_)) })); +} + +#[test] +fn fatp_one_view_future_and_ready_submit_one_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + // let header01b = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(header.hash(), SOURCE, xt0.clone()), + pool.submit_one(header.hash(), SOURCE, xt1.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + assert_pool_status!(header.hash(), &pool, 1, 1); +} + +#[test] +fn fatp_one_view_future_and_ready_submit_many_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + // let header01b = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header.hash()); + block_on(pool.maintain(event)); + + let xts0 = (200..205).map(|i| uxt(Alice, i)).collect::>(); + let xts1 = (205..210).map(|i| uxt(Alice, i)).collect::>(); + let xts2 = (215..220).map(|i| uxt(Alice, i)).collect::>(); + + let submissions = vec![ + pool.submit_at(header.hash(), SOURCE, xts0.clone()), + pool.submit_at(header.hash(), SOURCE, xts1.clone()), + pool.submit_at(header.hash(), SOURCE, xts2.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + assert_pool_status!(header.hash(), &pool, 10, 5); +} + +#[test] +fn fatp_one_view_stale_submit_one_fails() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 100); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + let results = block_on(futures::future::join_all(submissions)); + + //xt0 should be stale + assert!(matches!( + &results[0].as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) + )); + + assert_pool_status!(header.hash(), &pool, 0, 0); +} + +#[test] +fn fatp_one_view_stale_submit_many_fails() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header.hash()); + block_on(pool.maintain(event)); + + let xts0 = (100..105).map(|i| uxt(Alice, i)).collect::>(); + let xts1 = (105..110).map(|i| uxt(Alice, i)).collect::>(); + let xts2 = (195..201).map(|i| uxt(Alice, i)).collect::>(); + + let submissions = vec![ + pool.submit_at(header.hash(), SOURCE, xts0.clone()), + pool.submit_at(header.hash(), SOURCE, xts1.clone()), + pool.submit_at(header.hash(), SOURCE, xts2.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + + //xts2 contains one ready transaction (nonce:200) + let mut results = results.into_iter().flat_map(|x| x.unwrap()).collect::>(); + log::debug!("{:#?}", results); + assert!(results.pop().unwrap().is_ok()); + assert!(results.into_iter().all(|r| { + matches!( + &r.as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) + ) + })); + + assert_pool_status!(header.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_one_view_future_turns_to_ready_works() { + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + let at = header.hash(); + let event = new_best_block_event(&pool, None, at); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 201); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert!(pool.ready().count() == 0); + assert_pool_status!(at, &pool, 0, 1); + + let xt1 = uxt(Alice, 200); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let ready: Vec<_> = pool.ready().map(|v| (*v.data).clone()).collect(); + assert_eq!(ready, vec![xt1, xt0]); + assert_pool_status!(at, &pool, 2, 0); +} + +#[test] +fn fatp_one_view_ready_gets_pruned() { + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + let block1 = header.hash(); + let event = new_best_block_event(&pool, None, block1); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let pending: Vec<_> = pool.ready().map(|v| (*v.data).clone()).collect(); + assert_eq!(pending, vec![xt0.clone()]); + assert_eq!(pool.status_all()[&block1].ready, 1); + + let header = api.push_block(2, vec![xt0], true); + let block2 = header.hash(); + let event = new_best_block_event(&pool, Some(block1), block2); + block_on(pool.maintain(event)); + assert_pool_status!(block2, &pool, 0, 0); + assert!(pool.ready().count() == 0); +} + +#[test] +fn fatp_one_view_ready_turns_to_stale_works() { + let (pool, api, _) = pool(); + + let header = api.push_block(1, vec![], true); + let block1 = header.hash(); + let event = new_best_block_event(&pool, None, block1); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let pending: Vec<_> = pool.ready().map(|v| (*v.data).clone()).collect(); + assert_eq!(pending, vec![xt0.clone()]); + assert_eq!(pool.status_all()[&block1].ready, 1); + + let header = api.push_block(2, vec![], true); + let block2 = header.hash(); + //tricky: typically the block2 shall contain conflicting transaction for Alice. In this test we + //want to check revalidation, so we manually adjust nonce. + api.set_nonce(block2, Alice.into(), 201); + let event = new_best_block_event(&pool, Some(block1), block2); + //note: blocking revalidation (w/o background worker) which is used in this test will detect + // xt0 is stale + block_on(pool.maintain(event)); + //todo: should it work at all? (it requires better revalidation: mempool keeping validated txs) + // assert_pool_status!(block2, &pool, 0, 0); + // assert!(pool.ready(block2).unwrap().count() == 0); +} + +#[test] +fn fatp_two_views_future_and_ready_submit_one() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let genesis = api.genesis_hash(); + let header01a = api.push_block(1, vec![], true); + let header01b = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + api.set_nonce(header01b.hash(), Alice.into(), 202); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(genesis, SOURCE, xt0.clone()), + pool.submit_one(genesis, SOURCE, xt1.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + assert_pool_status!(header01a.hash(), &pool, 1, 1); + assert_pool_status!(header01b.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_two_views_future_and_ready_submit_many() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01a = api.push_block(1, vec![], true); + let header01b = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + api.set_nonce(header01b.hash(), Alice.into(), 215); + + let xts0 = (200..205).map(|i| uxt(Alice, i)).collect::>(); + let xts1 = (205..210).map(|i| uxt(Alice, i)).collect::>(); + let xts2 = (215..220).map(|i| uxt(Alice, i)).collect::>(); + + let submissions = vec![ + pool.submit_at(invalid_hash(), SOURCE, xts0.clone()), + pool.submit_at(invalid_hash(), SOURCE, xts1.clone()), + pool.submit_at(invalid_hash(), SOURCE, xts2.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + assert_pool_status!(header01a.hash(), &pool, 10, 5); + assert_pool_status!(header01b.hash(), &pool, 5, 0); +} + +#[test] +fn fatp_two_views_submit_many_variations() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 206); + let xt1 = uxt(Alice, 206); + + let result = block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())); + assert!(result.is_ok()); + + let header01a = api.push_block(1, vec![xt0.clone()], true); + let header01b = api.push_block(1, vec![xt0.clone()], true); + + api.set_nonce(header01a.hash(), Alice.into(), 201); + api.set_nonce(header01b.hash(), Alice.into(), 202); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + let mut xts = (199..204).map(|i| uxt(Alice, i)).collect::>(); + xts.push(xt0); + xts.push(xt1); + + let results = block_on(pool.submit_at(invalid_hash(), SOURCE, xts.clone())).unwrap(); + + log::debug!(target:LOG_TARGET, "res: {:#?}", results); + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + (0..2).for_each(|i| { + assert!(matches!( + results[i].as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) + )); + }); + //note: tx at 2 is valid at header01a and invalid at header01b + (2..5).for_each(|i| { + assert_eq!(*results[i].as_ref().unwrap(), api.hash_and_length(&xts[i]).0); + }); + //xt0 at index 5 (transaction from the imported block, gets banned when pruned) + assert!(matches!(results[5].as_ref().unwrap_err().0, TxPoolError::TemporarilyBanned)); + //xt1 at index 6 + assert!(matches!(results[6].as_ref().unwrap_err().0, TxPoolError::AlreadyImported(_))); +} + +#[test] +fn fatp_linear_progress() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let f11 = forks[1][1].hash(); + let f13 = forks[1][3].hash(); + + let event = new_best_block_event(&pool, None, f11); + block_on(pool.maintain(event)); + + let xt0 = uxt(Bob, 203); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f11), f13); + log::debug!(target:LOG_TARGET, "event: {:#?}", event); + block_on(pool.maintain(event)); + + //note: we only keep tip of the fork + assert_eq!(pool.active_views_count(), 1); + assert_pool_status!(f13, &pool, 1, 0); +} + +#[test] +fn fatp_linear_old_ready_becoming_stale() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + // Our initial transactions + let xts = vec![uxt(Alice, 300), uxt(Alice, 301), uxt(Alice, 302)]; + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + xts.into_iter().for_each(|xt| { + block_on(pool.submit_one(invalid_hash(), SOURCE, xt)).unwrap(); + }); + assert_eq!(pool.status_all()[&header01.hash()].ready, 0); + assert_eq!(pool.status_all()[&header01.hash()].future, 3); + + // Import enough blocks to make our transactions stale (longevity is 64) + let mut prev_header = header01; + for n in 2..66 { + let header = api.push_block(n, vec![], true); + let event = new_best_block_event(&pool, Some(prev_header.hash()), header.hash()); + block_on(pool.maintain(event)); + + if n == 65 { + assert_eq!(pool.status_all()[&header.hash()].ready, 0); + assert_eq!(pool.status_all()[&header.hash()].future, 0); + } else { + assert_eq!(pool.status_all()[&header.hash()].ready, 0); + assert_eq!(pool.status_all()[&header.hash()].future, 3); + } + prev_header = header; + } +} + +#[test] +fn fatp_fork_reorg() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let f03 = forks[0][3].hash(); + let f13 = forks[1][3].hash(); + + let event = new_best_block_event(&pool, None, f03); + block_on(pool.maintain(event)); + + let xt0 = uxt(Bob, 203); + let xt1 = uxt(Bob, 204); + let xt2 = uxt(Alice, 203); + let submissions = vec![ + pool.submit_one(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_one(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_one(invalid_hash(), SOURCE, xt2.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f03), f13); + log::debug!(target:LOG_TARGET, "event: {:#?}", event); + block_on(pool.maintain(event)); + + assert_pool_status!(f03, &pool, 1, 2); + assert_pool_status!(f13, &pool, 6, 0); + + //check if ready for block[1][3] contains resubmitted transactions + let mut expected = forks[0] + .iter() + .take(4) + .flat_map(|h| block_on(api.block_body(h.hash())).unwrap().unwrap()) + .collect::>(); + expected.extend_from_slice(&[xt0, xt1, xt2]); + + let ready_f13 = pool.ready().collect::>(); + expected.iter().for_each(|e| { + assert!(ready_f13.iter().any(|v| *v.data == *e)); + }); + assert_eq!(expected.len(), ready_f13.len()); +} + +#[test] +fn fatp_fork_do_resubmit_same_tx() { + let xt = uxt(Alice, 200); + + let (pool, api, _) = pool(); + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, xt.clone())).unwrap(); + assert_eq!(pool.status_all()[&header01.hash()].ready, 1); + + let header02a = api.push_block(1, vec![xt.clone()], true); + let header02b = api.push_block(1, vec![xt], true); + + let event = new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()); + api.set_nonce(header02a.hash(), Alice.into(), 201); + block_on(pool.maintain(event)); + assert_eq!(pool.status_all()[&header02b.hash()].ready, 0); + + let event = new_best_block_event(&pool, Some(api.genesis_hash()), header02b.hash()); + api.set_nonce(header02b.hash(), Alice.into(), 201); + block_on(pool.maintain(event)); + + assert_eq!(pool.status_all()[&header02b.hash()].ready, 0); +} + +#[test] +fn fatp_fork_stale_rejected() { + sp_tracing::try_init_simple(); + + // note: there are no xts in blocks on fork 0! + let (api, forks) = test_chain_with_forks::chain(Some(&|f, b| match (f, b) { + (0, _) => false, + _ => true, + })); + let (pool, _) = pool_with_api(api.clone()); + + let f03 = forks[0][3].hash(); + let f13 = forks[1][3].hash(); + + // n:201 n:202 n:203 <-- alice nonce + // F01 - F02 - F03 <-- xt2 is stale + // / + // F00 + // \ + // F11[t0] - F12[t1] - F13[t2] + // n:201 n:202 n:203 <-- bob nonce + // + // t0 = uxt(Bob,200) + // t1 = uxt(Bob,201) + // t2 = uxt(Bob,201) + // xt0 = uxt(Bob, 203) + // xt1 = uxt(Bob, 204) + // xt2 = uxt(Alice, 201); + + let event = new_best_block_event(&pool, None, f03); + block_on(pool.maintain(event)); + + let xt0 = uxt(Bob, 203); + let xt1 = uxt(Bob, 204); + let xt2 = uxt(Alice, 201); + let submissions = vec![ + pool.submit_one(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_one(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_one(invalid_hash(), SOURCE, xt2.clone()), + ]; + let submission_results = block_on(futures::future::join_all(submissions)); + let futures_f03 = pool.futures(); + + //xt2 should be stale + assert!(matches!( + &submission_results[2].as_ref().unwrap_err().0, + TxPoolError::InvalidTransaction(InvalidTransaction::Stale,) + )); + + let event = new_best_block_event(&pool, Some(f03), f13); + log::debug!(target:LOG_TARGET, "event: {:#?}", event); + block_on(pool.maintain(event)); + + assert_pool_status!(f03, &pool, 0, 2); + + //xt2 was removed from the pool, it is not becoming future: + //note: theoretically we could keep xt2 in the pool, even if it was reported as stale. But it + //seems to be an unnecessary complication. + assert_pool_status!(f13, &pool, 2, 0); + + let futures_f13 = pool.futures(); + let ready_f13 = pool.ready().collect::>(); + assert!(futures_f13.iter().next().is_none()); + assert!(futures_f03.iter().any(|v| *v.data == xt0)); + assert!(futures_f03.iter().any(|v| *v.data == xt1)); + assert!(ready_f13.iter().any(|v| *v.data == xt0)); + assert!(ready_f13.iter().any(|v| *v.data == xt1)); +} + +#[test] +fn fatp_fork_no_xts_ready_switch_to_future() { + //this scenario w/o xts is not likely to happen, but similar thing (xt changing from ready to + //future) could occur e.g. when runtime was updated on fork1. + sp_tracing::try_init_simple(); + + // note: there are no xts in blocks! + let (api, forks) = test_chain_with_forks::chain(Some(&|_, _| false)); + let (pool, _) = pool_with_api(api.clone()); + + let f03 = forks[0][3].hash(); + let f12 = forks[1][2].hash(); + + let event = new_best_block_event(&pool, None, f03); + block_on(pool.maintain(event)); + + // xt0 is ready on f03, but future on f12, f13 + let xt0 = uxt(Alice, 203); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f03), f12); + block_on(pool.maintain(event)); + + assert_pool_status!(f03, &pool, 1, 0); + // f12 was not updated - xt0 is still ready there + // (todo: can we do better? shall we revalidate all future xts?) + assert_pool_status!(f12, &pool, 1, 0); + + //xt0 becomes future, and this may only happen after view revalidation (which happens on + //finalization). So trigger it. + let event = finalized_block_event(&pool, api.genesis_hash(), f12); + block_on(pool.maintain(event)); + + // f03 still dangling + assert_eq!(pool.active_views_count(), 2); + + // wait 10 blocks for revalidation and 1 extra for applying revalidation results + let mut prev_header = forks[1][2].clone(); + log::debug!("====> {:?}", prev_header); + for _ in 3..=12 { + let header = api.push_block_with_parent(prev_header.hash(), vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + assert_pool_status!(prev_header.hash(), &pool, 0, 1); +} + +#[test] +fn fatp_ready_at_does_not_trigger() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let f03 = forks[0][3].hash(); + let f13 = forks[1][3].hash(); + + assert!(pool.ready_at(f03).now_or_never().is_none()); + assert!(pool.ready_at(f13).now_or_never().is_none()); +} + +#[test] +fn fatp_ready_at_does_not_trigger_after_submit() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let xt0 = uxt(Alice, 200); + let _ = block_on(pool.submit_one(invalid_hash(), SOURCE, xt0)); + + let f03 = forks[0][3].hash(); + let f13 = forks[1][3].hash(); + + assert!(pool.ready_at(f03).now_or_never().is_none()); + assert!(pool.ready_at(f13).now_or_never().is_none()); +} + +#[test] +fn fatp_ready_at_triggered_by_maintain() { + //this scenario w/o xts is not likely to happen, but similar thing (xt changing from ready to + //future) could occur e.g. when runtime was updated on fork1. + sp_tracing::try_init_simple(); + let (api, forks) = test_chain_with_forks::chain(Some(&|_, _| false)); + let (pool, _) = pool_with_api(api.clone()); + + let f03 = forks[0][3].hash(); + let f13 = forks[1][3].hash(); + + assert!(pool.ready_at(f03).now_or_never().is_none()); + + let event = new_best_block_event(&pool, None, f03); + block_on(pool.maintain(event)); + + assert!(pool.ready_at(f03).now_or_never().is_some()); + + let xt0 = uxt(Alice, 203); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f03), f13); + log::debug!(target:LOG_TARGET, "event: {:#?}", event); + assert!(pool.ready_at(f13).now_or_never().is_none()); + block_on(pool.maintain(event)); + assert!(pool.ready_at(f03).now_or_never().is_some()); + assert!(pool.ready_at(f13).now_or_never().is_some()); +} + +#[test] +fn fatp_ready_at_triggered_by_maintain2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let xt0 = uxt(Alice, 200); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + // let (pool, api, _guard) = maintained_pool(); + // let header = api.push_block(1, vec![], true); + // + // let xt1 = uxt(Alice, 209); + // + // block_on(pool.submit_one(api.expect_hash_from_number(1), SOURCE, xt1.clone())) + // .expect("1. Imported"); + + let noop_waker = futures::task::noop_waker(); + let mut context = futures::task::Context::from_waker(&noop_waker); + + let mut ready_set_future = pool.ready_at(header01.hash()); + if ready_set_future.poll_unpin(&mut context).is_ready() { + panic!("Ready set should not be ready before block update!"); + } + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + // block_on(pool.maintain(block_event(header))); + + match ready_set_future.poll_unpin(&mut context) { + Poll::Pending => { + panic!("Ready set should become ready after block update!"); + }, + Poll::Ready(iterator) => { + let data = iterator.collect::>(); + assert_eq!(data.len(), 1); + }, + } +} + +#[test] +fn fatp_linear_progress_finalization() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let f00 = forks[0][0].hash(); + let f12 = forks[1][2].hash(); + let f14 = forks[1][4].hash(); + + let event = new_best_block_event(&pool, None, f00); + block_on(pool.maintain(event)); + + let xt0 = uxt(Bob, 204); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f00), f12); + block_on(pool.maintain(event)); + assert_pool_status!(f12, &pool, 0, 1); + assert_eq!(pool.active_views_count(), 1); + + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + let event = ChainEvent::Finalized { hash: f14, tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + assert_eq!(pool.active_views_count(), 1); + assert_pool_status!(f14, &pool, 1, 0); +} + +#[test] +fn fatp_fork_finalization_removes_stale_views() { + sp_tracing::try_init_simple(); + + let (api, forks) = test_chain_with_forks::chain(None); + let (pool, _) = pool_with_api(api.clone()); + + let f00 = forks[0][0].hash(); + let f12 = forks[1][2].hash(); + let f14 = forks[1][4].hash(); + let f02 = forks[0][2].hash(); + let f03 = forks[0][3].hash(); + let f04 = forks[0][4].hash(); + + let xt0 = uxt(Bob, 203); + let submissions = vec![pool.submit_one(invalid_hash(), SOURCE, xt0.clone())]; + block_on(futures::future::join_all(submissions)); + + let event = new_best_block_event(&pool, Some(f00), f12); + block_on(pool.maintain(event)); + let event = new_best_block_event(&pool, Some(f00), f14); + block_on(pool.maintain(event)); + let event = new_best_block_event(&pool, Some(f00), f02); + block_on(pool.maintain(event)); + + //only views at the tips of the forks are kept + assert_eq!(pool.active_views_count(), 2); + + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + + let event = ChainEvent::Finalized { hash: f03, tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + log::debug!(target:LOG_TARGET, "stats: {:#?}", pool.status_all()); + // note: currently the pruning views only cleans views with block number less than finalized + // block. views with higher number on other forks are not cleaned (will be done in next round). + assert_eq!(pool.active_views_count(), 2); + + let event = ChainEvent::Finalized { hash: f04, tree_route: Arc::from(vec![]) }; + block_on(pool.maintain(event)); + assert_eq!(pool.active_views_count(), 1); +} + +#[test] +fn fatp_watcher_invalid_fails_on_submission() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 150); + api.add_invalid(&xt0); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())); + let xt0_watcher = xt0_watcher.map(|_| ()); + + assert_pool_status!(header01.hash(), &pool, 0, 0); + assert!(matches!( + xt0_watcher.unwrap_err().into_pool_error(), + Ok(TxPoolError::InvalidTransaction(InvalidTransaction::Stale)) + )); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, Some(api.genesis_hash()), header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + api.add_invalid(&xt0); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + let event = finalized_block_event(&pool, header01.hash(), header02.hash()); + block_on(pool.maintain(event)); + + // wait 10 blocks for revalidation + let mut prev_header = header02; + for n in 3..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_eq!(pool.mempool_len(), (0, 1)); + api.add_invalid(&xt0); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); + assert_eq!(pool.mempool_len(), (0, 0)); +} + +#[test] +fn fatp_watcher_invalid_single_revalidation3() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 150); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_eq!(pool.mempool_len(), (0, 1)); + + let header01 = api.push_block(1, vec![], true); + let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash()); + block_on(pool.maintain(event)); + + // wait 10 blocks for revalidation + let mut prev_header = header01; + for n in 2..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + log::debug!("xt0_events: {:#?}", xt0_events); + assert_eq!(xt0_events, vec![TransactionStatus::Invalid]); + assert_eq!(pool.mempool_len(), (0, 0)); +} + +#[test] +fn fatp_watcher_future() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 202); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 0, 1); + + let header02 = api.push_block(2, vec![], true); + let event = ChainEvent::Finalized { + hash: header02.hash(), + tree_route: Arc::from(vec![header01.hash()]), + }; + block_on(pool.maintain(event)); + + assert_pool_status!(header02.hash(), &pool, 0, 1); + + let xt0_events = block_on(xt0_watcher.take(1).collect::>()); + assert_eq!(xt0_events, vec![TransactionStatus::Future]); +} + +#[test] +fn fatp_watcher_ready() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02 = api.push_block(2, vec![], true); + let event = ChainEvent::Finalized { + hash: header02.hash(), + tree_route: Arc::from(vec![header01.hash()]), + }; + block_on(pool.maintain(event)); + + assert_pool_status!(header02.hash(), &pool, 1, 0); + + let xt0_events = block_on(xt0_watcher.take(1).collect::>()); + assert_eq!(xt0_events, vec![TransactionStatus::Ready]); +} + +#[test] +fn fatp_watcher_finalized() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02 = api.push_block(2, vec![xt0], true); + let event = ChainEvent::Finalized { + hash: header02.hash(), + tree_route: Arc::from(vec![header01.hash()]), + }; + block_on(pool.maintain(event)); + + assert_pool_status!(header02.hash(), &pool, 0, 0); + + let xt0_events = block_on(xt0_watcher.collect::>()); + assert_eq!( + xt0_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_in_block() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02 = api.push_block(2, vec![xt0], true); + + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + let xt0_events = block_on(xt0_watcher.take(2).collect::>()); + assert_eq!( + xt0_events, + vec![TransactionStatus::Ready, TransactionStatus::InBlock((header02.hash(), 0)),] + ); +} + +#[test] +fn fatp_watcher_future_and_finalized() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + ]; + + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 1, 1); + + let header02 = api.push_block(2, vec![xt0], true); + let event = ChainEvent::Finalized { + hash: header02.hash(), + tree_route: Arc::from(vec![header01.hash()]), + }; + // let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header02.hash(), &pool, 0, 1); + + let xt1_status = block_on(xt1_watcher.take(1).collect::>()); + assert_eq!(xt1_status, vec![TransactionStatus::Future]); + let xt0_status = block_on(xt0_watcher.collect::>()); + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_two_finalized_in_different_block() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Dave.into(), 200); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Bob, 200); + let xt3 = uxt(Dave, 200); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 3, 0); + + let header02 = api.push_block(2, vec![xt3.clone(), xt2.clone(), xt0.clone()], true); + api.set_nonce(header02.hash(), Alice.into(), 201); + //note: no maintain for block02 (!) + + let header03 = api.push_block(3, vec![xt1.clone()], true); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header03.hash()))); + + assert_pool_status!(header03.hash(), &pool, 0, 0); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + + assert_eq!( + xt1_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)) + ] + ); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 2)), + TransactionStatus::Finalized((header02.hash(), 2)) + ] + ); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).collect::>(); + log::debug!("xt2_status: {:#?}", xt2_status); + + assert_eq!( + xt2_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 1)), + TransactionStatus::Finalized((header02.hash(), 1)) + ] + ); +} + +#[test] +fn fatp_no_view_pool_watcher_two_finalized_in_different_block() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Dave.into(), 200); + + let header01 = api.push_block(1, vec![], true); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Bob, 200); + let xt3 = uxt(Dave, 200); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + let header02 = api.push_block(2, vec![xt3.clone(), xt2.clone(), xt0.clone()], true); + api.set_nonce(header02.hash(), Alice.into(), 201); + api.set_nonce(header02.hash(), Bob.into(), 201); + api.set_nonce(header02.hash(), Dave.into(), 201); + //note: no maintain for block02 (!) + + let header03 = api.push_block(3, vec![xt1.clone()], true); + api.set_nonce(header03.hash(), Alice.into(), 202); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header03.hash()))); + + assert_pool_status!(header03.hash(), &pool, 0, 0); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + + log::debug!("xt1_status: {:#?}", xt1_status); + + assert_eq!( + xt1_status, + vec![ + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)) + ] + ); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::InBlock((header02.hash(), 2)), + TransactionStatus::Finalized((header02.hash(), 2)) + ] + ); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).collect::>(); + log::debug!("xt2_status: {:#?}", xt2_status); + + assert_eq!( + xt2_status, + vec![ + TransactionStatus::InBlock((header02.hash(), 1)), + TransactionStatus::Finalized((header02.hash(), 1)) + ] + ); +} + +#[test] +fn fatp_watcher_in_block_across_many_blocks() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 2, 0); + + let header02 = api.push_block(2, vec![], true); + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + //note 1: transaction is not submitted to views that are not at the tip of the fork + assert_eq!(pool.active_views_count(), 1); + assert_eq!(pool.inactive_views_count(), 1); + assert_pool_status!(header02.hash(), &pool, 3, 0); + + let header03 = api.push_block(3, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(header02.hash()), header03.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::InBlock((header03.hash(), 0)),] + ); +} + +#[test] +fn fatp_watcher_in_block_across_many_blocks2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 2, 0); + + let header02 = api.push_block(2, vec![], true); + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + //note 1: transaction is not submitted to views that are not at the tip of the fork + assert_eq!(pool.active_views_count(), 1); + assert_eq!(pool.inactive_views_count(), 1); + assert_pool_status!(header02.hash(), &pool, 3, 0); + + let header03 = api.push_block(3, vec![xt0.clone()], true); + let header04 = api.push_block(4, vec![xt1.clone()], true); + let event = new_best_block_event(&pool, Some(header02.hash()), header04.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header04.hash(), &pool, 1, 0); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + log::debug!("xt1_status: {:#?}", xt1_status); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::InBlock((header03.hash(), 0)),] + ); + assert_eq!( + xt1_status, + vec![TransactionStatus::Ready, TransactionStatus::InBlock((header04.hash(), 0)),] + ); +} + +#[test] +fn fatp_watcher_dropping_listener_should_work() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + + // intentionally drop the listener - nothing should panic. + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02 = api.push_block(2, vec![], true); + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); +} + +#[test] +fn fatp_watcher_fork_retract_and_finalize() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02a = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(header01.hash()), header02a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02a.hash(), &pool, 0, 0); + + let header02b = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + let event = ChainEvent::Finalized { + hash: header02b.hash(), + tree_route: Arc::from(vec![header01.hash()]), + }; + block_on(pool.maintain(event)); + assert_pool_status!(header02b.hash(), &pool, 0, 0); + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02a.hash(), 0)), + TransactionStatus::InBlock((header02b.hash(), 0)), + TransactionStatus::Finalized((header02b.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_retract_all_forks() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let header02a = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header02a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02a.hash(), &pool, 0, 0); + + let header02b = api.push_block_with_parent(genesis, vec![xt1.clone()], true); + let event = new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02b.hash(), &pool, 1, 0); + + let header02c = api.push_block_with_parent(genesis, vec![], true); + let event = + ChainEvent::Finalized { hash: header02c.hash(), tree_route: Arc::from(vec![genesis]) }; + block_on(pool.maintain(event)); + assert_pool_status!(header02c.hash(), &pool, 2, 0); +} + +#[test] +fn fatp_watcher_finalizing_forks() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + api.set_nonce(api.genesis_hash(), Dave.into(), 200); + api.set_nonce(api.genesis_hash(), Eve.into(), 200); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + let xt2 = uxt(Charlie, 200); + let xt3 = uxt(Dave, 200); + let xt4 = uxt(Eve, 200); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let header01 = api.push_block(1, vec![xt0.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash()))); + + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let header02a = api.push_block_with_parent(header01.hash(), vec![xt1.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let header03a = api.push_block_with_parent(header02a.hash(), vec![xt2.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header03a.hash()))); + + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + let header02b = api.push_block_with_parent(header01.hash(), vec![xt3.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02b.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02b.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let header03b = api.push_block_with_parent(header02b.hash(), vec![xt4.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03b.hash()))); + + let header04b = + api.push_block_with_parent(header03b.hash(), vec![xt1.clone(), xt2.clone()], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03b.hash()), header04b.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header02b.hash(), header04b.hash()))); + + //======================= + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + let xt2_status = futures::executor::block_on_stream(xt2_watcher).collect::>(); + let xt3_status = futures::executor::block_on_stream(xt3_watcher).collect::>(); + let xt4_status = futures::executor::block_on_stream(xt4_watcher).collect::>(); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::InBlock((header01.hash(), 0)), + TransactionStatus::Finalized((header01.hash(), 0)), + ] + ); + + assert_eq!( + xt1_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02a.hash(), 0)), + TransactionStatus::InBlock((header04b.hash(), 0)), + TransactionStatus::Finalized((header04b.hash(), 0)), + ] + ); + assert_eq!( + xt2_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03a.hash(), 0)), + TransactionStatus::InBlock((header04b.hash(), 1)), + TransactionStatus::Finalized((header04b.hash(), 1)), + ] + ); + assert_eq!( + xt3_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02b.hash(), 0)), + TransactionStatus::Finalized((header02b.hash(), 0)), + ] + ); + assert_eq!( + xt4_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03b.hash(), 0)), + TransactionStatus::Finalized((header03b.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_best_block_after_finalized() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + let header01 = api.push_block(1, vec![], true); + let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + // todo: shall we submit to finalized views? (if it is at the tip of the fork then yes?) + // assert_pool_status!(header01.hash(), &pool, 1, 0); + + let header02 = api.push_block(2, vec![xt0.clone()], true); + + let event = finalized_block_event(&pool, header01.hash(), header02.hash()); + block_on(pool.maintain(event)); + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + + let xt0_events = block_on(xt0_watcher.collect::>()); + assert_eq!( + xt0_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_best_block_after_finalized2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + let header01 = api.push_block(1, vec![xt0.clone()], true); + + let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash()); + block_on(pool.maintain(event)); + let event = new_best_block_event(&pool, Some(api.genesis_hash()), header01.hash()); + block_on(pool.maintain(event)); + + let xt0_events = block_on(xt0_watcher.collect::>()); + assert_eq!( + xt0_events, + vec![ + TransactionStatus::InBlock((header01.hash(), 0)), + TransactionStatus::Finalized((header01.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_switching_fork_multiple_times_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let header01a = api.push_block(1, vec![xt0.clone()], true); + + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let header01b = api.push_block(1, vec![xt0.clone(), xt1.clone()], true); + + //note: finalized block here must be header01b. + //It is because of how the order in which MultiViewListener is processing tx events and view + //events. tx events from single view are processed first, then view commands are handled. If + //finalization happens in first view reported then no events from others views will be + //processed. + + block_on(pool.maintain(new_best_block_event(&pool, None, header01a.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01a.hash()), header01b.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01b.hash()), header01a.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01b.hash()))); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + log::debug!("xt1_status: {:#?}", xt1_status); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::InBlock((header01a.hash(), 0)), + TransactionStatus::InBlock((header01b.hash(), 0)), + TransactionStatus::Finalized((header01b.hash(), 0)), + ] + ); + + assert_eq!( + xt1_status, + vec![TransactionStatus::Ready, TransactionStatus::InBlock((header01b.hash(), 1)),] + ); +} + +#[test] +fn fatp_watcher_two_blocks_delayed_finalization_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + let xt2 = uxt(Charlie, 200); + + let header01 = api.push_block(1, vec![], true); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let header03 = api.push_block_with_parent(header02.hash(), vec![xt1.clone()], true); + + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let header04 = api.push_block_with_parent(header03.hash(), vec![xt2.clone()], true); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, None, header04.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header03.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header03.hash(), header04.hash()))); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + let xt2_status = futures::executor::block_on_stream(xt2_watcher).collect::>(); + + //todo: double events. + //view for header04 reported InBlock for all xts. + //Then finalization comes for header03. We need to create a view to sent finalization events. + //But in_block are also sent because of pruning - normal process during view creation. + // + //Do not know what solution should be in this case? + // - just jeep two events, + // - block pruning somehow (seems like excessive additional logic not really needed) + // - build view from recent best block? (retracting instead of enacting?) + // - de-dup events in listener (implemented) + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); + assert_eq!( + xt1_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)), + ] + ); + assert_eq!( + xt2_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header04.hash(), 0)), + TransactionStatus::Finalized((header04.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_delayed_finalization_does_not_retract() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let header01 = api.push_block(1, vec![], true); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let header03 = api.push_block_with_parent(header02.hash(), vec![xt1.clone()], true); + + block_on(pool.maintain(new_best_block_event(&pool, None, header02.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); + assert_eq!( + xt1_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_best_block_after_finalization_does_not_retract() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let header01 = api.push_block(1, vec![], true); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let header03 = api.push_block_with_parent(header02.hash(), vec![xt1.clone()], true); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash()))); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header03.hash()))); + block_on(pool.maintain(new_best_block_event(&pool, Some(api.genesis_hash()), header02.hash()))); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + log::debug!("xt1_status: {:#?}", xt1_status); + + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)), + ] + ); + assert_eq!( + xt1_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)), + ] + ); +} + +#[test] +fn fatp_watcher_invalid_many_revalidation() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Alice, 203); + let xt4 = uxt(Alice, 204); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone()), + ]; + + let submissions = block_on(futures::future::join_all(submissions)); + assert_eq!(pool.status_all()[&header01.hash()].ready, 5); + + let mut watchers = submissions.into_iter().map(Result::unwrap).collect::>(); + let xt4_watcher = watchers.remove(4); + let xt3_watcher = watchers.remove(3); + let xt2_watcher = watchers.remove(2); + let xt1_watcher = watchers.remove(1); + let xt0_watcher = watchers.remove(0); + + api.add_invalid(&xt3); + api.add_invalid(&xt4); + + let header02 = api.push_block(2, vec![], true); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&header02.hash()].ready, 5); + + let header03 = api.push_block(3, vec![xt0.clone(), xt1.clone(), xt2.clone()], true); + block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header03.clone(); + for n in 4..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::>(); + let xt1_events = futures::executor::block_on_stream(xt1_watcher).collect::>(); + let xt2_events = futures::executor::block_on_stream(xt2_watcher).collect::>(); + let xt3_events = futures::executor::block_on_stream(xt3_watcher).collect::>(); + let xt4_events = futures::executor::block_on_stream(xt4_watcher).collect::>(); + + log::debug!("xt0_events: {:#?}", xt0_events); + log::debug!("xt1_events: {:#?}", xt1_events); + log::debug!("xt2_events: {:#?}", xt2_events); + log::debug!("xt3_events: {:#?}", xt3_events); + log::debug!("xt4_events: {:#?}", xt4_events); + + assert_eq!( + xt0_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 0)), + TransactionStatus::Finalized((header03.hash(), 0)) + ], + ); + assert_eq!( + xt1_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 1)), + TransactionStatus::Finalized((header03.hash(), 1)) + ], + ); + assert_eq!( + xt2_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header03.hash(), 2)), + TransactionStatus::Finalized((header03.hash(), 2)) + ], + ); + assert_eq!(xt3_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); + assert_eq!(xt4_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid],); +} + +#[test] +fn should_not_retain_invalid_hashes_from_retracted() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + let xt = uxt(Alice, 200); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt.clone())).unwrap(); + + let header02a = api.push_block_with_parent(header01.hash(), vec![xt.clone()], true); + + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash()))); + assert_eq!(pool.status_all()[&header02a.hash()].ready, 0); + + api.add_invalid(&xt); + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02b.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header02b.clone(); + for _ in 3..=11 { + let header = api.push_block_with_parent(prev_header.hash(), vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02a.hash(), 0)), + TransactionStatus::Invalid + ], + ); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0); +} + +#[test] +fn should_revalidate_during_maintenance() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + assert_eq!(pool.status_all()[&header01.hash()].ready, 2); + assert_eq!(api.validation_requests().len(), 2); + + let header02 = api.push_block(2, vec![xt1.clone()], true); + api.add_invalid(&xt2); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + + //todo: shall revalidation check finalized (fork's tip) view? + assert_eq!(pool.status_all()[&header02.hash()].ready, 1); + + // wait 10 blocks for revalidation + let mut prev_header = header02.clone(); + for _ in 3..=11 { + let header = api.push_block_with_parent(prev_header.hash(), vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + assert_eq!( + futures::executor::block_on_stream(watcher).collect::>(), + vec![TransactionStatus::Ready, TransactionStatus::Invalid], + ); +} + +#[test] +fn fatp_transactions_purging_stale_on_finalization_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + let xt3 = uxt(Alice, 202); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_eq!(api.validation_requests().len(), 3); + assert_eq!(pool.status_all()[&header01.hash()].ready, 3); + assert_eq!(pool.mempool_len(), (1, 2)); + + let header02 = api.push_block(2, vec![xt1.clone(), xt2.clone(), xt3.clone()], true); + api.set_nonce(header02.hash(), Alice.into(), 203); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + + assert_eq!(pool.status_all()[&header02.hash()].ready, 0); + assert_eq!(pool.mempool_len(), (0, 0)); + + let xt1_events = futures::executor::block_on_stream(watcher1).collect::>(); + let xt2_events = futures::executor::block_on_stream(watcher2).collect::>(); + assert_eq!( + xt1_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)) + ], + ); + assert_eq!( + xt2_events, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 1)), + TransactionStatus::Finalized((header02.hash(), 1)) + ], + ); +} + +#[test] +fn fatp_transactions_purging_invalid_on_finalization_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + let xt3 = uxt(Alice, 202); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_eq!(api.validation_requests().len(), 3); + assert_eq!(pool.status_all()[&header01.hash()].ready, 3); + assert_eq!(pool.mempool_len(), (1, 2)); + + let header02 = api.push_block(2, vec![], true); + api.add_invalid(&xt1); + api.add_invalid(&xt2); + api.add_invalid(&xt3); + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + + // wait 10 blocks for revalidation + let mut prev_header = header02; + for n in 3..=13 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + //todo: should it work at all? (it requires better revalidation: mempool keeping validated txs) + //additionally it also requires revalidation of finalized view. + // assert_eq!(pool.status_all()[&header02.hash()].ready, 0); + assert_eq!(pool.mempool_len(), (0, 0)); + + let xt1_events = futures::executor::block_on_stream(watcher1).collect::>(); + let xt2_events = futures::executor::block_on_stream(watcher2).collect::>(); + assert_eq!(xt1_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); + assert_eq!(xt2_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]); +} + +#[test] +fn import_sink_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let genesis = api.genesis_hash(); + let header01a = api.push_block(1, vec![], true); + let header01b = api.push_block(1, vec![], true); + + let import_stream = pool.import_notification_stream(); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + api.set_nonce(header01b.hash(), Alice.into(), 202); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(genesis, SOURCE, xt0.clone()), + pool.submit_one(genesis, SOURCE, xt1.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + assert_pool_status!(header01a.hash(), &pool, 1, 1); + assert_pool_status!(header01b.hash(), &pool, 1, 0); + + let import_events = + futures::executor::block_on_stream(import_stream).take(2).collect::>(); + + let expected_import_events = vec![api.hash_and_length(&xt0).0, api.hash_and_length(&xt1).0]; + assert!(import_events.iter().all(|v| expected_import_events.contains(v))); +} + +#[test] +fn import_sink_works2() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let genesis = api.genesis_hash(); + let header01a = api.push_block(1, vec![], true); + let header01b = api.push_block(1, vec![], true); + + let import_stream = pool.import_notification_stream(); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(genesis, SOURCE, xt0.clone()), + pool.submit_one(genesis, SOURCE, xt1.clone()), + ]; + + block_on(futures::future::join_all(submissions)); + + assert_pool_status!(header01a.hash(), &pool, 1, 1); + assert_pool_status!(header01b.hash(), &pool, 1, 1); + + let import_events = + futures::executor::block_on_stream(import_stream).take(1).collect::>(); + + let expected_import_events = vec![api.hash_and_length(&xt0).0]; + assert_eq!(import_events, expected_import_events); +} + +#[test] +fn import_sink_works3() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let import_stream = pool.import_notification_stream(); + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(genesis, SOURCE, xt0.clone()), + pool.submit_one(genesis, SOURCE, xt1.clone()), + ]; + + let x = block_on(futures::future::join_all(submissions)); + + let header01a = api.push_block(1, vec![], true); + let header01b = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01a.hash()); + block_on(pool.maintain(event)); + + let event = new_best_block_event(&pool, None, header01b.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header01a.hash(), &pool, 1, 1); + assert_pool_status!(header01b.hash(), &pool, 1, 1); + + log::debug!("xxx {x:#?}"); + + let import_events = + futures::executor::block_on_stream(import_stream).take(1).collect::>(); + + let expected_import_events = vec![api.hash_and_length(&xt0).0]; + assert_eq!(import_events, expected_import_events); +} + +#[test] +fn fatp_avoid_stuck_transaction() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Alice, 203); + let xt4 = uxt(Alice, 204); + let xt4i = uxt(Alice, 204); + let xt4i_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4i.clone())).unwrap(); + + assert_eq!(pool.mempool_len(), (0, 1)); + + let header01 = api.push_block(1, vec![xt0], true); + api.set_nonce(header01.hash(), Alice.into(), 201); + let header02 = api.push_block(2, vec![xt1], true); + api.set_nonce(header02.hash(), Alice.into(), 202); + let header03 = api.push_block(3, vec![xt2], true); + api.set_nonce(header03.hash(), Alice.into(), 203); + + let header04 = api.push_block(4, vec![], true); + api.set_nonce(header04.hash(), Alice.into(), 203); + + let header05 = api.push_block(5, vec![], true); + api.set_nonce(header05.hash(), Alice.into(), 203); + + let event = new_best_block_event(&pool, None, header05.hash()); + block_on(pool.maintain(event)); + + let event = finalized_block_event(&pool, api.genesis_hash(), header03.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header05.hash(), &pool, 0, 1); + + let header06 = api.push_block(6, vec![xt3, xt4], true); + api.set_nonce(header06.hash(), Alice.into(), 205); + let event = new_best_block_event(&pool, None, header06.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header06.hash(), &pool, 0, 0); + + // Import enough blocks to make xt4i revalidated + let mut prev_header = header03; + // wait 10 blocks for revalidation + for n in 7..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + let xt4i_events = futures::executor::block_on_stream(xt4i_watcher).collect::>(); + log::debug!("xt4i_events: {:#?}", xt4i_events); + assert_eq!(xt4i_events, vec![TransactionStatus::Future, TransactionStatus::Invalid]); + assert_eq!(pool.mempool_len(), (0, 0)); +} + +#[test] +fn fatp_future_is_pruned_by_conflicting_tags() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt2i = uxt(Alice, 202); + log::debug!("xt0: {:#?}", api.hash_and_length(&xt0).0); + log::debug!("xt1: {:#?}", api.hash_and_length(&xt1).0); + log::debug!("xt2: {:#?}", api.hash_and_length(&xt2).0); + log::debug!("xt2i: {:#?}", api.hash_and_length(&xt2i).0); + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2i.clone())).unwrap(); + + assert_eq!(pool.mempool_len(), (0, 1)); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01.hash(), &pool, 0, 1); + + let header02 = api.push_block(2, vec![xt0, xt1, xt2], true); + api.set_nonce(header02.hash(), Alice.into(), 203); + + let event = new_best_block_event(&pool, None, header02.hash()); + block_on(pool.maintain(event)); + + assert_pool_status!(header02.hash(), &pool, 0, 0); +} + +#[test] +fn fatp_dangling_ready_gets_revalidated() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt2 = uxt(Alice, 202); + log::debug!("xt2: {:#?}", api.hash_and_length(&xt2).0); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01.hash(), &pool, 0, 0); + + let header02a = api.push_block_with_parent(header01.hash(), vec![], true); + api.set_nonce(header02a.hash(), Alice.into(), 202); + let event = new_best_block_event(&pool, Some(header01.hash()), header02a.hash()); + block_on(pool.maintain(event)); + + // send xt2 - it will become ready on block 02a. + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + assert_pool_status!(header02a.hash(), &pool, 1, 0); + assert_eq!(pool.mempool_len(), (0, 1)); + + //xt2 is still ready: view was just cloned (revalidation executed in background) + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + let event = new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02b.hash(), &pool, 1, 0); + + //xt2 is now future - view revalidation worked. + let header03b = api.push_block_with_parent(header02b.hash(), vec![], true); + let event = new_best_block_event(&pool, Some(header02b.hash()), header03b.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header03b.hash(), &pool, 0, 1); +} + +#[test] +fn fatp_ready_txs_are_provided_in_valid_order() { + // this test checks if recently_pruned tags are cleared for views cloned from retracted path + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + log::debug!("xt0: {:#?}", api.hash_and_length(&xt0).0); + log::debug!("xt1: {:#?}", api.hash_and_length(&xt1).0); + log::debug!("xt2: {:#?}", api.hash_and_length(&xt2).0); + + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let _ = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + + let header01 = api.push_block(1, vec![xt0], true); + api.set_nonce(header01.hash(), Alice.into(), 201); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01.hash(), &pool, 2, 0); + + let header02a = + api.push_block_with_parent(header01.hash(), vec![xt1.clone(), xt2.clone()], true); + api.set_nonce(header02a.hash(), Alice.into(), 203); + let event = new_best_block_event(&pool, Some(header01.hash()), header02a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02a.hash(), &pool, 0, 0); + + let header02b = api.push_block_with_parent(header01.hash(), vec![], true); + api.set_nonce(header02b.hash(), Alice.into(), 201); + let event = new_best_block_event(&pool, Some(header02a.hash()), header02b.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header02b.hash(), &pool, 2, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt1, xt2]); +} + +//todo: add test: check len of filter after finalization (!) +//todo: broadcasted test? + +#[test] +fn fatp_ready_light_empty_on_unmaintained_fork() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let header01a = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header01a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01a.hash(), &pool, 0, 0); + + let header01b = api.push_block_with_parent(genesis, vec![xt1.clone()], true); + + let mut ready_iterator = pool.ready_at_light(header01b.hash()).now_or_never().unwrap(); + assert!(ready_iterator.next().is_none()); +} + +#[test] +fn fatp_ready_light_misc_scenarios_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + let xt2 = uxt(Charlie, 200); + + //fork A + let header01a = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header01a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01a.hash(), &pool, 0, 0); + + //fork B + let header01b = api.push_block_with_parent(genesis, vec![xt1.clone()], true); + let event = new_best_block_event(&pool, Some(header01a.hash()), header01b.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01b.hash(), &pool, 1, 0); + + //new block at fork B + let header02b = api.push_block_with_parent(header01b.hash(), vec![xt1.clone()], true); + + // test 1: + //ready light returns just txs from view @header01b (which contains retracted xt0) + let mut ready_iterator = pool.ready_at_light(header02b.hash()).now_or_never().unwrap(); + let ready01 = ready_iterator.next(); + assert_eq!(ready01.unwrap().hash, api.hash_and_length(&xt0).0); + assert!(ready_iterator.next().is_none()); + + // test 2: + // submit new transaction to all views + block_on(pool.submit_one(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + + //new block at fork A, not yet notified to pool + let header02a = api.push_block_with_parent(header01a.hash(), vec![], true); + + //ready light returns just txs from view @header01a (which contains newly submitted xt2) + let mut ready_iterator = pool.ready_at_light(header02a.hash()).now_or_never().unwrap(); + let ready01 = ready_iterator.next(); + assert_eq!(ready01.unwrap().hash, api.hash_and_length(&xt2).0); + assert!(ready_iterator.next().is_none()); + + //test 3: + let mut ready_iterator = pool.ready_at_light(header02b.hash()).now_or_never().unwrap(); + let ready01 = ready_iterator.next(); + assert_eq!(ready01.unwrap().hash, api.hash_and_length(&xt0).0); + let ready02 = ready_iterator.next(); + assert_eq!(ready02.unwrap().hash, api.hash_and_length(&xt2).0); + assert!(ready_iterator.next().is_none()); + + //test 4: + //new block at fork B, not yet notified to pool + let header03b = + api.push_block_with_parent(header02b.hash(), vec![xt0.clone(), xt2.clone()], true); + //ready light @header03b will be empty: as new block contains xt0/xt2 + let mut ready_iterator = pool.ready_at_light(header03b.hash()).now_or_never().unwrap(); + assert!(ready_iterator.next().is_none()); +} + +#[test] +fn fatp_ready_light_long_fork_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + api.set_nonce(api.genesis_hash(), Dave.into(), 200); + api.set_nonce(api.genesis_hash(), Eve.into(), 200); + + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + let xt2 = uxt(Charlie, 200); + let xt3 = uxt(Dave, 200); + let xt4 = uxt(Eve, 200); + + let submissions = vec![pool.submit_at( + genesis, + SOURCE, + vec![xt0.clone(), xt1.clone(), xt2.clone(), xt3.clone(), xt4.clone()], + )]; + let results = block_on(futures::future::join_all(submissions)); + assert!(results.iter().all(Result::is_ok)); + + let header01 = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header01.hash()); + block_on(pool.maintain(event)); + + let header02 = api.push_block_with_parent(header01.hash(), vec![xt1.clone()], true); + let header03 = api.push_block_with_parent(header02.hash(), vec![xt2.clone()], true); + let header04 = api.push_block_with_parent(header03.hash(), vec![xt3.clone()], true); + + let mut ready_iterator = pool.ready_at_light(header04.hash()).now_or_never().unwrap(); + let ready01 = ready_iterator.next(); + assert_eq!(ready01.unwrap().hash, api.hash_and_length(&xt4).0); + assert!(ready_iterator.next().is_none()); +} + +#[test] +fn fatp_ready_light_long_fork_retracted_works() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + api.set_nonce(api.genesis_hash(), Dave.into(), 200); + api.set_nonce(api.genesis_hash(), Eve.into(), 200); + + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + let xt2 = uxt(Charlie, 200); + let xt3 = uxt(Dave, 200); + let xt4 = uxt(Eve, 200); + + let submissions = vec![pool.submit_at( + genesis, + SOURCE, + vec![xt0.clone(), xt1.clone(), xt2.clone(), xt3.clone()], + )]; + let results = block_on(futures::future::join_all(submissions)); + assert!(results.iter().all(|r| { r.is_ok() })); + + let header01a = api.push_block_with_parent(genesis, vec![xt4.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header01a.hash()); + block_on(pool.maintain(event)); + + let header01b = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let header02b = api.push_block_with_parent(header01b.hash(), vec![xt1.clone()], true); + let header03b = api.push_block_with_parent(header02b.hash(), vec![xt2.clone()], true); + + let mut ready_iterator = pool.ready_at_light(header03b.hash()).now_or_never().unwrap(); + assert!(ready_iterator.next().is_none()); + + let event = new_best_block_event(&pool, Some(header01a.hash()), header01b.hash()); + block_on(pool.maintain(event)); + + let mut ready_iterator = pool.ready_at_light(header03b.hash()).now_or_never().unwrap(); + let ready01 = ready_iterator.next(); + assert_eq!(ready01.unwrap().hash, api.hash_and_length(&xt3).0); + let ready02 = ready_iterator.next(); + assert_eq!(ready02.unwrap().hash, api.hash_and_length(&xt4).0); + assert!(ready_iterator.next().is_none()); +} + +#[test] +fn fatp_ready_at_with_timeout_works_for_misc_scenarios() { + sp_tracing::try_init_simple(); + + let (pool, api, _) = pool(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 200); + let genesis = api.genesis_hash(); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 200); + + let header01a = api.push_block_with_parent(genesis, vec![xt0.clone()], true); + let event = new_best_block_event(&pool, Some(genesis), header01a.hash()); + block_on(pool.maintain(event)); + assert_pool_status!(header01a.hash(), &pool, 0, 0); + + let header01b = api.push_block_with_parent(genesis, vec![xt1.clone()], true); + + let mut ready_at_future = + pool.ready_at_with_timeout(header01b.hash(), Duration::from_secs(36000)); + + let noop_waker = futures::task::noop_waker(); + let mut context = futures::task::Context::from_waker(&noop_waker); + + if ready_at_future.poll_unpin(&mut context).is_ready() { + panic!("Ready set should not be ready before maintenance on block update!"); + } + + let event = new_best_block_event(&pool, Some(header01a.hash()), header01b.hash()); + block_on(pool.maintain(event)); + + // ready should now be triggered: + let mut ready_at = ready_at_future.now_or_never().unwrap(); + assert_eq!(ready_at.next().unwrap().hash, api.hash_and_length(&xt0).0); + assert!(ready_at.next().is_none()); + + let header02a = api.push_block_with_parent(header01a.hash(), vec![], true); + let xt2 = uxt(Charlie, 200); + block_on(pool.submit_one(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + + // ready light should now be triggered: + let mut ready_at2 = block_on(pool.ready_at_with_timeout(header02a.hash(), Duration::ZERO)); + assert_eq!(ready_at2.next().unwrap().hash, api.hash_and_length(&xt2).0); + assert!(ready_at2.next().is_none()); +} diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs new file mode 100644 index 00000000000..63af729b8b7 --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -0,0 +1,285 @@ +// 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 . + +//! Tests for fork-aware transaction pool. + +use sc_transaction_pool::{ChainApi, PoolLimit}; +use sc_transaction_pool_api::ChainEvent; +use sp_runtime::transaction_validity::TransactionSource; +use std::sync::Arc; +use substrate_test_runtime_client::{ + runtime::{Block, Hash, Header}, + AccountKeyring::*, +}; +use substrate_test_runtime_transaction_pool::{uxt, TestApi}; +pub const LOG_TARGET: &str = "txpool"; + +use sc_transaction_pool::ForkAwareTxPool; + +pub fn invalid_hash() -> Hash { + Default::default() +} + +pub fn new_best_block_event( + pool: &ForkAwareTxPool, + from: Option, + to: Hash, +) -> ChainEvent { + ChainEvent::NewBestBlock { + hash: to, + tree_route: from.map(|from| { + // note: real tree route in NewBestBlock event does not contain 'to' block. + Arc::from( + pool.api() + .tree_route(from, pool.api().block_header(to).unwrap().unwrap().parent_hash) + .expect("Tree route exists"), + ) + }), + } +} + +pub fn finalized_block_event( + pool: &ForkAwareTxPool, + from: Hash, + to: Hash, +) -> ChainEvent { + let t = pool.api().tree_route(from, to).expect("Tree route exists"); + + let e = t.enacted().iter().map(|h| h.hash).collect::>(); + ChainEvent::Finalized { hash: to, tree_route: Arc::from(&e[0..e.len() - 1]) } +} + +pub struct TestPoolBuilder { + api: Option>, + use_default_limits: bool, + ready_limits: sc_transaction_pool::PoolLimit, + future_limits: sc_transaction_pool::PoolLimit, + mempool_max_transactions_count: usize, +} + +impl Default for TestPoolBuilder { + fn default() -> Self { + Self { + api: None, + use_default_limits: true, + ready_limits: PoolLimit { count: 8192, total_bytes: 20 * 1024 * 1024 }, + future_limits: PoolLimit { count: 512, total_bytes: 1 * 1024 * 1024 }, + mempool_max_transactions_count: usize::MAX, + } + } +} + +impl TestPoolBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn with_api(mut self, api: Arc) -> Self { + self.api = Some(api); + self + } + + pub fn with_mempool_count_limit(mut self, mempool_count_limit: usize) -> Self { + self.mempool_max_transactions_count = mempool_count_limit; + self.use_default_limits = false; + self + } + + pub fn with_ready_count(mut self, ready_count: usize) -> Self { + self.ready_limits.count = ready_count; + self.use_default_limits = false; + self + } + + pub fn with_ready_bytes_size(mut self, ready_bytes_size: usize) -> Self { + self.ready_limits.total_bytes = ready_bytes_size; + self.use_default_limits = false; + self + } + + pub fn with_future_count(mut self, future_count: usize) -> Self { + self.future_limits.count = future_count; + self.use_default_limits = false; + self + } + + pub fn with_future_bytes_size(mut self, future_bytes_size: usize) -> Self { + self.future_limits.total_bytes = future_bytes_size; + self.use_default_limits = false; + self + } + + pub fn build( + self, + ) -> (ForkAwareTxPool, Arc, futures::executor::ThreadPool) { + let api = self + .api + .unwrap_or(Arc::from(TestApi::with_alice_nonce(200).enable_stale_check())); + + let genesis_hash = api + .chain() + .read() + .block_by_number + .get(&0) + .map(|blocks| blocks[0].0.header.hash()) + .expect("there is block 0. qed"); + + let (pool, txpool_task) = if self.use_default_limits { + ForkAwareTxPool::new_test(api.clone(), genesis_hash, genesis_hash) + } else { + ForkAwareTxPool::new_test_with_limits( + api.clone(), + genesis_hash, + genesis_hash, + self.ready_limits, + self.future_limits, + self.mempool_max_transactions_count, + ) + }; + + let thread_pool = futures::executor::ThreadPool::new().unwrap(); + thread_pool.spawn_ok(txpool_task); + + (pool, api, thread_pool) + } +} + +pub fn pool_with_api( + test_api: Arc, +) -> (ForkAwareTxPool, futures::executor::ThreadPool) { + let builder = TestPoolBuilder::new(); + let (pool, _, threadpool) = builder.with_api(test_api).build(); + (pool, threadpool) +} + +pub fn pool() -> (ForkAwareTxPool, Arc, futures::executor::ThreadPool) { + let builder = TestPoolBuilder::new(); + builder.build() +} + +#[macro_export] +macro_rules! assert_pool_status { + ($hash:expr, $pool:expr, $ready:expr, $future:expr) => { + { + log::debug!(target:LOG_TARGET, "stats: {:#?}", $pool.status_all()); + let status = &$pool.status_all()[&$hash]; + assert_eq!(status.ready, $ready, "ready"); + assert_eq!(status.future, $future, "future"); + } + } +} + +#[macro_export] +macro_rules! assert_ready_iterator { + ($hash:expr, $pool:expr, [$( $xt:expr ),+]) => {{ + let ready_iterator = $pool.ready_at($hash).now_or_never().unwrap(); + let expected = vec![ $($pool.api().hash_and_length(&$xt).0),+]; + let output: Vec<_> = ready_iterator.collect(); + log::debug!(target:LOG_TARGET, "expected: {:#?}", expected); + log::debug!(target:LOG_TARGET, "output: {:#?}", output); + assert_eq!(expected.len(), output.len()); + assert!( + output.iter().zip(expected.iter()).all(|(o,e)| { + o.hash == *e + }) + ); + }}; +} + +pub const SOURCE: TransactionSource = TransactionSource::External; + +#[cfg(test)] +pub mod test_chain_with_forks { + use super::*; + + pub fn chain( + include_xts: Option<&dyn Fn(usize, usize) -> bool>, + ) -> (Arc, Vec>) { + // Fork layout: + // + // (fork 0) + // F01 - F02 - F03 - F04 - F05 | Alice nonce increasing, alice's txs + // / + // F00 + // \ (fork 1) + // F11 - F12 - F13 - F14 - F15 | Bob nonce increasing, Bob's txs + // + // + // e.g. F03 contains uxt(Alice, 202), nonces: Alice = 203, Bob = 200 + // F12 contains uxt(Bob, 201), nonces: Alice = 200, Bob = 202 + + let api = Arc::from(TestApi::empty().enable_stale_check()); + + let genesis = api.genesis_hash(); + + let mut forks = vec![Vec::with_capacity(6), Vec::with_capacity(6)]; + let accounts = vec![Alice, Bob]; + accounts.iter().for_each(|a| api.set_nonce(genesis, (*a).into(), 200)); + + for fork in 0..2 { + let account = accounts[fork]; + forks[fork].push(api.block_header(genesis).unwrap().unwrap()); + let mut parent = genesis; + for block in 1..6 { + let xts = if include_xts.map_or(true, |v| v(fork, block)) { + log::debug!("{},{} -> add", fork, block); + vec![uxt(account, (200 + block - 1) as u64)] + } else { + log::debug!("{},{} -> skip", fork, block); + vec![] + }; + let header = api.push_block_with_parent(parent, xts, true); + parent = header.hash(); + api.set_nonce(header.hash(), account.into(), (200 + block) as u64); + forks[fork].push(header); + } + } + + (api, forks) + } + + pub fn print_block(api: Arc, hash: Hash) { + let accounts = vec![Alice.into(), Bob.into()]; + let header = api.block_header(hash).unwrap().unwrap(); + + let nonces = accounts + .iter() + .map(|a| api.chain().read().nonces.get(&hash).unwrap().get(a).map(Clone::clone)) + .collect::>(); + log::debug!( + "number: {:?} hash: {:?}, parent: {:?}, nonces:{:?}", + header.number, + header.hash(), + header.parent_hash, + nonces + ); + } + + #[test] + fn test_chain_works() { + sp_tracing::try_init_simple(); + let (api, f) = chain(None); + log::debug!("forks: {f:#?}"); + f[0].iter().for_each(|h| print_block(api.clone(), h.hash())); + f[1].iter().for_each(|h| print_block(api.clone(), h.hash())); + let tr = api.tree_route(f[0][5].hash(), f[1][5].hash()).unwrap(); + log::debug!("{:#?}", tr); + log::debug!("e:{:#?}", tr.enacted()); + log::debug!("r:{:#?}", tr.retracted()); + } +} diff --git a/substrate/client/transaction-pool/tests/fatp_limits.rs b/substrate/client/transaction-pool/tests/fatp_limits.rs new file mode 100644 index 00000000000..6fd5f93ed07 --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp_limits.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 . + +//! Tests of limits for fork-aware transaction pool. + +pub mod fatp_common; +use fatp_common::{ + finalized_block_event, invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE, +}; +use futures::{executor::block_on, FutureExt}; +use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::{ + error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus, +}; +use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_transaction_pool::uxt; + +#[test] +fn fatp_limits_no_views_mempool_count() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(2).build(); + + let header = api.push_block(1, vec![], true); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(header.hash(), SOURCE, xt0.clone()), + pool.submit_one(header.hash(), SOURCE, xt1.clone()), + pool.submit_one(header.hash(), SOURCE, xt2.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + let mut results = results.iter(); + + assert!(results.next().unwrap().is_ok()); + assert!(results.next().unwrap().is_ok()); + assert!(matches!( + results.next().unwrap().as_ref().unwrap_err().0, + TxPoolError::ImmediatelyDropped + )); +} + +#[test] +fn fatp_limits_ready_count_works() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 500); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + //note: we need Charlie to be first as the oldest is removed. + //For 3x alice, all tree would be removed. + //(alice,bob,charlie would work too) + let xt0 = uxt(Charlie, 500); + let xt1 = uxt(Alice, 200); + let xt2 = uxt(Alice, 201); + + let submissions = vec![ + pool.submit_one(header01.hash(), SOURCE, xt0.clone()), + pool.submit_one(header01.hash(), SOURCE, xt1.clone()), + pool.submit_one(header01.hash(), SOURCE, xt2.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + assert!(results.iter().all(Result::is_ok)); + //charlie was not included into view: + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]); + + //branch with alice transactions: + let header02b = api.push_block(2, vec![xt1.clone(), xt2.clone()], true); + let event = new_best_block_event(&pool, Some(header01.hash()), header02b.hash()); + block_on(pool.maintain(event)); + assert_eq!(pool.mempool_len().0, 3); + //charlie was resubmitted from mmepool into the view: + assert_pool_status!(header02b.hash(), &pool, 1, 0); + assert_ready_iterator!(header02b.hash(), pool, [xt0]); + + //branch with alice/charlie transactions shall also work: + let header02a = api.push_block(2, vec![xt0.clone(), xt1.clone()], true); + let event = new_best_block_event(&pool, Some(header02b.hash()), header02a.hash()); + block_on(pool.maintain(event)); + assert_eq!(pool.mempool_len().0, 3); + assert_pool_status!(header02a.hash(), &pool, 1, 0); + assert_ready_iterator!(header02a.hash(), pool, [xt2]); +} + +#[test] +fn fatp_limits_future_count_works() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_future_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 200); + api.set_nonce(api.genesis_hash(), Charlie.into(), 500); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + + let xt1 = uxt(Charlie, 501); + let xt2 = uxt(Alice, 201); + let xt3 = uxt(Alice, 202); + + let submissions = vec![ + pool.submit_one(header01.hash(), SOURCE, xt1.clone()), + pool.submit_one(header01.hash(), SOURCE, xt2.clone()), + pool.submit_one(header01.hash(), SOURCE, xt3.clone()), + ]; + + let results = block_on(futures::future::join_all(submissions)); + assert!(results.iter().all(Result::is_ok)); + //charlie was not included into view due to limits: + assert_pool_status!(header01.hash(), &pool, 0, 2); + + let header02 = api.push_block(2, vec![xt0], true); + api.set_nonce(header02.hash(), Alice.into(), 201); //redundant + let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash()); + block_on(pool.maintain(event)); + + //charlie was resubmitted from mmepool into the view: + assert_pool_status!(header02.hash(), &pool, 2, 1); + assert_eq!(pool.mempool_len().0, 3); +} + +#[test] +fn fatp_limits_watcher_mempool_prevents_dropping() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Charlie, 400); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Alice, 200); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(1).collect::>(); + + log::debug!("xt0_status: {:#?}", xt0_status); + + assert_eq!(xt0_status, vec![TransactionStatus::Ready]); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::>(); + + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + log::debug!("xt2_status: {:#?}", xt2_status); + + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); +} + +#[test] +fn fatp_limits_watcher_non_intial_view_drops_transaction() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Dave, 500); + let xt1 = uxt(Charlie, 400); + let xt2 = uxt(Bob, 300); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt0]); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(1).collect::>(); + assert_eq!(xt0_status, vec![TransactionStatus::Ready]); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); +} + +#[test] +fn fatp_limits_watcher_finalized_transaction_frees_ready_space() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Dave, 500); + let xt1 = uxt(Charlie, 400); + let xt2 = uxt(Bob, 300); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt1, xt2]); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(3).collect::>(); + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)) + ] + ); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); +} + +#[test] +fn fatp_limits_watcher_view_can_drop_transcation() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Dave, 500); + let xt1 = uxt(Charlie, 400); + let xt2 = uxt(Bob, 300); + let xt3 = uxt(Alice, 200); + + let submissions = vec![ + pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()), + pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()), + ]; + let mut submissions = block_on(futures::future::join_all(submissions)); + let xt2_watcher = submissions.remove(2).unwrap(); + let xt1_watcher = submissions.remove(1).unwrap(); + let xt0_watcher = submissions.remove(0).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true); + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash()))); + + let submission = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())); + let xt3_watcher = submission.unwrap(); + + assert_pool_status!(header02.hash(), pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(3).collect::>(); + assert_eq!( + xt0_status, + vec![ + TransactionStatus::Ready, + TransactionStatus::InBlock((header02.hash(), 0)), + TransactionStatus::Finalized((header02.hash(), 0)) + ] + ); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); + + let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(1).collect::>(); + assert_eq!(xt3_status, vec![TransactionStatus::Ready]); +} diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index 6d70b6ce67e..ed0fd7d4e65 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -85,12 +85,13 @@ const SOURCE: TransactionSource = TransactionSource::External; #[test] fn submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + .unwrap(); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![209]); } @@ -98,13 +99,15 @@ fn submission_should_work() { #[test] fn multiple_submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 209))).unwrap(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 210))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + .unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + .unwrap(); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![209, 210]); } @@ -113,12 +116,14 @@ fn multiple_submission_should_work() { fn early_nonce_should_be_culled() { sp_tracing::try_init_simple(); let (pool, api) = pool(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 208))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 208).into())) + .unwrap(); + log::debug!("-> {:?}", pool.validated_pool().status()); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, Vec::::new()); } @@ -127,19 +132,21 @@ fn early_nonce_should_be_culled() { fn late_nonce_should_be_queued() { let (pool, api) = pool(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 210))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + .unwrap(); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, Vec::::new()); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 209))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + .unwrap(); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![209, 210]); } @@ -148,24 +155,25 @@ fn late_nonce_should_be_queued() { fn prune_tags_should_work() { let (pool, api) = pool(); let hash209 = - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 209))).unwrap(); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt(Alice, 210))).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + .unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + .unwrap(); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![209, 210]); pool.validated_pool().api().push_block(1, Vec::new(), true); - block_on(pool.prune_tags(api.expect_hash_from_number(1), vec![vec![209]], vec![hash209])) - .expect("Prune tags"); + block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![209]], vec![hash209])); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![210]); } @@ -173,22 +181,22 @@ fn prune_tags_should_work() { #[test] fn should_ban_invalid_transactions() { let (pool, api) = pool(); - let uxt = uxt(Alice, 209); + let uxt = Arc::from(uxt(Alice, 209)); let hash = - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt.clone())).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); // when let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, Vec::::new()); // then - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); } #[test] @@ -209,47 +217,56 @@ fn only_prune_on_new_best() { #[test] fn should_correctly_prune_transactions_providing_more_than_one_tag() { + sp_tracing::try_init_simple(); let api = Arc::new(TestApi::with_alice_nonce(209)); api.set_valid_modifier(Box::new(|v: &mut ValidTransaction| { v.provides.push(vec![155]); })); let pool = Pool::new(Default::default(), true.into(), api.clone()); - let xt = uxt(Alice, 209); - block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, xt.clone())) + let xt0 = Arc::from(uxt(Alice, 209)); + block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt0.clone())) .expect("1. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); + assert_eq!(api.validation_requests().len(), 1); // remove the transaction that just got imported. api.increment_nonce(Alice.into()); api.push_block(1, Vec::new(), true); - block_on(pool.prune_tags(api.expect_hash_from_number(1), vec![vec![209]], vec![])) - .expect("1. Pruned"); + block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![209]], vec![])); + assert_eq!(api.validation_requests().len(), 2); assert_eq!(pool.validated_pool().status().ready, 0); - // it's re-imported to future + // it's re-imported to future, API does not support stale - xt0 becomes future assert_eq!(pool.validated_pool().status().future, 1); // so now let's insert another transaction that also provides the 155 api.increment_nonce(Alice.into()); api.push_block(2, Vec::new(), true); - let xt = uxt(Alice, 211); - block_on(pool.submit_one(api.expect_hash_from_number(2), SOURCE, xt.clone())) + let xt1 = uxt(Alice, 211); + block_on(pool.submit_one(&api.expect_hash_and_number(2), SOURCE, xt1.clone().into())) .expect("2. Imported"); + assert_eq!(api.validation_requests().len(), 3); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 1); let pending: Vec<_> = pool .validated_pool() .ready() - .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .map(|a| TransferData::try_from(&*a.data).unwrap().nonce) .collect(); assert_eq!(pending, vec![211]); // prune it and make sure the pool is empty api.increment_nonce(Alice.into()); api.push_block(3, Vec::new(), true); - block_on(pool.prune_tags(api.expect_hash_from_number(3), vec![vec![155]], vec![])) - .expect("2. Pruned"); + block_on(pool.prune_tags(&api.expect_hash_and_number(3), vec![vec![155]], vec![])); + assert_eq!(api.validation_requests().len(), 4); + //xt0 was future, it failed (bc of 155 tag conflict) and was removed assert_eq!(pool.validated_pool().status().ready, 0); - assert_eq!(pool.validated_pool().status().future, 2); + //xt1 was ready, it was pruned (bc of 155 tag conflict) but was revalidated and resubmitted + // (API does not know about 155). + assert_eq!(pool.validated_pool().status().future, 1); + + let pending: Vec<_> = pool.validated_pool().futures().iter().map(|(hash, _)| *hash).collect(); + assert_eq!(pending[0], api.hash_and_length(&xt1).0); } fn block_event(header: Header) -> ChainEvent { @@ -297,7 +314,7 @@ fn should_revalidate_during_maintenance() { .expect("1. Imported"); let watcher = block_on(pool.submit_and_watch(api.expect_hash_from_number(0), SOURCE, xt2.clone())) - .expect("2. Imported"); + .expect("import"); //todo assert_eq!(pool.status().ready, 2); assert_eq!(api.validation_requests().len(), 2); @@ -929,14 +946,16 @@ fn ready_set_should_not_resolve_before_block_update() { let xt1 = uxt(Alice, 209); block_on(pool.submit_one(api.expect_hash_from_number(0), SOURCE, xt1.clone())) .expect("1. Imported"); + let hash_of_1 = api.push_block_with_parent(api.genesis_hash(), vec![], true).hash(); - assert!(pool.ready_at(1).now_or_never().is_none()); + assert!(pool.ready_at(hash_of_1).now_or_never().is_none()); } #[test] fn ready_set_should_resolve_after_block_update() { let (pool, api, _guard) = maintained_pool(); let header = api.push_block(1, vec![], true); + let hash_of_1 = header.hash(); let xt1 = uxt(Alice, 209); @@ -944,13 +963,14 @@ fn ready_set_should_resolve_after_block_update() { .expect("1. Imported"); block_on(pool.maintain(block_event(header))); - assert!(pool.ready_at(1).now_or_never().is_some()); + assert!(pool.ready_at(hash_of_1).now_or_never().is_some()); } #[test] fn ready_set_should_eventually_resolve_when_block_update_arrives() { let (pool, api, _guard) = maintained_pool(); let header = api.push_block(1, vec![], true); + let hash_of_1 = header.hash(); let xt1 = uxt(Alice, 209); @@ -960,7 +980,7 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { let noop_waker = futures::task::noop_waker(); let mut context = futures::task::Context::from_waker(&noop_waker); - let mut ready_set_future = pool.ready_at(1); + let mut ready_set_future = pool.ready_at(hash_of_1); if ready_set_future.poll_unpin(&mut context).is_ready() { panic!("Ready set should not be ready before block update!"); } @@ -1052,9 +1072,9 @@ fn stale_transactions_are_pruned() { // Our initial transactions let xts = vec![ - Transfer { from: Alice.into(), to: Bob.into(), nonce: 1, amount: 1 }, - Transfer { from: Alice.into(), to: Bob.into(), nonce: 2, amount: 1 }, - Transfer { from: Alice.into(), to: Bob.into(), nonce: 3, amount: 1 }, + Transfer { from: Alice.into(), to: Bob.into(), nonce: 10, amount: 1 }, + Transfer { from: Alice.into(), to: Bob.into(), nonce: 11, amount: 1 }, + Transfer { from: Alice.into(), to: Bob.into(), nonce: 12, amount: 1 }, ]; let (pool, api, _guard) = maintained_pool(); @@ -1086,6 +1106,7 @@ fn stale_transactions_are_pruned() { block_on(pool.maintain(block_event(header))); // The imported transactions have a different hash and should not evict our initial // transactions. + log::debug!("-> {:?}", pool.status()); assert_eq!(pool.status().future, 3); // Import enough blocks to make our transactions stale diff --git a/substrate/primitives/runtime/src/transaction_validity.rs b/substrate/primitives/runtime/src/transaction_validity.rs index ffff94e1746..2d800e29b8b 100644 --- a/substrate/primitives/runtime/src/transaction_validity.rs +++ b/substrate/primitives/runtime/src/transaction_validity.rs @@ -226,7 +226,7 @@ impl From for TransactionValidity { /// Depending on the source we might apply different validation schemes. /// For instance we can disallow specific kinds of transactions if they were not produced /// by our local node (for instance off-chain workers). -#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Hash)] pub enum TransactionSource { /// Transaction is already included in block. /// diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index b5dc034fed1..3cdaea64226 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -17,6 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } futures = { workspace = true } +log = { workspace = true } parking_lot = { workspace = true, default-features = true } thiserror = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } diff --git a/substrate/test-utils/runtime/transaction-pool/src/lib.rs b/substrate/test-utils/runtime/transaction-pool/src/lib.rs index 5202e6e6515..2d19dbfb6d4 100644 --- a/substrate/test-utils/runtime/transaction-pool/src/lib.rs +++ b/substrate/test-utils/runtime/transaction-pool/src/lib.rs @@ -23,7 +23,7 @@ use codec::Encode; use futures::future::ready; use parking_lot::RwLock; use sc_transaction_pool::ChainApi; -use sp_blockchain::{CachedHeaderMetadata, TreeRoute}; +use sp_blockchain::{CachedHeaderMetadata, HashAndNumber, TreeRoute}; use sp_runtime::{ generic::{self, BlockId}, traits::{ @@ -34,7 +34,10 @@ use sp_runtime::{ ValidTransaction, }, }; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::Arc, +}; use substrate_test_runtime_client::{ runtime::{ AccountId, Block, BlockNumber, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, @@ -46,7 +49,7 @@ use substrate_test_runtime_client::{ /// Error type used by [`TestApi`]. #[derive(Debug, thiserror::Error)] #[error(transparent)] -pub struct Error(#[from] sc_transaction_pool_api::error::Error); +pub struct Error(#[from] pub sc_transaction_pool_api::error::Error); impl sc_transaction_pool_api::error::IntoPoolError for Error { fn into_pool_error(self) -> Result { @@ -79,7 +82,7 @@ impl From for IsBestBlock { pub struct ChainState { pub block_by_number: BTreeMap>, pub block_by_hash: HashMap, - pub nonces: HashMap, + pub nonces: HashMap>, pub invalid_hashes: HashSet, pub priorities: HashMap, } @@ -89,14 +92,22 @@ pub struct TestApi { valid_modifier: RwLock>, chain: RwLock, validation_requests: RwLock>, + enable_stale_check: bool, } impl TestApi { /// Test Api with Alice nonce set initially. pub fn with_alice_nonce(nonce: u64) -> Self { let api = Self::empty(); + assert_eq!(api.chain.read().block_by_hash.len(), 1); + assert_eq!(api.chain.read().nonces.len(), 1); - api.chain.write().nonces.insert(Alice.into(), nonce); + api.chain + .write() + .nonces + .values_mut() + .nth(0) + .map(|h| h.insert(Alice.into(), nonce)); api } @@ -107,14 +118,23 @@ impl TestApi { valid_modifier: RwLock::new(Box::new(|_| {})), chain: Default::default(), validation_requests: RwLock::new(Default::default()), + enable_stale_check: false, }; // Push genesis block api.push_block(0, Vec::new(), true); + let hash0 = *api.chain.read().block_by_hash.keys().nth(0).unwrap(); + api.chain.write().nonces.insert(hash0, Default::default()); + api } + pub fn enable_stale_check(mut self) -> Self { + self.enable_stale_check = true; + self + } + /// Set hook on modify valid result of transaction. pub fn set_valid_modifier(&self, modifier: Box) { *self.valid_modifier.write() = modifier; @@ -184,6 +204,24 @@ impl TestApi { let mut chain = self.chain.write(); chain.block_by_hash.insert(hash, block.clone()); + if *block_number > 0 { + // copy nonces to new block + let prev_nonces = chain + .nonces + .get(block.header.parent_hash()) + .expect("there shall be nonces for parent block") + .clone(); + chain.nonces.insert(hash, prev_nonces); + } + + log::info!( + "add_block: {:?} {:?} {:?} nonces:{:#?}", + block_number, + hash, + block.header.parent_hash(), + chain.nonces + ); + if is_best_block { chain .block_by_number @@ -241,10 +279,33 @@ impl TestApi { &self.chain } + /// Set nonce in the inner state for given block. + pub fn set_nonce(&self, at: Hash, account: AccountId, nonce: u64) { + let mut chain = self.chain.write(); + chain.nonces.entry(at).and_modify(|h| { + h.insert(account, nonce); + }); + + log::debug!("set_nonce: {:?} nonces:{:#?}", at, chain.nonces); + } + + /// Increment nonce in the inner state for given block. + pub fn increment_nonce_at_block(&self, at: Hash, account: AccountId) { + let mut chain = self.chain.write(); + chain.nonces.entry(at).and_modify(|h| { + h.entry(account).and_modify(|n| *n += 1).or_insert(1); + }); + + log::debug!("increment_nonce_at_block: {:?} nonces:{:#?}", at, chain.nonces); + } + /// Increment nonce in the inner state. pub fn increment_nonce(&self, account: AccountId) { let mut chain = self.chain.write(); - chain.nonces.entry(account).and_modify(|n| *n += 1).or_insert(1); + // if no particular block was given, then update nonce everywhere + chain.nonces.values_mut().for_each(|h| { + h.entry(account).and_modify(|n| *n += 1).or_insert(1); + }) } /// Calculate a tree route between the two given blocks. @@ -260,6 +321,26 @@ impl TestApi { pub fn expect_hash_from_number(&self, n: BlockNumber) -> Hash { self.block_id_to_hash(&BlockId::Number(n)).unwrap().unwrap() } + + /// Helper function for getting genesis hash + pub fn genesis_hash(&self) -> Hash { + self.expect_hash_from_number(0) + } + + pub fn expect_hash_and_number(&self, n: BlockNumber) -> HashAndNumber { + HashAndNumber { hash: self.expect_hash_from_number(n), number: n } + } +} + +trait TagFrom { + fn tag_from(&self) -> u8; +} + +impl TagFrom for AccountId { + fn tag_from(&self) -> u8 { + let f = AccountKeyring::iter().enumerate().find(|k| AccountId::from(k.1) == *self); + u8::try_from(f.unwrap().0).unwrap() + } } impl ChainApi for TestApi { @@ -272,9 +353,11 @@ impl ChainApi for TestApi { &self, at: ::Hash, _source: TransactionSource, - uxt: ::Extrinsic, + uxt: Arc<::Extrinsic>, ) -> Self::ValidationFuture { + let uxt = (*uxt).clone(); self.validation_requests.write().push(uxt.clone()); + let block_number; match self.block_id_to_number(&BlockId::Hash(at)) { Ok(Some(number)) => { @@ -285,6 +368,7 @@ impl ChainApi for TestApi { .get(&number) .map(|blocks| blocks.iter().any(|b| b.1.is_best())) .unwrap_or(false); + block_number = Some(number); // If there is no best block, we don't know based on which block we should validate // the transaction. (This is not required for this test function, but in real @@ -303,10 +387,44 @@ impl ChainApi for TestApi { } let (requires, provides) = if let Ok(transfer) = TransferData::try_from(&uxt) { - let chain_nonce = self.chain.read().nonces.get(&transfer.from).cloned().unwrap_or(0); - let requires = - if chain_nonce == transfer.nonce { vec![] } else { vec![vec![chain_nonce as u8]] }; - let provides = vec![vec![transfer.nonce as u8]]; + let chain_nonce = self + .chain + .read() + .nonces + .get(&at) + .expect("nonces must be there for every block") + .get(&transfer.from) + .cloned() + .unwrap_or(0); + let requires = if chain_nonce == transfer.nonce { + vec![] + } else { + if self.enable_stale_check { + vec![vec![transfer.from.tag_from(), (transfer.nonce - 1) as u8]] + } else { + vec![vec![(transfer.nonce - 1) as u8]] + } + }; + let provides = if self.enable_stale_check { + vec![vec![transfer.from.tag_from(), transfer.nonce as u8]] + } else { + vec![vec![transfer.nonce as u8]] + }; + + log::info!( + "test_api::validate_transaction: h:{:?} n:{:?} cn:{:?} tn:{:?} r:{:?} p:{:?}", + at, + block_number, + chain_nonce, + transfer.nonce, + requires, + provides, + ); + + if self.enable_stale_check && transfer.nonce < chain_nonce { + log::info!("test_api::validate_transaction: invalid_transaction(stale)...."); + return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)))) + } (requires, provides) } else { @@ -314,6 +432,7 @@ impl ChainApi for TestApi { }; if self.chain.read().invalid_hashes.contains(&self.hash_and_length(&uxt).0) { + log::info!("test_api::validate_transaction: invalid_transaction...."); return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0))))) } diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index 9fcaa53a35d..824c871a356 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -245,8 +245,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let source = sp_runtime::transaction_validity::TransactionSource::External; let new_transaction = |nonce: u64| { @@ -281,8 +286,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let accounts = System::new(client, pool); @@ -300,8 +310,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let accounts = System::new(client, pool); @@ -331,8 +346,13 @@ mod tests { // given let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = - BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); + let pool = Arc::from(BasicPool::new_full( + Default::default(), + true.into(), + None, + spawner, + client.clone(), + )); let accounts = System::new(client, pool); diff --git a/substrate/utils/prometheus/src/lib.rs b/substrate/utils/prometheus/src/lib.rs index 460640bcd8e..35597cad03d 100644 --- a/substrate/utils/prometheus/src/lib.rs +++ b/substrate/utils/prometheus/src/lib.rs @@ -27,8 +27,8 @@ pub use prometheus::{ AtomicF64 as F64, AtomicI64 as I64, AtomicU64 as U64, GenericCounter as Counter, GenericCounterVec as CounterVec, GenericGauge as Gauge, GenericGaugeVec as GaugeVec, }, - exponential_buckets, Error as PrometheusError, Histogram, HistogramOpts, HistogramVec, Opts, - Registry, + exponential_buckets, histogram_opts, linear_buckets, Error as PrometheusError, Histogram, + HistogramOpts, HistogramVec, Opts, Registry, }; pub use sourced::{MetricSource, SourcedCounter, SourcedGauge, SourcedMetric}; diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs index 08cd345f1e3..6ba6959202c 100644 --- a/templates/minimal/node/src/service.rs +++ b/templates/minimal/node/src/service.rs @@ -46,7 +46,7 @@ pub type Service = sc_service::PartialComponents< FullBackend, FullSelectChain, sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, Option, >; @@ -79,12 +79,15 @@ pub fn new_partial(config: &Configuration) -> Result { let select_chain = sc_consensus::LongestChain::new(backend.clone()); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let import_queue = sc_consensus_manual_seal::import_queue( diff --git a/templates/parachain/node/src/service.rs b/templates/parachain/node/src/service.rs index 6729b9d7ef4..dd7dff2ebf1 100644 --- a/templates/parachain/node/src/service.rs +++ b/templates/parachain/node/src/service.rs @@ -57,7 +57,7 @@ pub type Service = PartialComponents< ParachainBackend, (), sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, (ParachainBlockImport, Option, Option), >; @@ -107,12 +107,15 @@ pub fn new_partial(config: &Configuration) -> Result telemetry }); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); @@ -173,7 +176,7 @@ fn start_consensus( telemetry: Option, task_manager: &TaskManager, relay_chain_interface: Arc, - transaction_pool: Arc>, + transaction_pool: Arc>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, para_id: ParaId, diff --git a/templates/solochain/node/src/service.rs b/templates/solochain/node/src/service.rs index 2de543235ec..4192128b672 100644 --- a/templates/solochain/node/src/service.rs +++ b/templates/solochain/node/src/service.rs @@ -31,7 +31,7 @@ pub type Service = sc_service::PartialComponents< FullBackend, FullSelectChain, sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, + sc_transaction_pool::TransactionPoolHandle, ( sc_consensus_grandpa::GrandpaBlockImport, sc_consensus_grandpa::LinkHalf, @@ -67,12 +67,15 @@ pub fn new_partial(config: &Configuration) -> Result { let select_chain = sc_consensus::LongestChain::new(backend.clone()); - let transaction_pool = sc_transaction_pool::BasicPool::new_full( - config.transaction_pool.clone(), - config.role.is_authority().into(), - config.prometheus_registry(), - task_manager.spawn_essential_handle(), - client.clone(), + let transaction_pool = Arc::from( + sc_transaction_pool::Builder::new( + task_manager.spawn_essential_handle(), + client.clone(), + config.role.is_authority().into(), + ) + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import( -- GitLab From 4edb21982befcce000537f13e31a87a64480ab38 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:19:14 +0200 Subject: [PATCH 103/127] [ci] Move build and publish rustdocs to GHA (#6047) PR moves jobs `build-rustdoc`, `test-doc` and `publish-rustdoc` to GHA. `publish-rustdoc` was changed so it can publish changes using token from GH APP. PR removes `test-rustdoc` because the same command in executed in `build-rustdoc` and I see no reason to run it twice. cc https://github.com/paritytech/ci_cd/issues/1006 --- .github/workflows/docs.yml | 68 ++++++++++++++++++++++++------------ .gitlab/pipeline/build.yml | 43 ----------------------- .gitlab/pipeline/publish.yml | 61 -------------------------------- .gitlab/pipeline/test.yml | 27 -------------- 4 files changed, 45 insertions(+), 154 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8fe3e80e628..610f45fa386 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,20 +14,8 @@ concurrency: jobs: preflight: - if: contains(github.event.label.name, 'GHA-migration') uses: ./.github/workflows/reusable-preflight.yml - test-rustdoc: - runs-on: ${{ needs.preflight.outputs.RUNNER }} - if: ${{ needs.preflight.outputs.changes_rust }} - needs: [preflight] - container: - image: ${{ needs.preflight.outputs.IMAGE }} - steps: - - uses: actions/checkout@v4 - - run: forklift cargo doc --workspace --all-features --no-deps - env: - SKIP_WASM_BUILD: 1 test-doc: runs-on: ${{ needs.preflight.outputs.RUNNER }} needs: [preflight] @@ -42,7 +30,7 @@ jobs: build-rustdoc: runs-on: ${{ needs.preflight.outputs.RUNNER }} if: ${{ needs.preflight.outputs.changes_rust }} - needs: [preflight, test-rustdoc] + needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} steps: @@ -88,6 +76,23 @@ jobs: retention-days: 1 if-no-files-found: error + confirm-required-jobs-passed: + runs-on: ubuntu-latest + name: All docs jobs passed + # If any new job gets added, be sure to add it to this array + needs: [test-doc, build-rustdoc, build-implementers-guide] + if: always() && !cancelled() + steps: + - run: | + tee resultfile <<< '${{ toJSON(needs) }}' + FAILURES=$(cat resultfile | grep '"result": "failure"' | wc -l) + if [ $FAILURES -gt 0 ]; then + echo "### At least one required job failed ❌" >> $GITHUB_STEP_SUMMARY + exit 1 + else + echo '### Good job! All the required jobs passed 🚀' >> $GITHUB_STEP_SUMMARY + fi + publish-rustdoc: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest @@ -121,13 +126,30 @@ jobs: - run: mkdir -p book - name: Move book files run: mv /tmp/book/html/* book/ - - name: Push to GH-Pages branch - uses: github-actions-x/commit@v2.9 - with: - github-token: ${{ steps.app-token.outputs.token }} - push-branch: "gh-pages" - commit-message: "___Updated docs for ${{ github.head_ref || github.ref_name }}___" - force-add: "true" - files: ${{ github.head_ref || github.ref_name }}/ book/ - name: devops-parity - email: devops-team@parity.io + - name: Push changes to gh-pages + env: + TOKEN: ${{ steps.app-token.outputs.token }} + APP_NAME: "paritytech-upd-ghpages-polkadotsdk" + REF_NAME: ${{ github.head_ref || github.ref_name }} + Green: "\e[32m" + NC: "\e[0m" + run: | + echo "${Green}Git add${NC}" + git add book/ + git add ${REF_NAME}/ + + echo "${Green}git status | wc -l${NC}" + git status | wc -l + + echo "${Green}Add new remote with gh app token${NC}" + git remote set-url origin $(git config remote.origin.url | sed "s/github.com/${APP_NAME}:${TOKEN}@github.com/g") + + echo "${Green}Remove http section that causes issues with gh app auth token${NC}" + sed -i.bak '/\[http/d' ./.git/config + sed -i.bak '/extraheader/d' ./.git/config + + echo "${Green}Git push${NC}" + git config user.email "ci@parity.io" + git config user.name "${APP_NAME}" + git commit --amend -m "___Updated docs" || echo "___Nothing to commit___" + git push origin gh-pages --force diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 179eebf8b50..1bd04ae670f 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -100,49 +100,6 @@ build-templates-node: - mv ./target/release/minimal-template-node ./artifacts/. - mv ./target/release/solochain-template-node ./artifacts/. -build-rustdoc: - stage: build - extends: - - .docker-env - - .common-refs - - .run-immediately - variables: - SKIP_WASM_BUILD: 1 - RUSTDOCFLAGS: "-Dwarnings --default-theme=ayu --html-in-header ./docs/sdk/assets/header.html --extend-css ./docs/sdk/assets/theme.css --html-after-content ./docs/sdk/assets/after-content.html" - artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" - when: on_success - expire_in: 1 days - paths: - - ./crate-docs/ - script: - - time cargo doc --all-features --workspace --no-deps - - rm -f ./target/doc/.lock - - mv ./target/doc ./crate-docs - # Inject Simple Analytics (https://www.simpleanalytics.com/) privacy preserving tracker into - # all .html files - - > - inject_simple_analytics() { - local path="$1"; - local script_content=""; - - # Function that inject script into the head of an html file using sed. - process_file() { - local file="$1"; - echo "Adding Simple Analytics script to $file"; - sed -i "s||$script_content|" "$file"; - }; - export -f process_file; - # xargs runs process_file in separate shells without access to outer variables. - # make script_content available inside process_file, export it as an env var here. - export script_content; - - # Modify .html files in parallel using xargs, otherwise it can take a long time. - find "$path" -name '*.html' | xargs -I {} -P "$(nproc)" bash -c 'process_file "$@"' _ {}; - }; - inject_simple_analytics "./crate-docs"; - - echo "" > ./crate-docs/index.html - build-implementers-guide: stage: build extends: diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index c0ce058f17f..92deaea2f61 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -1,67 +1,6 @@ # This file is part of .gitlab-ci.yml # Here are all jobs that are executed during "publish" stage -publish-rustdoc: - stage: publish - extends: - - .kubernetes-env - - .publish-gh-pages-refs - variables: - CI_IMAGE: node:18 - GIT_DEPTH: 100 - RUSTDOCS_DEPLOY_REFS: "master" - needs: - - job: build-rustdoc - artifacts: true - - job: build-implementers-guide - artifacts: true - script: - # If $CI_COMMIT_REF_NAME doesn't match one of $RUSTDOCS_DEPLOY_REFS space-separated values, we - # exit immediately. - # Putting spaces at the front and back to ensure we are not matching just any substring, but the - # whole space-separated value. - # setup ssh - - eval $(ssh-agent) - - ssh-add - <<< ${GITHUB_SSH_PRIV_KEY} - - mkdir ~/.ssh && touch ~/.ssh/known_hosts - - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - # Set git config - - git config user.email "devops-team@parity.io" - - git config user.name "${GITHUB_USER}" - - git config remote.origin.url "git@github.com:/paritytech/${CI_PROJECT_NAME}.git" - - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" - - git fetch origin gh-pages - # Save README and docs - - cp -r ./crate-docs/ /tmp/doc/ - - cp -r ./artifacts/book/ /tmp/ - - cp README.md /tmp/doc/ - # we don't need to commit changes because we copy docs to /tmp - - git checkout gh-pages --force - # Enable if docs needed for other refs - # Install `index-tpl-crud` and generate index.html based on RUSTDOCS_DEPLOY_REFS - # - which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud - # - index-tpl-crud upsert ./index.html ${CI_COMMIT_REF_NAME} - # Ensure the destination dir doesn't exist. - - rm -rf ${CI_COMMIT_REF_NAME} - - rm -rf book/ - - mv -f /tmp/doc ${CI_COMMIT_REF_NAME} - # dir for implementors guide - - mkdir -p book - - mv /tmp/book/html/* book/ - # Upload files - - git add --all - # `git commit` has an exit code of > 0 if there is nothing to commit. - # This causes GitLab to exit immediately and marks this job failed. - # We don't want to mark the entire job failed if there's nothing to - # publish though, hence the `|| true`. - - git commit --amend -m "___Updated docs" || - echo "___Nothing to commit___" - - git push origin gh-pages --force - # artificial sleep to publish gh-pages - - sleep 300 - after_script: - - rm -rf .git/ ./* - # note: images are used not only in zombienet but also in rococo, wococo and versi .build-push-image: image: $BUILDAH_IMAGE diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index 81e9ad12751..8e32a361467 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -109,30 +109,3 @@ test-linux-stable-codecov: else codecovcli -v do-upload -f target/coverage/result/report-${CI_NODE_INDEX}.lcov --disable-search -t ${CODECOV_TOKEN} -r paritytech/polkadot-sdk --commit-sha ${CI_COMMIT_SHA} --fail-on-error --git-service github; fi - -test-doc: - stage: test - extends: - - .docker-env - - .common-refs - # DAG - needs: - - job: test-rustdoc - artifacts: false - variables: - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - script: - - time cargo test --doc --workspace - -test-rustdoc: - stage: test - extends: - - .docker-env - - .common-refs - - .run-immediately - variables: - SKIP_WASM_BUILD: 1 - script: - - time cargo doc --workspace --all-features --no-deps -- GitLab From f754863ac18cc5ee852d8bfd3988e3b4f24fa45e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:15:32 +0000 Subject: [PATCH 104/127] Bump tokio-test from 0.4.3 to 0.4.4 (#5944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [tokio-test](https://github.com/tokio-rs/tokio) from 0.4.3 to 0.4.4.
[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tokio-test&package-manager=cargo&previous-version=0.4.3&new-version=0.4.4)](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> Co-authored-by: Bastian Köcher Co-authored-by: Sebastian Kunert --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40b36e2602f..1a1c10b9570 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24813,9 +24813,9 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" dependencies = [ "async-stream", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 08f06982d2f..490b086eb24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1321,7 +1321,7 @@ tiny-keccak = { version = "2.0.2" } tokio = { version = "1.40.0", default-features = false } tokio-retry = { version = "0.3.0" } tokio-stream = { version = "0.1.14" } -tokio-test = { version = "0.4.2" } +tokio-test = { version = "0.4.4" } tokio-tungstenite = { version = "0.20.1" } tokio-util = { version = "0.7.8" } toml = { version = "0.8.12" } -- GitLab From 5a8e082b2314ab6deff7991877e6b572f20df7ea Mon Sep 17 00:00:00 2001 From: Radek Bochenek <81041102+rbochenek@users.noreply.github.com> Date: Tue, 15 Oct 2024 23:37:58 +0200 Subject: [PATCH 105/127] Fix `solochain-template-runtime` freezes config (#5846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Sets the correct `RuntimeFreezeReason` type for `solochain-template-runtime` configuration of pallet_balances. ## Review Notes For whatever reason `RuntimeFreezeReason` is currently set to `RuntimeHoldReason`. This in turn causes problems with variant counting in `MaxFreezes` and results in pallet_balances integrity tests failing whenever hold/freeze reasons are added to the runtime. This fixes it by simply setting `RuntimeFreezeReason` to `RuntimeFreezeReason` in pallet_balances Config. Co-authored-by: Bastian Köcher --- templates/solochain/runtime/src/configs/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/solochain/runtime/src/configs/mod.rs b/templates/solochain/runtime/src/configs/mod.rs index 4f77ad37fe6..02d44967513 100644 --- a/templates/solochain/runtime/src/configs/mod.rs +++ b/templates/solochain/runtime/src/configs/mod.rs @@ -133,7 +133,7 @@ impl pallet_balances::Config for Runtime { type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = VariantCountOf; type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; type DoneSlashHandler = (); } -- GitLab From fbd69a3b10859403dea0bd0b31ac0fe9b6deec62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Gil?= Date: Wed, 16 Oct 2024 02:20:59 +0200 Subject: [PATCH 106/127] remove pallet::getter from pallet-offences (#6027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Part of https://github.com/paritytech/polkadot-sdk/issues/3326 Removes pallet::getter from pallet-offences from type `Reports`. Adds a test to verify that retrieval of `Reports` still works with storage::getter. polkadot address: 155YyFVoMnv9BY6qxy2i5wxtveUw7WE1n5H81fL8PZKTA1Sd --------- Co-authored-by: Shawn Tabrizi Co-authored-by: Dónal Murray --- prdoc/pr_6027.prdoc | 9 +++++++++ substrate/frame/offences/src/lib.rs | 8 +++++++- substrate/frame/offences/src/tests.rs | 26 ++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6027.prdoc diff --git a/prdoc/pr_6027.prdoc b/prdoc/pr_6027.prdoc new file mode 100644 index 00000000000..36bdb57b25f --- /dev/null +++ b/prdoc/pr_6027.prdoc @@ -0,0 +1,9 @@ +title: Remove pallet::getter from pallet-offences +doc: + - audience: Runtime Dev + description: | + This PR removes pallet::getter from pallet-offences from type Reports. It also adds a test to verify that retrieval of Reports still works with storage::getter. + +crates: + - name: pallet-offences + bump: patch diff --git a/substrate/frame/offences/src/lib.rs b/substrate/frame/offences/src/lib.rs index ffea32a1f47..18f37c759a6 100644 --- a/substrate/frame/offences/src/lib.rs +++ b/substrate/frame/offences/src/lib.rs @@ -73,7 +73,6 @@ pub mod pallet { /// The primary structure that holds all offence records keyed by report identifiers. #[pallet::storage] - #[pallet::getter(fn reports)] pub type Reports = StorageMap< _, Twox64Concat, @@ -152,6 +151,13 @@ where } impl Pallet { + /// Get the offence details from reports of given ID. + pub fn reports( + report_id: ReportIdOf, + ) -> Option> { + Reports::::get(report_id) + } + /// Compute the ID for the given report properties. /// /// The report id depends on the offence kind, time slot and the id of offender. diff --git a/substrate/frame/offences/src/tests.rs b/substrate/frame/offences/src/tests.rs index 4897b78f3e4..ab72b51054d 100644 --- a/substrate/frame/offences/src/tests.rs +++ b/substrate/frame/offences/src/tests.rs @@ -21,12 +21,34 @@ use super::*; use crate::mock::{ - new_test_ext, offence_reports, with_on_offence_fractions, Offence, Offences, RuntimeEvent, - System, KIND, + new_test_ext, offence_reports, with_on_offence_fractions, Offence, Offences, Runtime, + RuntimeEvent, System, KIND, }; use frame_system::{EventRecord, Phase}; +use sp_core::H256; use sp_runtime::Perbill; +#[test] +fn should_get_reports_with_storagemap_getter_and_function_getter() { + new_test_ext().execute_with(|| { + // given + let report_id: ReportIdOf = H256::from_low_u64_be(1); + let offence_details = OffenceDetails { offender: 1, reporters: vec![2, 3] }; + + Reports::::insert(report_id, offence_details.clone()); + + // when + let stored_offence_details = Offences::reports(report_id); + // then + assert_eq!(stored_offence_details, Some(offence_details.clone())); + + // when + let stored_offence_details = Reports::::get(report_id); + // then + assert_eq!(stored_offence_details, Some(offence_details.clone())); + }); +} + #[test] fn should_report_an_authority_and_trigger_on_offence() { new_test_ext().execute_with(|| { -- GitLab From 38aa4b755c5bb332436950363a9826a91de4b2ef Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:45:36 +0300 Subject: [PATCH 107/127] litep2p/discovery: Fix memory leak in `litep2p.public_addresses()` (#5998) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR ensures that the `litep2p.public_addresses()` never grows indefinitely. - effectively fixes subtle memory leaks - fixes authority DHT records being dropped due to size limits being exceeded - provides a healthier subset of public addresses to the `/identify` protocol This PR adds a new `ExternalAddressExpired` event to the litep2p/discovery process. Substrate uses an LRU `address_confirmations` bounded to 32 address entries. The oldest entry is propagated via the `ExternalAddressExpired` event when a new address is added to the list (if capacity is exceeded). The expired address is then removed from the `litep2p.public_addresses()`, effectively limiting its size to 32 entries (the size of `address_confirmations` LRU). cc @paritytech/networking @alexggh --------- Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher Co-authored-by: Dmitry Markin --- prdoc/pr_5998.prdoc | 15 +++++ .../client/network/src/litep2p/discovery.rs | 57 +++++++++++++++++-- substrate/client/network/src/litep2p/mod.rs | 19 +++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_5998.prdoc diff --git a/prdoc/pr_5998.prdoc b/prdoc/pr_5998.prdoc new file mode 100644 index 00000000000..e3279051ca6 --- /dev/null +++ b/prdoc/pr_5998.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: Fix memory leak in litep2p public addresses + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + This PR bounds the number of public addresses of litep2p to 32 entries. + This ensures we do not increase the number of addresses over time, and that the DHT + authority records will not exceed the upper size limit. + +crates: + - name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index 5fe944cadc0..13cf8a4c6ee 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -116,7 +116,16 @@ pub enum DiscoveryEvent { /// New external address discovered. ExternalAddressDiscovered { - /// Discovered addresses. + /// Discovered address. + address: Multiaddr, + }, + + /// The external address has expired. + /// + /// This happens when the internal buffers exceed the maximum number of external addresses, + /// and this address is the oldest one. + ExternalAddressExpired { + /// Expired address. address: Multiaddr, }, @@ -423,7 +432,13 @@ impl Discovery { } /// Check if `address` can be considered a new external address. - fn is_new_external_address(&mut self, address: &Multiaddr, peer: PeerId) -> bool { + /// + /// If this address replaces an older address, the expired address is returned. + fn is_new_external_address( + &mut self, + address: &Multiaddr, + peer: PeerId, + ) -> (bool, Option) { log::trace!(target: LOG_TARGET, "verify new external address: {address}"); // is the address one of our known addresses @@ -434,7 +449,7 @@ impl Discovery { .chain(self.public_addresses.iter()) .any(|known_address| Discovery::is_known_address(&known_address, &address)) { - return true + return (true, None) } match self.address_confirmations.get(address) { @@ -442,15 +457,31 @@ impl Discovery { confirmations.insert(peer); if confirmations.len() >= MIN_ADDRESS_CONFIRMATIONS { - return true + return (true, None) } }, None => { + let oldest = (self.address_confirmations.len() >= + self.address_confirmations.limiter().max_length() as usize) + .then(|| { + self.address_confirmations.pop_oldest().map(|(address, peers)| { + if peers.len() >= MIN_ADDRESS_CONFIRMATIONS { + return Some(address) + } else { + None + } + }) + }) + .flatten() + .flatten(); + self.address_confirmations.insert(address.clone(), Default::default()); + + return (false, oldest) }, } - false + (false, None) } } @@ -557,7 +588,21 @@ impl Stream for Discovery { observed_address, .. })) => { - if this.is_new_external_address(&observed_address, peer) { + let (is_new, expired_address) = + this.is_new_external_address(&observed_address, peer); + + if let Some(expired_address) = expired_address { + log::trace!( + target: LOG_TARGET, + "Removing expired external address expired={expired_address} is_new={is_new} observed={observed_address}", + ); + + this.pending_events.push_back(DiscoveryEvent::ExternalAddressExpired { + address: expired_address, + }); + } + + if is_new { this.pending_events.push_back(DiscoveryEvent::ExternalAddressDiscovered { address: observed_address.clone(), }); diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 277f0759729..df4244890f9 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -935,6 +935,25 @@ impl NetworkBackend for Litep2pNetworkBac }, } } + Some(DiscoveryEvent::ExternalAddressExpired{ address }) => { + let local_peer_id = self.litep2p.local_peer_id(); + + // Litep2p requires the peer ID to be present in the address. + let address = if !std::matches!(address.iter().last(), Some(Protocol::P2p(_))) { + address.with(Protocol::P2p(*local_peer_id.as_ref())) + } else { + address + }; + + if self.litep2p.public_addresses().remove_address(&address) { + log::info!(target: LOG_TARGET, "🔍 Expired external address for our node: {address}"); + } else { + log::warn!( + target: LOG_TARGET, + "🔍 Failed to remove expired external address {address:?}" + ); + } + } Some(DiscoveryEvent::Ping { peer, rtt }) => { log::trace!( target: LOG_TARGET, -- GitLab From 2c41656cff79ac12d0d4ddab3420d1aab35f4847 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Wed, 16 Oct 2024 10:35:56 +0200 Subject: [PATCH 108/127] Stabilize elastic-pov-recovery zombienet test (#6076) We should start the `recovery-target` node first, then the `collator-elastic` node. Also increases the resources available to these pods. --- .../tests/0009-elastic_pov_recovery.toml | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml b/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml index b695f8aa937..1cf0775a2e1 100644 --- a/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml +++ b/cumulus/zombienet/tests/0009-elastic_pov_recovery.toml @@ -1,6 +1,14 @@ [settings] timeout = 1000 +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + +[parachain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + [relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] max_candidate_depth = 6 allowed_ancestry_len = 3 @@ -23,7 +31,11 @@ command = "polkadot" [[relaychain.node_groups]] name = "validator" - args = ["-lruntime=debug,parachain=trace", "--reserved-only", "--reserved-nodes {{'alice'|zombie('multiAddress')}}"] + args = [ + "-lruntime=debug,parachain=trace", + "--reserved-only", + "--reserved-nodes {{'alice'|zombie('multiAddress')}}" + ] count = 8 # Slot based authoring with 3 cores and 2s slot duration @@ -32,17 +44,29 @@ id = 2100 chain = "elastic-scaling" add_to_genesis = false - # Slot based authoring with 3 cores and 2s slot duration + # run 'recovery-target' as a parachain full node [[parachains.collators]] - name = "collator-elastic" + name = "recovery-target" + validator = false # full node image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["--disable-block-announcements", "-laura=trace,runtime=info,cumulus-consensus=trace,consensus::common=trace,parachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug", "--force-authoring", "--experimental-use-slot-based"] + args = [ + "-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", + "--disable-block-announcements", + "--in-peers 0", + "--out-peers 0", + "--", + "--reserved-only", + "--reserved-nodes {{'alice'|zombie('multiAddress')}}"] - # run 'recovery-target' as a parachain full node + # Slot based authoring with 3 cores and 2s slot duration [[parachains.collators]] - name = "recovery-target" - validator = false # full node + name = "collator-elastic" image = "{{COL_IMAGE}}" command = "test-parachain" - args = ["-lparachain::availability=trace,sync=debug,parachain=debug,cumulus-pov-recovery=debug,cumulus-consensus=debug", "--disable-block-announcements", "--bootnodes {{'collator-elastic'|zombie('multiAddress')}}", "--in-peers 0", "--out-peers 0", "--", "--reserved-only", "--reserved-nodes {{'alice'|zombie('multiAddress')}}"] + args = [ + "-laura=trace,runtime=info,cumulus-consensus=trace,consensus::common=trace,parachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug", + "--disable-block-announcements", + "--force-authoring", + "--experimental-use-slot-based" + ] -- GitLab From b649f4a4695b5444e73ef755658cf5c6efdc68b6 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:53:50 +0300 Subject: [PATCH 109/127] Metadata V16 (unstable): Enrich metadata with associated types of config traits (#5274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature is part of the upcoming metadata V16. The associated types of the `Config` trait that require the `TypeInfo` or `Parameter` bounds are included in the metadata of the pallet. The metadata is not yet exposed to the end-user, however the metadata intermediate representation (IR) contains these types. Developers can opt out of metadata collection of the associated types by specifying `without_metadata` optional attribute to the `#[pallet::config]`. Furthermore, the `without_metadata` argument can be used in combination with the newly added `#[pallet::include_metadata]` attribute to selectively include only certain associated types in the metadata collection. ### API Design - There is nothing to collect from the associated types: ```rust #[pallet::config] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From> + IsType<::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get; } ``` - Default automatic collection of associated types that require TypeInfo or Parameter bounds: ```rust #[pallet::config] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From> + IsType<::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get; // Associated type included by default, because it requires TypeInfo bound. /// Nonce doc. type Nonce: TypeInfo; // Associated type included by default, because it requires // Parameter bound (indirect TypeInfo). type AccountData: Parameter; // Associated type without metadata bounds, not included. type NotIncluded: From; } ``` - Disable automatic collection ```rust // Associated types are not collected by default. #[pallet::config(without_metadata)] pub trait Config: frame_system::Config { // Runtime events already propagated to the metadata. type RuntimeEvent: From> + IsType<::RuntimeEvent>; // Constants are already propagated. #[pallet::constant] type MyGetParam2: Get; // Explicitly include associated types. #[pallet::include_metadata] type Nonce: TypeInfo; type AccountData: Parameter; type NotIncluded: From; } ``` Builds on top of the PoC: https://github.com/paritytech/polkadot-sdk/pull/4358 Closes: https://github.com/paritytech/polkadot-sdk/issues/4519 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher Co-authored-by: Guillaume Thiolliere Co-authored-by: Shawn Tabrizi --- prdoc/pr_5274.prdoc | 24 ++ .../src/construct_runtime/expand/metadata.rs | 11 + substrate/frame/support/procedural/src/lib.rs | 9 + .../procedural/src/pallet/expand/config.rs | 48 ++++ .../procedural/src/pallet/expand/mod.rs | 2 + .../procedural/src/pallet/parse/config.rs | 113 +++++++- .../procedural/src/pallet/parse/mod.rs | 79 ++++- .../frame/support/procedural/tools/src/lib.rs | 14 + substrate/frame/support/src/lib.rs | 58 ++++ .../deprecated_where_block.stderr | 25 ++ .../tests/pallet_associated_types_metadata.rs | 269 ++++++++++++++++++ .../tests/pallet_ui/config_duplicate_attr.rs | 39 +++ .../pallet_ui/config_duplicate_attr.stderr | 5 + .../config_metadata_non_type_info.rs | 42 +++ .../config_metadata_non_type_info.stderr | 5 + .../pallet_ui/config_metadata_on_constants.rs | 40 +++ .../config_metadata_on_constants.stderr | 5 + .../pallet_ui/config_metadata_on_events.rs | 43 +++ .../config_metadata_on_events.stderr | 5 + ...no_default_but_missing_with_default.stderr | 2 +- .../pallet_ui/pass/config_multiple_attr.rs | 32 +++ .../pallet_ui/pass/config_without_metadata.rs | 32 +++ substrate/primitives/metadata-ir/src/types.rs | 26 ++ 23 files changed, 917 insertions(+), 11 deletions(-) create mode 100644 prdoc/pr_5274.prdoc create mode 100644 substrate/frame/support/test/tests/pallet_associated_types_metadata.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr create mode 100644 substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs create mode 100644 substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs diff --git a/prdoc/pr_5274.prdoc b/prdoc/pr_5274.prdoc new file mode 100644 index 00000000000..fb76ce661b4 --- /dev/null +++ b/prdoc/pr_5274.prdoc @@ -0,0 +1,24 @@ +title: Enrich metadata IR with associated types of config traits + +doc: + - audience: Runtime Dev + description: | + This feature is part of the upcoming metadata V16. The associated types of the `Config` trait that require the `TypeInfo` + or `Parameter` bounds are included in the metadata of the pallet. The metadata is not yet exposed to the end-user, however + the metadata intermediate representation (IR) contains these types. + + Developers can opt out of metadata collection of the associated types by specifying `without_metadata` optional attribute + to the `#[pallet::config]`. + + Furthermore, the `without_metadata` argument can be used in combination with the newly added `#[pallet::include_metadata]` + attribute to selectively include only certain associated types in the metadata collection. + +crates: + - name: frame-support + bump: patch + - name: frame-support-procedural + bump: patch + - name: frame-support-procedural-tools + bump: patch + - name: sp-metadata-ir + bump: major diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index f3724f4ccb6..54eb290ca6c 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -49,6 +49,7 @@ pub fn expand_runtime_metadata( let event = expand_pallet_metadata_events(&filtered_names, runtime, decl); let constants = expand_pallet_metadata_constants(runtime, decl); let errors = expand_pallet_metadata_errors(runtime, decl); + let associated_types = expand_pallet_metadata_associated_types(runtime, decl); let docs = expand_pallet_metadata_docs(runtime, decl); let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) @@ -70,6 +71,7 @@ pub fn expand_runtime_metadata( constants: #constants, error: #errors, docs: #docs, + associated_types: #associated_types, deprecation_info: #deprecation_info, } } @@ -261,3 +263,12 @@ fn expand_pallet_metadata_docs(runtime: &Ident, decl: &Pallet) -> TokenStream { #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_documentation_metadata() } } + +fn expand_pallet_metadata_associated_types(runtime: &Ident, decl: &Pallet) -> TokenStream { + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + + quote! { + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_associated_types_metadata() + } +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index a2c1e6eec7f..c2f546d9204 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -972,6 +972,15 @@ pub fn event(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// +/// Documentation for this macro can be found at `frame_support::pallet_macros::include_metadata`. +#[proc_macro_attribute] +pub fn include_metadata(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// /// --- /// diff --git a/substrate/frame/support/procedural/src/pallet/expand/config.rs b/substrate/frame/support/procedural/src/pallet/expand/config.rs index 5cf4035a8f8..0a583f1359b 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/config.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/config.rs @@ -95,3 +95,51 @@ Consequently, a runtime that wants to include this pallet must implement this tr _ => Default::default(), } } + +/// Generate the metadata for the associated types of the config trait. +/// +/// Implements the `pallet_associated_types_metadata` function for the pallet. +pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { + let frame_support = &def.frame_support; + let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site()); + let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site()); + let pallet_ident = &def.pallet_struct.pallet; + let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site()); + + let mut where_clauses = vec![&def.config.where_clause]; + where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause)); + let completed_where_clause = super::merge_where_clauses(&where_clauses); + + let types = def.config.associated_types_metadata.iter().map(|metadata| { + let ident = &metadata.ident; + let span = ident.span(); + let ident_str = ident.to_string(); + let cfgs = &metadata.cfg; + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &metadata.doc }; + + quote::quote_spanned!(span => { + #( #cfgs ) * + #frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR { + name: #ident_str, + ty: #frame_support::__private::scale_info::meta_type::< + ::#ident + >(), + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + }) + }); + + quote::quote!( + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { + + #[doc(hidden)] + pub fn pallet_associated_types_metadata() + -> #frame_support::__private::sp_std::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> + { + #frame_support::__private::sp_std::vec![ #( #types ),* ] + } + } + ) +} diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 067839c2846..3f9b50f79c0 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -60,6 +60,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let constants = constants::expand_constants(&mut def); let pallet_struct = pallet_struct::expand_pallet_struct(&mut def); let config = config::expand_config(&mut def); + let associated_types = config::expand_config_metadata(&def); let call = call::expand_call(&mut def); let tasks = tasks::expand_tasks(&mut def); let error = error::expand_error(&mut def); @@ -101,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #constants #pallet_struct #config + #associated_types #call #tasks #error diff --git a/substrate/frame/support/procedural/src/pallet/parse/config.rs b/substrate/frame/support/procedural/src/pallet/parse/config.rs index 9a59d711420..6b6dcc802e2 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/config.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/config.rs @@ -16,9 +16,9 @@ // limitations under the License. use super::helper; -use frame_support_procedural_tools::{get_doc_literals, is_using_frame_crate}; +use frame_support_procedural_tools::{get_cfg_attributes, get_doc_literals, is_using_frame_crate}; use quote::ToTokens; -use syn::{spanned::Spanned, token, Token}; +use syn::{spanned::Spanned, token, Token, TraitItemType}; /// List of additional token to be used for parsing. mod keyword { @@ -36,6 +36,7 @@ mod keyword { syn::custom_keyword!(no_default); syn::custom_keyword!(no_default_bounds); syn::custom_keyword!(constant); + syn::custom_keyword!(include_metadata); } #[derive(Default)] @@ -55,6 +56,8 @@ pub struct ConfigDef { pub has_instance: bool, /// Const associated type. pub consts_metadata: Vec, + /// Associated types metadata. + pub associated_types_metadata: Vec, /// Whether the trait has the associated type `Event`, note that those bounds are /// checked: /// * `IsType::RuntimeEvent` @@ -70,6 +73,26 @@ pub struct ConfigDef { pub default_sub_trait: Option, } +/// Input definition for an associated type in pallet config. +pub struct AssociatedTypeMetadataDef { + /// Name of the associated type. + pub ident: syn::Ident, + /// The doc associated. + pub doc: Vec, + /// The cfg associated. + pub cfg: Vec, +} + +impl From<&syn::TraitItemType> for AssociatedTypeMetadataDef { + fn from(trait_ty: &syn::TraitItemType) -> Self { + let ident = trait_ty.ident.clone(); + let doc = get_doc_literals(&trait_ty.attrs); + let cfg = get_cfg_attributes(&trait_ty.attrs); + + Self { ident, doc, cfg } + } +} + /// Input definition for a constant in pallet config. pub struct ConstMetadataDef { /// Name of the associated type. @@ -146,6 +169,8 @@ pub enum PalletAttrType { NoBounds(keyword::no_default_bounds), #[peek(keyword::constant, name = "constant")] Constant(keyword::constant), + #[peek(keyword::include_metadata, name = "include_metadata")] + IncludeMetadata(keyword::include_metadata), } /// Parsing for `#[pallet::X]` @@ -322,12 +347,32 @@ pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenS .collect() } +/// Check that the trait item requires the `TypeInfo` bound (or similar). +fn contains_type_info_bound(ty: &TraitItemType) -> bool { + const KNOWN_TYPE_INFO_BOUNDS: &[&str] = &[ + // Explicit TypeInfo trait. + "TypeInfo", + // Implicit known substrate traits that implement type info. + // Note: Aim to keep this list as small as possible. + "Parameter", + ]; + + ty.bounds.iter().any(|bound| { + let syn::TypeParamBound::Trait(bound) = bound else { return false }; + + KNOWN_TYPE_INFO_BOUNDS + .iter() + .any(|known| bound.path.segments.last().map_or(false, |last| last.ident == *known)) + }) +} + impl ConfigDef { pub fn try_from( frame_system: &syn::Path, index: usize, item: &mut syn::Item, enable_default: bool, + disable_associated_metadata: bool, ) -> syn::Result { let syn::Item::Trait(item) = item else { let msg = "Invalid pallet::config, expected trait definition"; @@ -368,6 +413,7 @@ impl ConfigDef { let mut has_event_type = false; let mut consts_metadata = vec![]; + let mut associated_types_metadata = vec![]; let mut default_sub_trait = if enable_default { Some(DefaultTrait { items: Default::default(), @@ -383,6 +429,7 @@ impl ConfigDef { let mut already_no_default = false; let mut already_constant = false; let mut already_no_default_bounds = false; + let mut already_collected_associated_type = None; while let Ok(Some(pallet_attr)) = helper::take_first_item_pallet_attr::(trait_item) @@ -403,11 +450,29 @@ impl ConfigDef { trait_item.span(), "Invalid #[pallet::constant] in #[pallet::config], expected type item", )), + // Pallet developer has explicitly requested to include metadata for this associated type. + // + // They must provide a type item that implements `TypeInfo`. + (PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => { + if already_collected_associated_type.is_some() { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Duplicate #[pallet::include_metadata] attribute not allowed.", + )); + } + already_collected_associated_type = Some(pallet_attr._bracket.span.join()); + associated_types_metadata.push(AssociatedTypeMetadataDef::from(AssociatedTypeMetadataDef::from(typ))); + } + (PalletAttrType::IncludeMetadata(_), _) => + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Invalid #[pallet::include_metadata] in #[pallet::config], expected type item", + )), (PalletAttrType::NoDefault(_), _) => { if !enable_default { return Err(syn::Error::new( pallet_attr._bracket.span.join(), - "`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \ + "`#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` \ has been specified" )); } @@ -439,6 +504,47 @@ impl ConfigDef { } } + if let Some(span) = already_collected_associated_type { + // Events and constants are already propagated to the metadata + if is_event { + return Err(syn::Error::new( + span, + "Invalid #[pallet::include_metadata] for `type RuntimeEvent`. \ + The associated type `RuntimeEvent` is already collected in the metadata.", + )) + } + + if already_constant { + return Err(syn::Error::new( + span, + "Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. \ + Pallet constant already collect the metadata for the type.", + )) + } + + if let syn::TraitItem::Type(ref ty) = trait_item { + if !contains_type_info_bound(ty) { + let msg = format!( + "Invalid #[pallet::include_metadata] in #[pallet::config], collected type `{}` \ + does not implement `TypeInfo` or `Parameter`", + ty.ident, + ); + return Err(syn::Error::new(span, msg)); + } + } + } else { + // Metadata of associated types is collected by default, if the associated type + // implements `TypeInfo`, or a similar trait that requires the `TypeInfo` bound. + if !disable_associated_metadata && !is_event && !already_constant { + if let syn::TraitItem::Type(ref ty) = trait_item { + // Collect the metadata of the associated type if it implements `TypeInfo`. + if contains_type_info_bound(ty) { + associated_types_metadata.push(AssociatedTypeMetadataDef::from(ty)); + } + } + } + } + if !already_no_default && enable_default { default_sub_trait .as_mut() @@ -481,6 +587,7 @@ impl ConfigDef { index, has_instance, consts_metadata, + associated_types_metadata, has_event_type, where_clause, default_sub_trait, diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index b9c7afcab0f..5036f691690 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -108,12 +108,13 @@ impl Def { let pallet_attr: Option = helper::take_first_item_pallet_attr(item)?; match pallet_attr { - Some(PalletAttr::Config(_, with_default)) if config.is_none() => + Some(PalletAttr::Config{ with_default, without_automatic_metadata, ..}) if config.is_none() => config = Some(config::ConfigDef::try_from( &frame_system, index, item, with_default, + without_automatic_metadata, )?), Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => { let p = pallet_struct::PalletStructDef::try_from(span, index, item)?; @@ -547,6 +548,7 @@ mod keyword { syn::custom_keyword!(event); syn::custom_keyword!(config); syn::custom_keyword!(with_default); + syn::custom_keyword!(without_automatic_metadata); syn::custom_keyword!(hooks); syn::custom_keyword!(inherent); syn::custom_keyword!(error); @@ -560,10 +562,37 @@ mod keyword { syn::custom_keyword!(composite_enum); } +/// The possible values for the `#[pallet::config]` attribute. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum ConfigValue { + /// `#[pallet::config(with_default)]` + WithDefault(keyword::with_default), + /// `#[pallet::config(without_automatic_metadata)]` + WithoutAutomaticMetadata(keyword::without_automatic_metadata), +} + +impl syn::parse::Parse for ConfigValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(keyword::with_default) { + input.parse().map(ConfigValue::WithDefault) + } else if lookahead.peek(keyword::without_automatic_metadata) { + input.parse().map(ConfigValue::WithoutAutomaticMetadata) + } else { + Err(lookahead.error()) + } + } +} + /// Parse attributes for item in pallet module /// syntax must be `pallet::` (e.g. `#[pallet::config]`) enum PalletAttr { - Config(proc_macro2::Span, bool), + Config { + span: proc_macro2::Span, + with_default: bool, + without_automatic_metadata: bool, + }, Pallet(proc_macro2::Span), Hooks(proc_macro2::Span), /// A `#[pallet::call]` with optional attributes to specialize the behaviour. @@ -625,7 +654,7 @@ enum PalletAttr { impl PalletAttr { fn span(&self) -> proc_macro2::Span { match self { - Self::Config(span, _) => *span, + Self::Config { span, .. } => *span, Self::Pallet(span) => *span, Self::Hooks(span) => *span, Self::Tasks(span) => *span, @@ -660,13 +689,49 @@ impl syn::parse::Parse for PalletAttr { let lookahead = content.lookahead1(); if lookahead.peek(keyword::config) { let span = content.parse::()?.span(); - let with_default = content.peek(syn::token::Paren); - if with_default { + if content.peek(syn::token::Paren) { let inside_config; + + // Parse (with_default, without_automatic_metadata) attributes. let _paren = syn::parenthesized!(inside_config in content); - inside_config.parse::()?; + + let fields: syn::punctuated::Punctuated = + inside_config.parse_terminated(ConfigValue::parse, syn::Token![,])?; + let config_values = fields.iter().collect::>(); + + let mut with_default = false; + let mut without_automatic_metadata = false; + for config in config_values { + match config { + ConfigValue::WithDefault(_) => { + if with_default { + return Err(syn::Error::new( + span, + "Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: with_default.", + )); + } + with_default = true; + }, + ConfigValue::WithoutAutomaticMetadata(_) => { + if without_automatic_metadata { + return Err(syn::Error::new( + span, + "Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: without_automatic_metadata.", + )); + } + without_automatic_metadata = true; + }, + } + } + + Ok(PalletAttr::Config { span, with_default, without_automatic_metadata }) + } else { + Ok(PalletAttr::Config { + span, + with_default: false, + without_automatic_metadata: false, + }) } - Ok(PalletAttr::Config(span, with_default)) } else if lookahead.peek(keyword::pallet) { Ok(PalletAttr::Pallet(content.parse::()?.span())) } else if lookahead.peek(keyword::hooks) { diff --git a/substrate/frame/support/procedural/tools/src/lib.rs b/substrate/frame/support/procedural/tools/src/lib.rs index ea53335a88f..d1d7efaab01 100644 --- a/substrate/frame/support/procedural/tools/src/lib.rs +++ b/substrate/frame/support/procedural/tools/src/lib.rs @@ -181,3 +181,17 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { }) .collect() } + +/// Return all cfg attributes literals found. +pub fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|attr| { + if let syn::Meta::List(meta) = &attr.meta { + meta.path.get_ident().filter(|ident| *ident == "cfg").map(|_| attr.clone()) + } else { + None + } + }) + .collect() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index d76073a97a3..1f2ec71b191 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -1565,6 +1565,53 @@ pub mod pallet_macros { /// * [`frame_support::derive_impl`]. /// * [`#[pallet::no_default]`](`no_default`) /// * [`#[pallet::no_default_bounds]`](`no_default_bounds`) + /// + /// ## Optional: `without_automatic_metadata` + /// + /// By default, the associated types of the `Config` trait that require the `TypeInfo` or + /// `Parameter` bounds are included in the metadata of the pallet. + /// + /// The optional `without_automatic_metadata` argument can be used to exclude these + /// associated types from the metadata collection. + /// + /// Furthermore, the `without_automatic_metadata` argument can be used in combination with + /// the [`#[pallet::include_metadata]`](`include_metadata`) attribute to selectively + /// include only certain associated types in the metadata collection. + /// + /// ``` + /// #[frame_support::pallet] + /// mod pallet { + /// # use frame_support::pallet_prelude::*; + /// # use frame_system::pallet_prelude::*; + /// # use core::fmt::Debug; + /// # use frame_support::traits::Contains; + /// # + /// # pub trait SomeMoreComplexBound {} + /// # + /// #[pallet::pallet] + /// pub struct Pallet(_); + /// + /// #[pallet::config(with_default, without_automatic_metadata)] // <- with_default and without_automatic_metadata are optional + /// pub trait Config: frame_system::Config { + /// /// The overarching event type. + /// #[pallet::no_default_bounds] // Default with bounds is not supported for RuntimeEvent + /// type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// + /// /// A simple type. + /// // Type that would have been included in metadata, but is now excluded. + /// type SimpleType: From + TypeInfo; + /// + /// // The `pallet::include_metadata` is used to selectively include this type in metadata. + /// #[pallet::include_metadata] + /// type SelectivelyInclude: From + TypeInfo; + /// } + /// + /// #[pallet::event] + /// pub enum Event { + /// SomeEvent(u16, u32), + /// } + /// } + /// ``` pub use frame_support_procedural::config; /// Allows defining an enum that gets composed as an aggregate enum by `construct_runtime`. @@ -1962,6 +2009,17 @@ pub mod pallet_macros { /// will be returned. pub use frame_support_procedural::event; + /// Selectively includes associated types in the metadata. + /// + /// The optional attribute allows you to selectively include associated types in the + /// metadata. This can be attached to trait items that implement `TypeInfo`. + /// + /// By default all collectable associated types are included in the metadata. + /// + /// This attribute can be used in combination with the + /// [`#[pallet::config(without_automatic_metadata)]`](`config`). + pub use frame_support_procedural::include_metadata; + /// Allows a pallet to declare a set of functions as a *dispatchable extrinsic*. /// /// In slightly simplified terms, this macro declares the set of "transactions" of a diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 59e36775d46..55b19ac1a65 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -711,6 +711,31 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0599]: the function or associated item `pallet_associated_types_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | diff --git a/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs b/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs new file mode 100644 index 00000000000..a2b916f54c5 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs @@ -0,0 +1,269 @@ +// 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. + +use frame_support::{derive_impl, traits::ConstU32}; +use scale_info::meta_type; +use sp_metadata_ir::PalletAssociatedTypeMetadataIR; + +pub type BlockNumber = u64; +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +/// Pallet without collectable associated types. +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +/// Pallet with default collectable associated types. +#[frame_support::pallet] +pub mod pallet2 { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + + // Associated type included by default, because it requires TypeInfo bound. + /// Nonce doc. + type Nonce: TypeInfo; + + // Associated type included by default, because it requires + // Parameter bound (indirect TypeInfo). + type AccountData: Parameter; + + // Associated type without metadata bounds, not included. + type NotIncluded: From; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +/// Pallet with implicit collectable associated types. +#[frame_support::pallet] +pub mod pallet3 { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + // Associated types are not collected by default. + #[pallet::config(without_automatic_metadata)] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + + // Explicitly include associated types. + #[pallet::include_metadata] + type Nonce: TypeInfo; + + type AccountData: Parameter; + + type NotIncluded: From; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +impl pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; +} + +impl pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; + type Nonce = u64; + type AccountData = u16; + type NotIncluded = u8; +} + +impl pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; + type Nonce = u64; + type AccountData = u16; + type NotIncluded = u8; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + Example: pallet, + DefaultInclusion: pallet2, + ExplicitInclusion: pallet3, + } +); + +#[test] +fn associated_types_metadata() { + fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + doc + } + } + + let ir = Runtime::metadata_ir(); + + // No associated types to collect. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "Example").unwrap(); + pretty_assertions::assert_eq!(pallet.associated_types, vec![]); + + // Collect by default types that implement TypeInfo or Parameter. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "DefaultInclusion").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![ + PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: maybe_docs(vec![" Nonce doc."]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountData", + ty: meta_type::(), + docs: vec![], + } + ] + ); + + // Explicitly include associated types. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "ExplicitInclusion").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: vec![], + }] + ); + + // Check system pallet. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "System").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![ + PalletAssociatedTypeMetadataIR { + name: "RuntimeCall", + ty: meta_type::(), + docs: maybe_docs(vec![" The aggregated `RuntimeCall` type."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: maybe_docs(vec![" This stores the number of previous transactions associated with a sender account."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Hash", + ty: meta_type::(), + docs: maybe_docs(vec![" The output of the `Hashing` function."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Hashing", + ty: meta_type::(), + docs: maybe_docs(vec![" The hashing system (algorithm) being used in the runtime (e.g. Blake2)."]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountId", + ty: meta_type::(), + docs: maybe_docs(vec![" The user account identifier type for the runtime."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Block", + ty: meta_type::(), + docs: maybe_docs(vec![ + " The Block type used by the runtime. This is used by `construct_runtime` to retrieve the", + " extrinsics or other block specific data as needed.", + ]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountData", + ty: meta_type::<()>(), + docs: maybe_docs(vec![ + " Data to be associated with an account (other than nonce/transaction counter, which this", + " pallet does regardless).", + ]), + }, + ] + ); +} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs new file mode 100644 index 00000000000..f58e11b0226 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs @@ -0,0 +1,39 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default, without_automatic_metadata, without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr new file mode 100644 index 00000000000..46326bde055 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr @@ -0,0 +1,5 @@ +error: Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: without_automatic_metadata. + --> tests/pallet_ui/config_duplicate_attr.rs:23:12 + | +23 | #[pallet::config(with_default, without_automatic_metadata, without_automatic_metadata)] + | ^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs new file mode 100644 index 00000000000..38c3870ba73 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs @@ -0,0 +1,42 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + + #[pallet::include_metadata] + type MyNonScaleTypeInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr new file mode 100644 index 00000000000..362e97e8bb9 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata] in #[pallet::config], collected type `MyNonScaleTypeInfo` does not implement `TypeInfo` or `Parameter` + --> tests/pallet_ui/config_metadata_non_type_info.rs:28:4 + | +28 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs new file mode 100644 index 00000000000..5452479b76e --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs @@ -0,0 +1,40 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::include_metadata] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr new file mode 100644 index 00000000000..eb943158f38 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. Pallet constant already collect the metadata for the type. + --> tests/pallet_ui/config_metadata_on_constants.rs:26:10 + | +26 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs new file mode 100644 index 00000000000..d91f86771bf --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs @@ -0,0 +1,43 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::no_default_bounds] + #[pallet::include_metadata] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr new file mode 100644 index 00000000000..15132ccce04 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata] for `type RuntimeEvent`. The associated type `RuntimeEvent` is already collected in the metadata. + --> tests/pallet_ui/config_metadata_on_events.rs:26:4 + | +26 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr b/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr index e8df28a3046..1b066bbe9fb 100644 --- a/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr @@ -1,4 +1,4 @@ -error: `#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` has been specified +error: `#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` has been specified --> tests/pallet_ui/no_default_but_missing_with_default.rs:26:4 | 26 | #[pallet::no_default] diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs b/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs new file mode 100644 index 00000000000..c016c52181c --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs @@ -0,0 +1,32 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config(with_default, without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs b/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs new file mode 100644 index 00000000000..c9f5244d734 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs @@ -0,0 +1,32 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config(without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +fn main() {} diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index 4ebe8c25a67..da4f5d7f371 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -133,6 +133,8 @@ pub struct PalletMetadataIR { pub constants: Vec>, /// Pallet error metadata. pub error: Option>, + /// Config's trait associated types. + pub associated_types: Vec>, /// Define the index of the pallet, this index will be used for the encoding of pallet event, /// call and origin variants. pub index: u8, @@ -153,6 +155,7 @@ impl IntoPortable for PalletMetadataIR { event: self.event.map(|event| event.into_portable(registry)), constants: registry.map_into_portable(self.constants), error: self.error.map(|error| error.into_portable(registry)), + associated_types: registry.map_into_portable(self.associated_types), index: self.index, docs: registry.map_into_portable(self.docs), deprecation_info: self.deprecation_info.into_portable(registry), @@ -197,6 +200,29 @@ impl IntoPortable for ExtrinsicMetadataIR { } } +/// Metadata of a pallet's associated type. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletAssociatedTypeMetadataIR { + /// The name of the associated type. + pub name: T::String, + /// The type of the associated type. + pub ty: T::Type, + /// The documentation of the associated type. + pub docs: Vec, +} + +impl IntoPortable for PalletAssociatedTypeMetadataIR { + type Output = PalletAssociatedTypeMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletAssociatedTypeMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + docs: registry.map_into_portable(self.docs), + } + } +} + /// Metadata of an extrinsic's signed extension. #[derive(Clone, PartialEq, Eq, Encode, Debug)] pub struct SignedExtensionMetadataIR { -- GitLab From 9d78c51c4a4c5abc7a7bd86aa774873b07c71a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20R=2E=20Bald=C3=A9?= Date: Wed, 16 Oct 2024 14:29:25 +0100 Subject: [PATCH 110/127] Refactor staking pallet benchmarks to `v2` (#6025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR migrates the staking pallet's benchmarks to the `v2` of pallet benchmarking tooling provided by [`frame_benchmarking`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/benchmarking). ## Integration N/A ## Review Notes The PR is straightforward, as were #1676 , #1838 and #1868. --------- Co-authored-by: Dónal Murray Co-authored-by: Shawn Tabrizi --- prdoc/pr_6025.prdoc | 13 + substrate/frame/staking/src/benchmarking.rs | 631 ++++++++++++-------- 2 files changed, 403 insertions(+), 241 deletions(-) create mode 100644 prdoc/pr_6025.prdoc diff --git a/prdoc/pr_6025.prdoc b/prdoc/pr_6025.prdoc new file mode 100644 index 00000000000..64072c0ae63 --- /dev/null +++ b/prdoc/pr_6025.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: Refactor staking pallet benchmarks to `v2` + +doc: + - audience: Runtime Dev + description: | + Update benchmarks in staking pallet to the second version of the `frame_benchmarking` runtime benchmarking framework. + +crates: + - name: pallet-staking + bump: patch \ No newline at end of file diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index a25085a1803..96bd3860542 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -34,8 +34,8 @@ use sp_runtime::{ }; use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex}; -pub use frame_benchmarking::v1::{ - account, benchmarks, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, +pub use frame_benchmarking::{ + impl_benchmark_test_suite, v2::*, whitelist_account, whitelisted_caller, BenchmarkError, }; use frame_system::RawOrigin; @@ -219,19 +219,26 @@ impl ListScenario { const USER_SEED: u32 = 999666; -benchmarks! { - bond { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn bond() { let stash = create_funded_user::("stash", USER_SEED, 100); let reward_destination = RewardDestination::Staked; let amount = asset::existential_deposit::() * 10u32.into(); whitelist_account!(stash); - }: _(RawOrigin::Signed(stash.clone()), amount, reward_destination) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(stash.clone()), amount, reward_destination); + assert!(Bonded::::contains_key(stash.clone())); assert!(Ledger::::contains_key(stash)); } - bond_extra { + #[benchmark] + fn bond_extra() -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -246,25 +253,29 @@ benchmarks! { let stash = scenario.origin_stash1.clone(); let controller = scenario.origin_controller1; - let original_bonded: BalanceOf - = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; + let original_bonded: BalanceOf = Ledger::::get(&controller) + .map(|l| l.active) + .ok_or("ledger not created after")?; let _ = asset::mint_existing::(&stash, max_additional).unwrap(); whitelist_account!(stash); - }: _(RawOrigin::Signed(stash), max_additional) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(stash), max_additional); + let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; let new_bonded: BalanceOf = ledger.active; assert!(original_bonded < new_bonded); + + Ok(()) } - unbond { + #[benchmark] + fn unbond() -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); - // setup the worst case list scenario. - let total_issuance = asset::total_issuance::(); // the weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) @@ -272,24 +283,29 @@ benchmarks! { .unwrap(); let scenario = ListScenario::::new(origin_weight, false)?; - let stash = scenario.origin_stash1.clone(); let controller = scenario.origin_controller1.clone(); let amount = origin_weight - scenario.dest_weight; let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), amount) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller.clone()), amount); + let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; let new_bonded: BalanceOf = ledger.active; assert!(original_bonded > new_bonded); + + Ok(()) } + #[benchmark] // Withdraw only updates the ledger - withdraw_unbonded_update { + fn withdraw_unbonded_update( // Slashing Spans - let s in 0 .. MAX_SPANS; + s: Linear<0, MAX_SPANS>, + ) -> Result<(), BenchmarkError> { let (stash, controller) = create_stash_controller::(0, 100, RewardDestination::Staked)?; add_slashing_spans::(&stash, s); let amount = asset::existential_deposit::() * 5u32.into(); // Half of total @@ -298,17 +314,23 @@ benchmarks! { let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_total: BalanceOf = ledger.total; whitelist_account!(controller); - }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) - verify { + + #[extrinsic_call] + withdraw_unbonded(RawOrigin::Signed(controller.clone()), s); + let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; let new_total: BalanceOf = ledger.total; assert!(original_total > new_total); + + Ok(()) } + #[benchmark] // Worst case scenario, everything is removed after the bonding duration - withdraw_unbonded_kill { + fn withdraw_unbonded_kill( // Slashing Spans - let s in 0 .. MAX_SPANS; + s: Linear<0, MAX_SPANS>, + ) -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -329,13 +351,18 @@ benchmarks! { CurrentEra::::put(EraIndex::max_value()); whitelist_account!(controller); - }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) - verify { + + #[extrinsic_call] + withdraw_unbonded(RawOrigin::Signed(controller.clone()), s); + assert!(!Ledger::::contains_key(controller)); assert!(!T::VoterList::contains(&stash)); + + Ok(()) } - validate { + #[benchmark] + fn validate() -> Result<(), BenchmarkError> { let (stash, controller) = create_stash_controller::( MaxNominationsOf::::get() - 1, 100, @@ -346,22 +373,28 @@ benchmarks! { let prefs = ValidatorPrefs::default(); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), prefs) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller), prefs); + assert!(Validators::::contains_key(&stash)); assert!(T::VoterList::contains(&stash)); + + Ok(()) } - kick { + #[benchmark] + fn kick( // scenario: we want to kick `k` nominators from nominating us (we are a validator). // we'll assume that `k` is under 128 for the purposes of determining the slope. - // each nominator should have `T::MaxNominations::get()` validators nominated, and our validator - // should be somewhere in there. - let k in 1 .. 128; - + // each nominator should have `T::MaxNominations::get()` validators nominated, and our + // validator should be somewhere in there. + k: Linear<1, 128>, + ) -> Result<(), BenchmarkError> { // these are the other validators; there are `T::MaxNominations::get() - 1` of them, so // there are a total of `T::MaxNominations::get()` validators in the system. - let rest_of_validators = create_validators_with_seed::(MaxNominationsOf::::get() - 1, 100, 415)?; + let rest_of_validators = + create_validators_with_seed::(MaxNominationsOf::::get() - 1, 100, 415)?; // this is the validator that will be kicking. let (stash, controller) = create_stash_controller::( @@ -377,7 +410,7 @@ benchmarks! { // we now create the nominators. there will be `k` of them; each will nominate all // validators. we will then kick each of the `k` nominators from the main validator. let mut nominator_stashes = Vec::with_capacity(k as usize); - for i in 0 .. k { + for i in 0..k { // create a nominator stash. let (n_stash, n_controller) = create_stash_controller::( MaxNominationsOf::::get() + i, @@ -402,49 +435,60 @@ benchmarks! { } // we need the unlookuped version of the nominator stash for the kick. - let kicks = nominator_stashes.iter() + let kicks = nominator_stashes + .iter() .map(|n| T::Lookup::unlookup(n.clone())) .collect::>(); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), kicks) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller), kicks); + // all nominators now should *not* be nominating our validator... for n in nominator_stashes.iter() { assert!(!Nominators::::get(n).unwrap().targets.contains(&stash)); } + + Ok(()) } + #[benchmark] // Worst case scenario, T::MaxNominations::get() - nominate { - let n in 1 .. MaxNominationsOf::::get(); - + fn nominate(n: Linear<1, { MaxNominationsOf::::get() }>) -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); let origin_weight = MinNominatorBond::::get().max(asset::existential_deposit::()); - // setup a worst case list scenario. Note we don't care about the destination position, because - // we are just doing an insert into the origin position. - let scenario = ListScenario::::new(origin_weight, true)?; + // setup a worst case list scenario. Note we don't care about the destination position, + // because we are just doing an insert into the origin position. + ListScenario::::new(origin_weight, true)?; let (stash, controller) = create_stash_controller_with_balance::( - SEED + MaxNominationsOf::::get() + 1, // make sure the account does not conflict with others + SEED + MaxNominationsOf::::get() + 1, /* make sure the account does not conflict + * with others */ origin_weight, RewardDestination::Staked, - ).unwrap(); + ) + .unwrap(); assert!(!Nominators::::contains_key(&stash)); assert!(!T::VoterList::contains(&stash)); let validators = create_validators::(n, 100).unwrap(); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), validators) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller), validators); + assert!(Nominators::::contains_key(&stash)); - assert!(T::VoterList::contains(&stash)) + assert!(T::VoterList::contains(&stash)); + + Ok(()) } - chill { + #[benchmark] + fn chill() -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -458,97 +502,138 @@ benchmarks! { assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller)) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller)); + assert!(!T::VoterList::contains(&stash)); + + Ok(()) } - set_payee { - let (stash, controller) = create_stash_controller::(USER_SEED, 100, RewardDestination::Staked)?; + #[benchmark] + fn set_payee() -> Result<(), BenchmarkError> { + let (stash, controller) = + create_stash_controller::(USER_SEED, 100, RewardDestination::Staked)?; assert_eq!(Payee::::get(&stash), Some(RewardDestination::Staked)); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), RewardDestination::Account(controller.clone())) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller.clone()), RewardDestination::Account(controller.clone())); + assert_eq!(Payee::::get(&stash), Some(RewardDestination::Account(controller))); + + Ok(()) } - update_payee { - let (stash, controller) = create_stash_controller::(USER_SEED, 100, RewardDestination::Staked)?; + #[benchmark] + fn update_payee() -> Result<(), BenchmarkError> { + let (stash, controller) = + create_stash_controller::(USER_SEED, 100, RewardDestination::Staked)?; Payee::::insert(&stash, { #[allow(deprecated)] RewardDestination::Controller }); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), controller.clone()) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller.clone()), controller.clone()); + assert_eq!(Payee::::get(&stash), Some(RewardDestination::Account(controller))); + + Ok(()) } - set_controller { - let (stash, ctlr) = create_unique_stash_controller::(9000, 100, RewardDestination::Staked, false)?; + #[benchmark] + fn set_controller() -> Result<(), BenchmarkError> { + let (stash, ctlr) = + create_unique_stash_controller::(9000, 100, RewardDestination::Staked, false)?; // ensure `ctlr` is the currently stored controller. assert!(!Ledger::::contains_key(&stash)); assert!(Ledger::::contains_key(&ctlr)); assert_eq!(Bonded::::get(&stash), Some(ctlr.clone())); whitelist_account!(stash); - }: _(RawOrigin::Signed(stash.clone())) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(stash.clone())); + assert!(Ledger::::contains_key(&stash)); + + Ok(()) } - set_validator_count { + #[benchmark] + fn set_validator_count() { let validator_count = MaxValidators::::get(); - }: _(RawOrigin::Root, validator_count) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, validator_count); + assert_eq!(ValidatorCount::::get(), validator_count); } - force_no_eras {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceNone); } + #[benchmark] + fn force_no_eras() { + #[extrinsic_call] + _(RawOrigin::Root); - force_new_era {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceNew); } + assert_eq!(ForceEra::::get(), Forcing::ForceNone); + } - force_new_era_always {}: _(RawOrigin::Root) - verify { assert_eq!(ForceEra::::get(), Forcing::ForceAlways); } + #[benchmark] + fn force_new_era() { + #[extrinsic_call] + _(RawOrigin::Root); + + assert_eq!(ForceEra::::get(), Forcing::ForceNew); + } + #[benchmark] + fn force_new_era_always() { + #[extrinsic_call] + _(RawOrigin::Root); + + assert_eq!(ForceEra::::get(), Forcing::ForceAlways); + } + + #[benchmark] // Worst case scenario, the list of invulnerables is very long. - set_invulnerables { - let v in 0 .. MaxValidators::::get(); + fn set_invulnerables(v: Linear<0, { MaxValidators::::get() }>) { let mut invulnerables = Vec::new(); - for i in 0 .. v { + for i in 0..v { invulnerables.push(account("invulnerable", i, SEED)); } - }: _(RawOrigin::Root, invulnerables) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, invulnerables); + assert_eq!(Invulnerables::::get().len(), v as usize); } - deprecate_controller_batch { + #[benchmark] + fn deprecate_controller_batch( // We pass a dynamic number of controllers to the benchmark, up to // `MaxControllersInDeprecationBatch`. - let i in 0 .. T::MaxControllersInDeprecationBatch::get(); - + u: Linear<0, { T::MaxControllersInDeprecationBatch::get() }>, + ) -> Result<(), BenchmarkError> { let mut controllers: Vec<_> = vec![]; let mut stashes: Vec<_> = vec![]; - for n in 0..i as u32 { - let (stash, controller) = create_unique_stash_controller::( - n, - 100, - RewardDestination::Staked, - false - )?; + for i in 0..u as u32 { + let (stash, controller) = + create_unique_stash_controller::(i, 100, RewardDestination::Staked, false)?; controllers.push(controller); stashes.push(stash); } let bounded_controllers: BoundedVec<_, T::MaxControllersInDeprecationBatch> = BoundedVec::try_from(controllers.clone()).unwrap(); - }: _(RawOrigin::Root, bounded_controllers) - verify { - for n in 0..i as u32 { - let stash = &stashes[n as usize]; - let controller = &controllers[n as usize]; + + #[extrinsic_call] + _(RawOrigin::Root, bounded_controllers); + + for i in 0..u as u32 { + let stash = &stashes[i as usize]; + let controller = &controllers[i as usize]; // Ledger no longer keyed by controller. assert_eq!(Ledger::::get(controller), None); // Bonded now maps to the stash. @@ -556,11 +641,15 @@ benchmarks! { // Ledger is now keyed by stash. assert_eq!(Ledger::::get(stash).unwrap().stash, *stash); } + + Ok(()) } - force_unstake { + #[benchmark] + fn force_unstake( // Slashing Spans - let s in 0 .. MAX_SPANS; + s: Linear<0, MAX_SPANS>, + ) -> Result<(), BenchmarkError> { // Clean up any existing state. clear_validators_and_nominators::(); @@ -574,30 +663,38 @@ benchmarks! { assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); - }: _(RawOrigin::Root, stash.clone(), s) - verify { + #[extrinsic_call] + _(RawOrigin::Root, stash.clone(), s); + assert!(!Ledger::::contains_key(&controller)); assert!(!T::VoterList::contains(&stash)); + + Ok(()) } - cancel_deferred_slash { - let s in 1 .. MAX_SLASHES; + #[benchmark] + fn cancel_deferred_slash(s: Linear<1, MAX_SLASHES>) { let mut unapplied_slashes = Vec::new(); let era = EraIndex::one(); let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - for _ in 0 .. MAX_SLASHES { - unapplied_slashes.push(UnappliedSlash::>::default_from(dummy())); + for _ in 0..MAX_SLASHES { + unapplied_slashes + .push(UnappliedSlash::>::default_from(dummy())); } UnappliedSlashes::::insert(era, &unapplied_slashes); - let slash_indices: Vec = (0 .. s).collect(); - }: _(RawOrigin::Root, era, slash_indices) - verify { + let slash_indices: Vec = (0..s).collect(); + + #[extrinsic_call] + _(RawOrigin::Root, era, slash_indices); + assert_eq!(UnappliedSlashes::::get(&era).len(), (MAX_SLASHES - s) as usize); } - payout_stakers_alive_staked { - let n in 0 .. T::MaxExposurePageSize::get() as u32; + #[benchmark] + fn payout_stakers_alive_staked( + n: Linear<0, { T::MaxExposurePageSize::get() as u32 }>, + ) -> Result<(), BenchmarkError> { let (validator, nominators) = create_validator_with_nominators::( n, T::MaxExposurePageSize::get() as u32, @@ -608,7 +705,11 @@ benchmarks! { let current_era = CurrentEra::::get().unwrap(); // set the commission for this particular era as well. - >::insert(current_era, validator.clone(), >::validators(&validator)); + >::insert( + current_era, + validator.clone(), + >::validators(&validator), + ); let caller = whitelisted_caller(); let balance_before = asset::stakeable_balance::(&validator); @@ -617,25 +718,29 @@ benchmarks! { let balance = asset::stakeable_balance::(stash); nominator_balances_before.push(balance); } - }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) - verify { + + #[extrinsic_call] + payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era); + let balance_after = asset::stakeable_balance::(&validator); ensure!( balance_before < balance_after, "Balance of validator stash should have increased after payout.", ); - for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) { + for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) + { let balance_after = asset::stakeable_balance::(stash); ensure!( balance_before < &balance_after, "Balance of nominator stash should have increased after payout.", ); } - } - rebond { - let l in 1 .. T::MaxUnlockingChunks::get() as u32; + Ok(()) + } + #[benchmark] + fn rebond(l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>) -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -658,31 +763,31 @@ benchmarks! { // so the sum of unlocking chunks puts voter into the dest bag. assert!(value * l.into() + origin_weight > origin_weight); assert!(value * l.into() + origin_weight <= dest_weight); - let unlock_chunk = UnlockChunk::> { - value, - era: EraIndex::zero(), - }; + let unlock_chunk = UnlockChunk::> { value, era: EraIndex::zero() }; - let stash = scenario.origin_stash1.clone(); let controller = scenario.origin_controller1; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); - for _ in 0 .. l { + for _ in 0..l { staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap() } Ledger::::insert(controller.clone(), staking_ledger.clone()); let original_bonded: BalanceOf = staking_ledger.active; whitelist_account!(controller); - }: _(RawOrigin::Signed(controller.clone()), rebond_amount) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller.clone()), rebond_amount); + let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; let new_bonded: BalanceOf = ledger.active; assert!(original_bonded < new_bonded); + + Ok(()) } - reap_stash { - let s in 1 .. MAX_SPANS; + #[benchmark] + fn reap_stash(s: Linear<1, MAX_SPANS>) -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -695,26 +800,26 @@ benchmarks! { let stash = scenario.origin_stash1; add_slashing_spans::(&stash, s); - let l = StakingLedger::::new( - stash.clone(), - asset::existential_deposit::() - One::one(), - ); + let l = + StakingLedger::::new(stash.clone(), asset::existential_deposit::() - One::one()); Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); - }: _(RawOrigin::Signed(controller), stash.clone(), s) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(controller), stash.clone(), s); + assert!(!Bonded::::contains_key(&stash)); assert!(!T::VoterList::contains(&stash)); - } - new_era { - let v in 1 .. 10; - let n in 0 .. 100; + Ok(()) + } + #[benchmark] + fn new_era(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> { create_validators_with_nominators_for_era::( v, n, @@ -723,16 +828,21 @@ benchmarks! { None, )?; let session_index = SessionIndex::one(); - }: { - let validators = Staking::::try_trigger_new_era(session_index, true) - .ok_or("`new_era` failed")?; + + let validators; + #[block] + { + validators = + Staking::::try_trigger_new_era(session_index, true).ok_or("`new_era` failed")?; + } + assert!(validators.len() == v as usize); + + Ok(()) } - #[extra] - payout_all { - let v in 1 .. 10; - let n in 0 .. 100; + #[benchmark(extra)] + fn payout_all(v: Linear<1, 10>, n: Linear<0, 100>) -> Result<(), BenchmarkError> { create_validators_with_nominators_for_era::( v, n, @@ -769,94 +879,135 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); let origin = RawOrigin::Signed(caller); - let calls: Vec<_> = payout_calls_arg.iter().map(|arg| - Call::::payout_stakers_by_page { validator_stash: arg.0.clone(), era: arg.1, page: 0 }.encode() - ).collect(); - }: { - for call in calls { - as Decode>::decode(&mut &*call) - .expect("call is encoded above, encoding must be correct") - .dispatch_bypass_filter(origin.clone().into())?; + let calls: Vec<_> = payout_calls_arg + .iter() + .map(|arg| { + Call::::payout_stakers_by_page { + validator_stash: arg.0.clone(), + era: arg.1, + page: 0, + } + .encode() + }) + .collect(); + + #[block] + { + for call in calls { + as Decode>::decode(&mut &*call) + .expect("call is encoded above, encoding must be correct") + .dispatch_bypass_filter(origin.clone().into())?; + } } + + Ok(()) } - #[extra] - do_slash { - let l in 1 .. T::MaxUnlockingChunks::get() as u32; + #[benchmark(extra)] + fn do_slash( + l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>, + ) -> Result<(), BenchmarkError> { let (stash, controller) = create_stash_controller::(0, 100, RewardDestination::Staked)?; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); - let unlock_chunk = UnlockChunk::> { - value: 1u32.into(), - era: EraIndex::zero(), - }; - for _ in 0 .. l { + let unlock_chunk = + UnlockChunk::> { value: 1u32.into(), era: EraIndex::zero() }; + for _ in 0..l { staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); } Ledger::::insert(controller, staking_ledger); let slash_amount = asset::existential_deposit::() * 10u32.into(); let balance_before = asset::stakeable_balance::(&stash); - }: { - crate::slashing::do_slash::( - &stash, - slash_amount, - &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero(), - EraIndex::zero() - ); - } verify { + + #[block] + { + crate::slashing::do_slash::( + &stash, + slash_amount, + &mut BalanceOf::::zero(), + &mut NegativeImbalanceOf::::zero(), + EraIndex::zero(), + ); + } + let balance_after = asset::stakeable_balance::(&stash); assert!(balance_before > balance_after); + + Ok(()) } - get_npos_voters { + #[benchmark] + fn get_npos_voters( // number of validator intention. we will iterate all of them. - let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); - // number of nominator intention. we will iterate all of them. - let n in (MaxNominators::::get() / 2) .. MaxNominators::::get(); + v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, - let validators = create_validators_with_nominators_for_era::( - v, n, MaxNominationsOf::::get() as usize, false, None - )? - .into_iter() - .map(|v| T::Lookup::lookup(v).unwrap()) - .collect::>(); + // number of nominator intention. we will iterate all of them. + n: Linear<{ MaxNominators::::get() / 2 }, { MaxNominators::::get() }>, + ) -> Result<(), BenchmarkError> { + create_validators_with_nominators_for_era::( + v, + n, + MaxNominationsOf::::get() as usize, + false, + None, + )?; assert_eq!(Validators::::count(), v); assert_eq!(Nominators::::count(), n); let num_voters = (v + n) as usize; - }: { + // default bounds are unbounded. - let voters = >::get_npos_voters(DataProviderBounds::default()); + let voters; + #[block] + { + voters = >::get_npos_voters(DataProviderBounds::default()); + } + assert_eq!(voters.len(), num_voters); + + Ok(()) } - get_npos_targets { + #[benchmark] + fn get_npos_targets( // number of validator intention. - let v in (MaxValidators::::get() / 2) .. MaxValidators::::get(); + v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, + ) -> Result<(), BenchmarkError> { // number of nominator intention. let n = MaxNominators::::get(); - let _ = create_validators_with_nominators_for_era::( - v, n, MaxNominationsOf::::get() as usize, false, None - )?; - }: { + #[block] + { + create_validators_with_nominators_for_era::( + v, + n, + MaxNominationsOf::::get() as usize, + false, + None, + )?; + } + // default bounds are unbounded. let targets = >::get_npos_targets(DataProviderBounds::default()); assert_eq!(targets.len() as u32, v); + + Ok(()) } - set_staking_configs_all_set { - }: set_staking_configs( - RawOrigin::Root, - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(BalanceOf::::max_value()), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX), - ConfigOp::Set(Percent::max_value()), - ConfigOp::Set(Perbill::max_value()), - ConfigOp::Set(Percent::max_value()) - ) verify { + #[benchmark] + fn set_staking_configs_all_set() { + #[extrinsic_call] + set_staking_configs( + RawOrigin::Root, + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(Percent::max_value()), + ConfigOp::Set(Perbill::max_value()), + ConfigOp::Set(Percent::max_value()), + ); + assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxNominatorsCount::::get(), Some(u32::MAX)); @@ -866,17 +1017,20 @@ benchmarks! { assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); } - set_staking_configs_all_remove { - }: set_staking_configs( - RawOrigin::Root, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove, - ConfigOp::Remove - ) verify { + #[benchmark] + fn set_staking_configs_all_remove() { + #[extrinsic_call] + set_staking_configs( + RawOrigin::Root, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ); + assert!(!MinNominatorBond::::exists()); assert!(!MinValidatorBond::::exists()); assert!(!MaxNominatorsCount::::exists()); @@ -886,7 +1040,8 @@ benchmarks! { assert!(!MaxStakedRewards::::exists()); } - chill_other { + #[benchmark] + fn chill_other() -> Result<(), BenchmarkError> { // clean up any existing state. clear_validators_and_nominators::(); @@ -895,7 +1050,6 @@ benchmarks! { // setup a worst case list scenario. Note that we don't care about the setup of the // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1; assert!(T::VoterList::contains(&stash)); @@ -911,18 +1065,22 @@ benchmarks! { )?; let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), stash.clone()) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), stash.clone()); + assert!(!T::VoterList::contains(&stash)); + + Ok(()) } - force_apply_min_commission { + #[benchmark] + fn force_apply_min_commission() -> Result<(), BenchmarkError> { // Clean up any existing state clear_validators_and_nominators::(); // Create a validator with a commission of 50% - let (stash, controller) = - create_stash_controller::(1, 1, RewardDestination::Staked)?; + let (stash, controller) = create_stash_controller::(1, 1, RewardDestination::Staked)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; @@ -936,29 +1094,41 @@ benchmarks! { // Set the min commission to 75% MinCommission::::set(Perbill::from_percent(75)); let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), stash.clone()) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), stash.clone()); + // The validators commission has been bumped to 75% assert_eq!( Validators::::get(&stash), ValidatorPrefs { commission: Perbill::from_percent(75), ..Default::default() } ); + + Ok(()) } - set_min_commission { + #[benchmark] + fn set_min_commission() { let min_commission = Perbill::max_value(); - }: _(RawOrigin::Root, min_commission) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, min_commission); + assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); } - restore_ledger { + #[benchmark] + fn restore_ledger() -> Result<(), BenchmarkError> { let (stash, controller) = create_stash_controller::(0, 100, RewardDestination::Staked)?; // corrupt ledger. Ledger::::remove(controller); - }: _(RawOrigin::Root, stash.clone(), None, None, None) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, stash.clone(), None, None, None); + assert_eq!(Staking::::inspect_bond_state(&stash), Ok(LedgerIntegrityState::Ok)); + + Ok(()) } impl_benchmark_test_suite!( @@ -1065,25 +1235,4 @@ mod tests { } }); } - - #[test] - fn test_payout_all() { - ExtBuilder::default().build_and_execute(|| { - let v = 10; - let n = 100; - - let selected_benchmark = SelectedBenchmark::payout_all; - let c = vec![ - (frame_benchmarking::BenchmarkParameter::v, v), - (frame_benchmarking::BenchmarkParameter::n, n), - ]; - - assert_ok!( - >::unit_test_instance( - &selected_benchmark, - &c, - ) - ); - }); - } } -- GitLab From e61005976c60e0094465e2fc65a7d7b1ffd1ef65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:35:18 +0000 Subject: [PATCH 111/127] Bump platforms from 3.0.2 to 3.4.1 (#5865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [platforms](https://github.com/rustsec/rustsec) from 3.0.2 to 3.4.1.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=platforms&package-manager=cargo&previous-version=3.0.2&new-version=3.4.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 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> Co-authored-by: Bastian Köcher --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a1c10b9570..eb52a1a4c1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13715,9 +13715,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" -version = "3.0.2" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" [[package]] name = "plotters" diff --git a/Cargo.toml b/Cargo.toml index 490b086eb24..40d3615fed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1016,7 +1016,7 @@ people-rococo-runtime = { path = "cumulus/parachains/runtimes/people/people-roco people-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend" } people-westend-runtime = { path = "cumulus/parachains/runtimes/people/people-westend" } pin-project = { version = "1.1.3" } -platforms = { version = "3.0" } +platforms = { version = "3.4" } polkadot-approval-distribution = { path = "polkadot/node/network/approval-distribution", default-features = false } polkadot-availability-bitfield-distribution = { path = "polkadot/node/network/bitfield-distribution", default-features = false } polkadot-availability-distribution = { path = "polkadot/node/network/availability-distribution", default-features = false } -- GitLab From 90c0a0cafd24110ea78d83b2a88d99f23d40ba13 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 16 Oct 2024 15:44:48 +0200 Subject: [PATCH 112/127] [pallet-revive] ensure the return data is reset if no frame was instantiated (#6045) Failed call frames do not produce new return data but still reset it. --------- Signed-off-by: xermicus Co-authored-by: GitHub Action --- prdoc/pr_6045.prdoc | 10 ++++ substrate/frame/revive/src/exec.rs | 82 +++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_6045.prdoc diff --git a/prdoc/pr_6045.prdoc b/prdoc/pr_6045.prdoc new file mode 100644 index 00000000000..d1b3fb4e77f --- /dev/null +++ b/prdoc/pr_6045.prdoc @@ -0,0 +1,10 @@ +title: '[pallet-revive] ensure the return data is reset if no frame was instantiated' + +doc: +- audience: + - Runtime Dev + description: Failed call frames do not produce new return data but still reset it. + +crates: +- name: pallet-revive + bump: patch diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index c6d2f205ae2..fffc3e4f483 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1331,14 +1331,18 @@ where // is caught by it. self.top_frame_mut().allows_reentry = allows_reentry; - let dest = T::AddressMapper::to_account_id(dest); - let value = value.try_into().map_err(|_| Error::::BalanceConversionFailed)?; + // We reset the return data now, so it is cleared out even if no new frame was executed. + // This is for example the case for balance transfers or when creating the frame fails. + *self.last_frame_output_mut() = Default::default(); let try_call = || { + let dest = T::AddressMapper::to_account_id(dest); if !self.allows_reentry(&dest) { return Err(>::ReentranceDenied.into()); } + let value = value.try_into().map_err(|_| Error::::BalanceConversionFailed)?; + // We ignore instantiate frames in our search for a cached contract. // Otherwise it would be possible to recursively call a contract from its own // constructor: We disallow calling not fully constructed contracts. @@ -1378,6 +1382,10 @@ where } fn delegate_call(&mut self, code_hash: H256, input_data: Vec) -> Result<(), ExecError> { + // We reset the return data now, so it is cleared out even if no new frame was executed. + // This is for example the case for unknown code hashes or creating the frame fails. + *self.last_frame_output_mut() = Default::default(); + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); @@ -1406,6 +1414,10 @@ where input_data: Vec, salt: Option<&[u8; 32]>, ) -> Result { + // We reset the return data now, so it is cleared out even if no new frame was executed. + // This is for example the case when creating the frame fails. + *self.last_frame_output_mut() = Default::default(); + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let sender = &self.top_frame().account_id; let executable = self.push_frame( @@ -4340,6 +4352,72 @@ mod tests { }); } + #[test] + fn last_frame_output_is_always_reset() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + let invalid_code_hash = H256::from_low_u64_le(u64::MAX); + let output_revert = || ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1] }; + + // A value of u256::MAX to fail the call on the first condition. + *ctx.ext.last_frame_output_mut() = output_revert(); + assert_eq!( + ctx.ext.call( + Weight::zero(), + U256::zero(), + &H160::zero(), + U256::max_value(), + vec![], + true, + false, + ), + Err(Error::::BalanceConversionFailed.into()) + ); + assert_eq!(ctx.ext.last_frame_output(), &Default::default()); + + // An unknown code hash to fail the delegate_call on the first condition. + *ctx.ext.last_frame_output_mut() = output_revert(); + assert_eq!( + ctx.ext.delegate_call(invalid_code_hash, Default::default()), + Err(Error::::CodeNotFound.into()) + ); + assert_eq!(ctx.ext.last_frame_output(), &Default::default()); + + // An unknown code hash to fail instantiation on the first condition. + *ctx.ext.last_frame_output_mut() = output_revert(); + assert_eq!( + ctx.ext.instantiate( + Weight::zero(), + U256::zero(), + invalid_code_hash, + U256::zero(), + vec![], + None, + ), + Err(Error::::CodeNotFound.into()) + ); + assert_eq!(ctx.ext.last_frame_output(), &Default::default()); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, code_bob); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + #[test] fn immutable_data_access_checks_work() { let dummy_ch = MockLoader::insert(Constructor, move |ctx, _| { -- GitLab From 4cd7e8650b480e371ee2077f50cc7fbfb13a5f6f Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:27:45 +0200 Subject: [PATCH 113/127] [ci] Small updates (#6085) PR adds timeouts to some jobs, fixes `node-bench-regression-guard` so it works on master. cc https://github.com/paritytech/ci_cd/issues/1063 --- .github/workflows/check-cargo-check-runtimes.yml | 1 - .github/workflows/check-labels.yml | 1 + .github/workflows/check-links.yml | 1 + .github/workflows/check-prdoc.yml | 1 + .github/workflows/check-semver.yml | 1 + .github/workflows/command-backport.yml | 6 +++--- .github/workflows/docs.yml | 2 ++ .github/workflows/fork-sync-action.yml | 16 ++++++++-------- .github/workflows/tests-misc.yml | 7 ++++++- 9 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-cargo-check-runtimes.yml b/.github/workflows/check-cargo-check-runtimes.yml index b49a2cbbfd3..376c34d1f25 100644 --- a/.github/workflows/check-cargo-check-runtimes.yml +++ b/.github/workflows/check-cargo-check-runtimes.yml @@ -110,7 +110,6 @@ jobs: - check-runtime-coretime - check-runtime-bridge-hubs - check-runtime-contracts - - check-runtime-starters - check-runtime-testing if: always() && !cancelled() steps: diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 6ec35840608..d5c91e7f55e 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -12,6 +12,7 @@ on: jobs: check-labels: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check labels env: diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 0ebd33d417a..baf8cd5b155 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -20,6 +20,7 @@ permissions: jobs: link-checker: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Restore lychee cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v3.3.2 (7. Sep 2023) diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index fc282477022..8af1dd8cef7 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -21,6 +21,7 @@ env: jobs: check-prdoc: runs-on: ubuntu-latest + timeout-minutes: 10 if: github.event.pull_request.number != '' steps: - name: Checkout repo diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 811ec4d5558..24050d9e098 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -17,6 +17,7 @@ jobs: uses: ./.github/workflows/reusable-preflight.yml check-semver: runs-on: ubuntu-latest + timeout-minutes: 90 needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 7f1d59bc0f6..8f23bcd75f0 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -4,7 +4,7 @@ on: # This trigger can be problematic, see: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ # In our case it is fine since we only run it on merged Pull Requests and do not execute any of the repo code itself. pull_request_target: - types: [ closed, labeled ] + types: [closed, labeled] permissions: contents: write # so it can comment @@ -66,7 +66,7 @@ jobs: with: script: | const pullNumbers = '${{ steps.backport.outputs.created_pull_numbers }}'.split(' '); - + for (const pullNumber of pullNumbers) { await github.rest.issues.addLabels({ issue_number: parseInt(pullNumber), @@ -84,7 +84,7 @@ jobs: script: | const pullNumbers = '${{ steps.backport.outputs.created_pull_numbers }}'.split(' '); const reviewer = '${{ github.event.pull_request.user.login }}'; - + for (const pullNumber of pullNumbers) { await github.pulls.createReviewRequest({ owner: context.repo.owner, diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 610f45fa386..a257c822959 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,6 +18,7 @@ jobs: test-doc: runs-on: ${{ needs.preflight.outputs.RUNNER }} + timeout-minutes: 60 needs: [preflight] container: image: ${{ needs.preflight.outputs.IMAGE }} @@ -29,6 +30,7 @@ jobs: build-rustdoc: runs-on: ${{ needs.preflight.outputs.RUNNER }} + timeout-minutes: 40 if: ${{ needs.preflight.outputs.changes_rust }} needs: [preflight] container: diff --git a/.github/workflows/fork-sync-action.yml b/.github/workflows/fork-sync-action.yml index 69e9e93bf54..065226764be 100644 --- a/.github/workflows/fork-sync-action.yml +++ b/.github/workflows/fork-sync-action.yml @@ -1,5 +1,5 @@ # This Workflow is not supposed to run in the paritytech/polkadot-sdk repo. -# This Workflow is supposed to run only in the forks of the repo, +# This Workflow is supposed to run only in the forks of the repo, # paritytech-release/polkadot-sdk specifically, # to automatically maintain the critical fork synced with the upstream. # This Workflow should be always disabled in the paritytech/polkadot-sdk repo. @@ -11,10 +11,10 @@ on: workflow_dispatch: jobs: - job_sync_branches: - uses: paritytech-release/sync-workflows/.github/workflows/sync-with-upstream.yml@latest - with: - fork_writer_app_id: ${{ vars.UPSTREAM_CONTENT_SYNC_APP_ID}} - fork_owner: ${{ vars.RELEASE_ORG}} - secrets: - fork_writer_app_key: ${{ secrets.UPSTREAM_CONTENT_SYNC_APP_KEY }} + job_sync_branches: + uses: paritytech-release/sync-workflows/.github/workflows/sync-with-upstream.yml@latest + with: + fork_writer_app_id: ${{ vars.UPSTREAM_CONTENT_SYNC_APP_ID}} + fork_owner: ${{ vars.RELEASE_ORG}} + secrets: + fork_writer_app_key: ${{ secrets.UPSTREAM_CONTENT_SYNC_APP_KEY }} diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index 4355cd0a9f8..b51f2c249d8 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -178,6 +178,11 @@ jobs: - name: script id: compare run: | + if [ "${{ github.ref_name }}" = "master" ]; then + echo -e "Exiting on master branch" + exit 0 + fi + docker run --rm \ -v $PWD/artifacts/master:/artifacts/master \ -v $PWD/artifacts/current:/artifacts/current \ @@ -299,7 +304,7 @@ jobs: # name: hfuzz-${{ github.sha }} cargo-check-each-crate: - timeout-minutes: 140 + timeout-minutes: 70 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} if: ${{ needs.preflight.outputs.changes_rust }} -- GitLab From 504edb1b20b835dd8f7a3f74648121b5487c8796 Mon Sep 17 00:00:00 2001 From: Miles Patterson Date: Wed, 16 Oct 2024 13:08:35 -0700 Subject: [PATCH 114/127] Fix for Issue 4762 (#4803) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Issue #4762 ](https://github.com/paritytech/polkadot-sdk/issues/4762) - Creates an enum for passing context of `service_queues` being called from within `on_initialize` and `on_idle` hooks. Uses a match statement inside of `service_queues` to continue using the same code, but NOT throw a `defensive` if being called within `on_idle` hook. - The issue requested to not throw the `defensive` if being called from within `on_idle` hook. - Created the `ServiceQueuesContext` enum to pass as an argument of `service_queues` when called within the `on_initialize` and `on_idle` hooks. A match statement was added inside of `service_queues` to continue to throw the defensive if called from `on_initialize` but NOT throw the defensive if called from `on_idle`. --------- Co-authored-by: gotnoshoeson Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- prdoc/pr_4803.prdoc | 14 +++ substrate/frame/message-queue/src/lib.rs | 109 ++++++++++++--------- substrate/frame/message-queue/src/tests.rs | 2 +- 3 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 prdoc/pr_4803.prdoc diff --git a/prdoc/pr_4803.prdoc b/prdoc/pr_4803.prdoc new file mode 100644 index 00000000000..0d2ad08d610 --- /dev/null +++ b/prdoc/pr_4803.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: Fix for issue #4762 + +doc: + - audience: Runtime Dev + description: | + When the status of the queue is on_initialize, throw a defensive message and return weight of 0, + however when status is on_idle, do not throw a defensive message, only return weight of 0 + +crates: + - name: pallet-message-queue + bump: patch diff --git a/substrate/frame/message-queue/src/lib.rs b/substrate/frame/message-queue/src/lib.rs index 48002acb147..31402f2a9d8 100644 --- a/substrate/frame/message-queue/src/lib.rs +++ b/substrate/frame/message-queue/src/lib.rs @@ -649,7 +649,7 @@ pub mod pallet { impl Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { if let Some(weight_limit) = T::ServiceWeight::get() { - Self::service_queues(weight_limit) + Self::service_queues_impl(weight_limit, ServiceQueuesContext::OnInitialize) } else { Weight::zero() } @@ -658,7 +658,10 @@ pub mod pallet { fn on_idle(_n: BlockNumberFor, remaining_weight: Weight) -> Weight { if let Some(weight_limit) = T::IdleMaxServiceWeight::get() { // Make use of the remaining weight to process enqueued messages. - Self::service_queues(weight_limit.min(remaining_weight)) + Self::service_queues_impl( + weight_limit.min(remaining_weight), + ServiceQueuesContext::OnIdle, + ) } else { Weight::zero() } @@ -777,6 +780,18 @@ enum MessageExecutionStatus { StackLimitReached, } +/// The context to pass to [`Pallet::service_queues_impl`] through on_idle and on_initialize hooks +/// We don't want to throw the defensive message if called from on_idle hook +#[derive(PartialEq)] +enum ServiceQueuesContext { + /// Context of on_idle hook. + OnIdle, + /// Context of on_initialize hook. + OnInitialize, + /// Context `service_queues` trait function. + ServiceQueues, +} + impl Pallet { /// Knit `origin` into the ready ring right at the end. /// @@ -1511,6 +1526,53 @@ impl Pallet { }, } } + + fn service_queues_impl(weight_limit: Weight, context: ServiceQueuesContext) -> Weight { + let mut weight = WeightMeter::with_limit(weight_limit); + + // Get the maximum weight that processing a single message may take: + let max_weight = Self::max_message_weight(weight_limit).unwrap_or_else(|| { + if matches!(context, ServiceQueuesContext::OnInitialize) { + defensive!("Not enough weight to service a single message."); + } + Weight::zero() + }); + + match with_service_mutex(|| { + let mut next = match Self::bump_service_head(&mut weight) { + Some(h) => h, + None => return weight.consumed(), + }; + // The last queue that did not make any progress. + // The loop aborts as soon as it arrives at this queue again without making any progress + // on other queues in between. + let mut last_no_progress = None; + + loop { + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, max_weight); + next = match n { + Some(n) => + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n + } else { + last_no_progress = None; + n + }, + None => break, + } + } + weight.consumed() + }) { + Err(()) => weight.consumed(), + Ok(w) => w, + } + } } /// Run a closure that errors on re-entrance. Meant to be used by anything that services queues. @@ -1580,48 +1642,7 @@ impl ServiceQueues for Pallet { type OverweightMessageAddress = (MessageOriginOf, PageIndex, T::Size); fn service_queues(weight_limit: Weight) -> Weight { - let mut weight = WeightMeter::with_limit(weight_limit); - - // Get the maximum weight that processing a single message may take: - let max_weight = Self::max_message_weight(weight_limit).unwrap_or_else(|| { - defensive!("Not enough weight to service a single message."); - Weight::zero() - }); - - match with_service_mutex(|| { - let mut next = match Self::bump_service_head(&mut weight) { - Some(h) => h, - None => return weight.consumed(), - }; - // The last queue that did not make any progress. - // The loop aborts as soon as it arrives at this queue again without making any progress - // on other queues in between. - let mut last_no_progress = None; - - loop { - let (progressed, n) = Self::service_queue(next.clone(), &mut weight, max_weight); - next = match n { - Some(n) => - if !progressed { - if last_no_progress == Some(n.clone()) { - break - } - if last_no_progress.is_none() { - last_no_progress = Some(next.clone()) - } - n - } else { - last_no_progress = None; - n - }, - None => break, - } - } - weight.consumed() - }) { - Err(()) => weight.consumed(), - Ok(w) => w, - } + Self::service_queues_impl(weight_limit, ServiceQueuesContext::ServiceQueues) } /// Execute a single overweight message. diff --git a/substrate/frame/message-queue/src/tests.rs b/substrate/frame/message-queue/src/tests.rs index fac135f135c..b75764b67be 100644 --- a/substrate/frame/message-queue/src/tests.rs +++ b/substrate/frame/message-queue/src/tests.rs @@ -279,7 +279,7 @@ fn service_queues_low_weight_defensive() { assert!(MessageQueue::do_integrity_test().is_err()); MessageQueue::enqueue_message(msg("weight=0"), Here); - MessageQueue::service_queues(104.into_weight()); + MessageQueue::service_queues_impl(104.into_weight(), ServiceQueuesContext::OnInitialize); }); } -- GitLab From d5b96e9e7f24adc1799f8e426c5cb69b4f2dbf8a Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 16 Oct 2024 18:48:19 -0300 Subject: [PATCH 115/127] bump `zombienet` version and inc. memory request in k8s (#6091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump zombienet version, include updated baseline resources request. Thx! Co-authored-by: Bastian Köcher --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 85a14d90da8..1589fccc972 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -8,7 +8,7 @@ RUST_LOG: "info,zombienet_orchestrator=debug" RUN_IN_CI: "1" KUBERNETES_CPU_REQUEST: "512m" - KUBERNETES_MEMORY_REQUEST: "512M" + KUBERNETES_MEMORY_REQUEST: "1Gi" timeout: 60m include: -- GitLab From 31dfc9f69f7c2ace923b36916622602de984be10 Mon Sep 17 00:00:00 2001 From: drewstone Date: Thu, 17 Oct 2024 08:44:01 -0400 Subject: [PATCH 116/127] Import vec to bridges/primitives/header-chain (#6031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description I'm unable to build a bridge library due to the errors below, so I am adding the explicit import to `sp_std::vec`. ## Integration Projects using it can update their dependency. We are using the branch `stable2407` (mixed them up in my branch). ## Errors ``` error: cannot find macro `vec` in this scope --> /Users/drew/.cargo/git/checkouts/polkadot-sdk-cff69157b985ed76/88c3250/bridges/primitives/header-chain/src/justification/verification/mod.rs:99:19 | 99 | let mut route = vec![]; | ^^^ | help: consider importing one of these items | 23 + use scale_info::prelude::vec; | 23 + use sp_std::vec; | error: cannot find macro `vec` in this scope --> /Users/drew/.cargo/git/checkouts/polkadot-sdk-cff69157b985ed76/88c3250/bridges/primitives/header-chain/src/justification/verification/optimizer.rs:135:36 | 135 | duplicate_votes_ancestries_idxs: vec![], | ^^^ | help: consider importing one of these items | 19 + use scale_info::prelude::vec; | 19 + use sp_std::vec; | error: cannot find macro `vec` in this scope --> /Users/drew/.cargo/git/checkouts/polkadot-sdk-cff69157b985ed76/88c3250/bridges/primitives/header-chain/src/justification/verification/optimizer.rs:134:21 | 134 | extra_precommits: vec![], | ^^^ | help: consider importing one of these items | 19 + use scale_info::prelude::vec; | 19 + use sp_std::vec; | error: cannot find macro `vec` in this scope --> /Users/drew/.cargo/git/checkouts/polkadot-sdk-cff69157b985ed76/88c3250/bridges/primitives/header-chain/src/justification/verification/equivocation.rs:89:27 | 89 | let mut equivocations = vec![]; | ^^^ | help: consider importing one of these items | 19 + use scale_info::prelude::vec; | 19 + use sp_std::vec; ``` --------- Co-authored-by: Bastian Köcher Co-authored-by: Serban Iorga Co-authored-by: command-bot <> Co-authored-by: Adrian Catangiu --- .../src/justification/verification/equivocation.rs | 2 ++ .../header-chain/src/justification/verification/mod.rs | 2 ++ .../src/justification/verification/optimizer.rs | 2 +- prdoc/pr_6031.prdoc | 10 ++++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6031.prdoc diff --git a/bridges/primitives/header-chain/src/justification/verification/equivocation.rs b/bridges/primitives/header-chain/src/justification/verification/equivocation.rs index fbad3012819..bfcd22f8ca6 100644 --- a/bridges/primitives/header-chain/src/justification/verification/equivocation.rs +++ b/bridges/primitives/header-chain/src/justification/verification/equivocation.rs @@ -34,6 +34,8 @@ use sp_runtime::traits::Header as HeaderT; use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, prelude::*, + vec, + vec::Vec, }; enum AuthorityVotes { diff --git a/bridges/primitives/header-chain/src/justification/verification/mod.rs b/bridges/primitives/header-chain/src/justification/verification/mod.rs index 9df3511e103..9941537eb09 100644 --- a/bridges/primitives/header-chain/src/justification/verification/mod.rs +++ b/bridges/primitives/header-chain/src/justification/verification/mod.rs @@ -35,6 +35,8 @@ use sp_std::{ btree_set::BTreeSet, }, prelude::*, + vec, + vec::Vec, }; type SignedPrecommit
= finality_grandpa::SignedPrecommit< diff --git a/bridges/primitives/header-chain/src/justification/verification/optimizer.rs b/bridges/primitives/header-chain/src/justification/verification/optimizer.rs index 3f1e6ab670c..5098b594db6 100644 --- a/bridges/primitives/header-chain/src/justification/verification/optimizer.rs +++ b/bridges/primitives/header-chain/src/justification/verification/optimizer.rs @@ -26,7 +26,7 @@ use crate::justification::verification::{ }; use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::Header as HeaderT; -use sp_std::{collections::btree_set::BTreeSet, prelude::*}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*, vec, vec::Vec}; // Verification callbacks for justification optimization. struct JustificationOptimizer { diff --git a/prdoc/pr_6031.prdoc b/prdoc/pr_6031.prdoc new file mode 100644 index 00000000000..702d0c12fa0 --- /dev/null +++ b/prdoc/pr_6031.prdoc @@ -0,0 +1,10 @@ +title: "Import vec to bridges/primitives/header-chain" + +doc: + - audience: Runtime Dev + description: | + Add the `vec` dependency to these files to resolve compiler errors. + +crates: + - name: bp-header-chain + bump: patch -- GitLab From 9714796ba9544f98c0361be466b9d993056aad50 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 17 Oct 2024 16:22:48 +0300 Subject: [PATCH 117/127] [AHs] Support registering assets on Asset Hubs over bridge (#5435) ## Changes Allows one Asset Hub on one side, to register assets on the other Asset Hub over the bridge. Rococo <> Ethereum test bridge will be dropped in favor of Westend <> Ethereum test bridge. This PR also changes emulated tests to simulate double bridging from Ethereum<>Westend<>Rococo. ## Tests - [x] e2e test: Westend Asset Hub can register Westend and Ethereum assets on Asset Hub Rococo - [x] e2e test: Rococo Asset Hub can register Rococo assets on Asset Hub Westend - [x] e2e test (existing): Users on Ethereum can register Ethereum assets on Asset Hub Westend - [x] e2e test: transfer Rococo assets from Rococo to Westend and back - [x] e2e test: transfer Westend assets from Westend to Rococo and back - [x] e2e test: transfer Ethereum assets (bridged in through Snowbridge) from Westend to Rococo and back --- .../bridges/bridge-hub-rococo/src/lib.rs | 11 +- .../src/tests/asset_transfers.rs | 198 ++++++++++-------- .../bridge-hub-rococo/src/tests/mod.rs | 42 ++-- .../src/tests/register_bridged_assets.rs | 107 ++++++++++ .../bridges/bridge-hub-westend/src/lib.rs | 8 +- .../src/tests/asset_transfers.rs | 194 ++++++++--------- .../bridge-hub-westend/src/tests/mod.rs | 9 +- .../src/tests/register_bridged_assets.rs | 131 ++++++++++++ .../assets/asset-hub-rococo/src/lib.rs | 1 + .../assets/asset-hub-rococo/src/xcm_config.rs | 18 +- .../assets/asset-hub-westend/src/lib.rs | 1 + .../asset-hub-westend/src/xcm_config.rs | 18 +- .../runtimes/assets/common/src/matching.rs | 31 ++- prdoc/pr_5435.prdoc | 16 ++ 14 files changed, 537 insertions(+), 248 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs create mode 100644 prdoc/pr_5435.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index a542de16de5..77e4c8183e6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -24,8 +24,7 @@ mod imports { pub use xcm::{ latest::ParentThen, prelude::{AccountId32 as AccountId32Junction, *}, - v4, - v4::NetworkId::Westend as WestendId, + v4::{self, NetworkId::Westend as WestendId}, }; pub use xcm_executor::traits::TransferType; @@ -38,17 +37,18 @@ mod imports { xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, + xcm_helpers::xcm_transact_paid_execution, ASSETS_PALLET_ID, USDT_ID, }; pub use parachains_common::AccountId; pub use rococo_westend_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ asset_hub_rococo_runtime::xcm_config as ahr_xcm_config, - genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED}, - AssetHubRococoParaPallet as AssetHubRococoPallet, + genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, }, asset_hub_westend_emulated_chain::{ - genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, + genesis::{AssetHubWestendAssetOwner, ED as ASSET_HUB_WESTEND_ED}, + AssetHubWestendParaPallet as AssetHubWestendPallet, }, bridge_hub_rococo_emulated_chain::{ genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoExistentialDeposit, @@ -80,6 +80,7 @@ mod imports { RococoRelayReceiver as RococoReceiver, RococoRelaySender as RococoSender, }; + pub const ASSET_ID: u32 = 1; pub const ASSET_MIN_BALANCE: u128 = 1000; } 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 f1e2a6dcca6..0e1cfdd82aa 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 @@ -113,14 +113,8 @@ fn send_assets_from_penpal_rococo_through_rococo_ah_to_westend_ah( } #[test] -/// Test transfer of ROC, USDT and wETH from AssetHub Rococo to AssetHub Westend. -/// -/// This mix of assets should cover the whole range: -/// - native assets: ROC, -/// - trust-based assets: USDT (exists only on Rococo, Westend gets it from Rococo over bridge), -/// - foreign asset / bridged asset (other bridge / Snowfork): wETH (bridged from Ethereum to Rococo -/// over Snowbridge, then bridged over to Westend through this bridge). -fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { +/// Test transfer of ROC from AssetHub Rococo to AssetHub Westend. +fn send_roc_from_asset_hub_rococo_to_asset_hub_westend() { let amount = ASSET_HUB_ROCOCO_ED * 1_000_000; let sender = AssetHubRococoSender::get(); let receiver = AssetHubWestendReceiver::get(); @@ -128,11 +122,8 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { let bridged_roc_at_asset_hub_westend = bridged_roc_at_ah_westend(); create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true); - set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend.clone()); + set_up_pool_with_wnd_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true); - //////////////////////////////////////////////////////////// - // Let's first send over just some ROCs as a simple example - //////////////////////////////////////////////////////////// let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( Westend, AssetHubWestend::para_id(), @@ -146,8 +137,7 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { // send ROCs, use them for fees send_assets_over_bridge(|| { let destination = asset_hub_westend_location(); - let assets: Assets = - (Location::try_from(roc_at_asset_hub_rococo.clone()).unwrap(), amount).into(); + let assets: Assets = (roc_at_asset_hub_rococo.clone(), amount).into(); let fee_idx = 0; assert_ok!(send_assets_from_asset_hub_rococo(destination, assets, fee_idx)); }); @@ -183,84 +173,18 @@ fn send_roc_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { assert!(receiver_rocs_after > receiver_rocs_before); // Reserve ROC balance is increased by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount); - - ///////////////////////////////////////////////////////////// - // Now let's send over USDTs + wETH (and pay fees with USDT) - ///////////////////////////////////////////////////////////// - - let usdt_at_asset_hub_rococo = usdt_at_ah_rococo(); - let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend(); - // wETH has same relative location on both Rococo and Westend AssetHubs - let bridged_weth_at_ah = weth_at_asset_hubs(); - - // mint USDT in sender's account (USDT already created in genesis) - AssetHubRococo::mint_asset( - ::RuntimeOrigin::signed(AssetHubRococoAssetOwner::get()), - USDT_ID, - sender.clone(), - amount * 2, - ); - // create wETH at src and dest and prefund sender's account - create_foreign_on_ah_rococo( - bridged_weth_at_ah.clone(), - true, - vec![(sender.clone(), amount * 2)], - ); - create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true); - create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), true); - set_up_pool_with_wnd_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone()); - - let receiver_usdts_before = - foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), &receiver); - let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah.clone(), &receiver); - - // send USDTs and wETHs - let assets: Assets = vec![ - (usdt_at_asset_hub_rococo.clone(), amount).into(), - (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount).into(), - ] - .into(); - // use USDT for fees - let fee: AssetId = usdt_at_asset_hub_rococo.into(); - - // use the more involved transfer extrinsic - let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { - assets: Wild(AllCounted(assets.len() as u32)), - beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), - }]); - assert_ok!(AssetHubRococo::execute_with(|| { - ::PolkadotXcm::transfer_assets_using_type_and_then( - ::RuntimeOrigin::signed(sender.into()), - bx!(asset_hub_westend_location().into()), - bx!(assets.into()), - bx!(TransferType::LocalReserve), - bx!(fee.into()), - bx!(TransferType::LocalReserve), - bx!(VersionedXcm::from(custom_xcm_on_dest)), - WeightLimit::Unlimited, - ) - })); - // verify hops (also advances the message through the hops) - assert_bridge_hub_rococo_message_accepted(true); - assert_bridge_hub_westend_message_received(); - AssetHubWestend::execute_with(|| { - AssetHubWestend::assert_xcmp_queue_success(None); - }); - - let receiver_usdts_after = - foreign_balance_on_ah_westend(bridged_usdt_at_asset_hub_westend, &receiver); - let receiver_weth_after = foreign_balance_on_ah_westend(bridged_weth_at_ah, &receiver); - - // Receiver's USDT balance is increased by almost `amount` (minus fees) - assert!(receiver_usdts_after > receiver_usdts_before); - assert!(receiver_usdts_after < receiver_usdts_before + amount); - // Receiver's wETH balance is increased by sent amount - assert_eq!(receiver_weth_after, receiver_weth_before + amount); } #[test] -/// Send bridged WNDs "back" from AssetHub Rococo to AssetHub Westend. -fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { +/// Send bridged assets "back" from AssetHub Rococo to AssetHub Westend. +/// +/// This mix of assets should cover the whole range: +/// - bridged native assets: ROC, +/// - bridged trust-based assets: USDT (exists only on Westend, Rococo gets it from Westend over +/// bridge), +/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from +/// Ethereum to Westend over Snowbridge, then bridged over to Rococo through this bridge). +fn send_back_wnds_usdt_and_weth_from_asset_hub_rococo_to_asset_hub_westend() { let prefund_amount = 10_000_000_000_000u128; let amount_to_send = ASSET_HUB_WESTEND_ED * 1_000; let sender = AssetHubRococoSender::get(); @@ -269,6 +193,10 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { let prefund_accounts = vec![(sender.clone(), prefund_amount)]; create_foreign_on_ah_rococo(wnd_at_asset_hub_rococo.clone(), true, prefund_accounts); + //////////////////////////////////////////////////////////// + // Let's first send back just some WNDs as a simple example + //////////////////////////////////////////////////////////// + // fund the AHR's SA on AHW with the WND tokens held in reserve let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( Rococo, @@ -317,7 +245,7 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { }); let sender_wnds_after = foreign_balance_on_ah_rococo(wnd_at_asset_hub_rococo, &sender); - let receiver_wnds_after = ::account_data_of(receiver).free; + let receiver_wnds_after = ::account_data_of(receiver.clone()).free; let wnds_in_reserve_on_ahw_after = ::account_data_of(sov_ahr_on_ahw).free; @@ -327,6 +255,96 @@ fn send_back_wnds_from_asset_hub_rococo_to_asset_hub_westend() { assert!(receiver_wnds_after > receiver_wnds_before); // Reserve balance is reduced by sent amount assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send); + + ////////////////////////////////////////////////////////////////// + // Now let's send back over USDTs + wETH (and pay fees with USDT) + ////////////////////////////////////////////////////////////////// + + // wETH has same relative location on both Westend and Rococo AssetHubs + let bridged_weth_at_ah = weth_at_asset_hubs(); + let bridged_usdt_at_asset_hub_rococo = bridged_usdt_at_ah_rococo(); + + // set up destination chain AH Westend: + // create a WND/USDT pool to be able to pay fees with USDT (USDT created in genesis) + set_up_pool_with_wnd_on_ah_westend(usdt_at_ah_westend(), false); + // create wETH on Westend (IRL it's already created by Snowbridge) + create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true); + // prefund AHR's sovereign account on AHW to be able to withdraw USDT and wETH from reserves + let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + AssetHubWestend::mint_asset( + ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), + USDT_ID, + sov_ahr_on_ahw.clone(), + amount_to_send * 2, + ); + AssetHubWestend::mint_foreign_asset( + ::RuntimeOrigin::signed(AssetHubWestend::account_id_of(ALICE)), + bridged_weth_at_ah.clone(), + sov_ahr_on_ahw, + amount_to_send * 2, + ); + + // set up source chain AH Rococo: + // create wETH and USDT foreign assets on Rococo and prefund sender's account + let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)]; + create_foreign_on_ah_rococo(bridged_weth_at_ah.clone(), true, prefund_accounts.clone()); + create_foreign_on_ah_rococo(bridged_usdt_at_asset_hub_rococo.clone(), true, prefund_accounts); + + // check balances before + let receiver_usdts_before = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) + }); + let receiver_weth_before = foreign_balance_on_ah_westend(bridged_weth_at_ah.clone(), &receiver); + + let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_rococo).unwrap().into(); + // send USDTs and wETHs + let assets: Assets = vec![ + (usdt_id.clone(), amount_to_send).into(), + (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount_to_send).into(), + ] + .into(); + // use USDT for fees + let fee = usdt_id; + + // use the more involved transfer extrinsic + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), + }]); + assert_ok!(AssetHubRococo::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender.into()), + bx!(asset_hub_westend_location().into()), + bx!(assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + // verify hops (also advances the message through the hops) + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); + AssetHubWestend::execute_with(|| { + AssetHubWestend::assert_xcmp_queue_success(None); + }); + + let receiver_usdts_after = AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + >::balance(USDT_ID, &receiver) + }); + let receiver_weth_after = foreign_balance_on_ah_westend(bridged_weth_at_ah, &receiver); + + // Receiver's USDT balance is increased by almost `amount_to_send` (minus fees) + assert!(receiver_usdts_after > receiver_usdts_before); + assert!(receiver_usdts_after < receiver_usdts_before + amount_to_send); + // Receiver's wETH balance is increased by `amount_to_send` + assert_eq!(receiver_weth_after, receiver_weth_before + amount_to_send); } #[test] 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 a989881fef0..767f74f6ad7 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 @@ -17,6 +17,7 @@ use crate::imports::*; mod asset_transfers; mod claim_assets; +mod register_bridged_assets; mod send_xcm; mod snowbridge; mod teleport; @@ -45,15 +46,15 @@ pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { } // USDT and wUSDT -pub(crate) fn usdt_at_ah_rococo() -> Location { +pub(crate) fn usdt_at_ah_westend() -> Location { Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) } -pub(crate) fn bridged_usdt_at_ah_westend() -> Location { +pub(crate) fn bridged_usdt_at_ah_rococo() -> Location { Location::new( 2, [ - GlobalConsensus(Rococo), - Parachain(AssetHubRococo::para_id().into()), + GlobalConsensus(Westend), + Parachain(AssetHubWestend::para_id().into()), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into()), ], @@ -100,23 +101,36 @@ pub(crate) fn foreign_balance_on_ah_westend(id: v4::Location, who: &AccountId) - } // set up pool -pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v4::Location) { +pub(crate) fn set_up_pool_with_wnd_on_ah_westend(asset: v4::Location, is_foreign: bool) { let wnd: v4::Location = v4::Parent.into(); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let owner = AssetHubWestendSender::get(); let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - assert_ok!(::ForeignAssets::mint( - signed_owner.clone(), - foreign_asset.clone().into(), - owner.clone().into(), - 3_000_000_000_000, - )); + if is_foreign { + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + } else { + let asset_id = match asset.interior.last() { + Some(v4::Junction::GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + assert_ok!(::Assets::mint( + signed_owner.clone(), + asset_id.into(), + owner.clone().into(), + 3_000_000_000_000, + )); + } assert_ok!(::AssetConversion::create_pool( signed_owner.clone(), Box::new(wnd.clone()), - Box::new(foreign_asset.clone()), + Box::new(asset.clone()), )); assert_expected_events!( AssetHubWestend, @@ -127,7 +141,7 @@ pub(crate) fn set_up_pool_with_wnd_on_ah_westend(foreign_asset: v4::Location) { assert_ok!(::AssetConversion::add_liquidity( signed_owner.clone(), Box::new(wnd), - Box::new(foreign_asset), + Box::new(asset), 1_000_000_000_000, 2_000_000_000_000, 1, @@ -149,7 +163,7 @@ pub(crate) fn send_assets_from_asset_hub_rococo( fee_idx: u32, ) -> DispatchResult { let signed_origin = - ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + ::RuntimeOrigin::signed(AssetHubRococoSender::get()); let beneficiary: Location = AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs new file mode 100644 index 00000000000..44637670112 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs @@ -0,0 +1,107 @@ +// 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. + +use crate::{imports::*, tests::*}; + +const XCM_FEE: u128 = 4_000_000_000_000; + +/// Tests the registering of a Rococo Asset as a bridged asset on Westend Asset Hub. +#[test] +fn register_rococo_asset_on_wah_from_rah() { + let sa_of_rah_on_wah = + AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( + Rococo, + AssetHubRococo::para_id(), + ); + + // Rococo Asset Hub asset when bridged to Westend Asset Hub. + let bridged_asset_at_wah = Location::new( + 2, + [ + GlobalConsensus(Rococo), + Parachain(AssetHubRococo::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(ASSET_ID.into()), + ], + ); + + // Encoded `create_asset` call to be executed in Westend Asset Hub ForeignAssets pallet. + let call = AssetHubWestend::create_foreign_asset_call( + bridged_asset_at_wah.clone(), + ASSET_MIN_BALANCE, + sa_of_rah_on_wah.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = XCM_FEE; + let fees = (Parent, fee_amount).into(); + + let xcm = xcm_transact_paid_execution(call, origin_kind, fees, sa_of_rah_on_wah.clone()); + + // SA-of-RAH-on-WAH needs to have balance to pay for fees and asset creation deposit + AssetHubWestend::fund_accounts(vec![( + sa_of_rah_on_wah.clone(), + ASSET_HUB_WESTEND_ED * 10000000000, + )]); + + let destination = asset_hub_westend_location(); + + // fund the RAH's SA on RBH for paying bridge transport fees + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubRococo::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); + + let root_origin = ::RuntimeOrigin::root(); + AssetHubRococo::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(destination.into()), + bx!(xcm), + )); + + AssetHubRococo::assert_xcm_pallet_sent(); + }); + + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + AssetHubWestend::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubWestend, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == sa_of_rah_on_wah.clone(), + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: asset_id == &bridged_asset_at_wah, + creator: *creator == sa_of_rah_on_wah.clone(), + owner: *owner == sa_of_rah_on_wah, + }, + // Unspent fee minted to origin + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sa_of_rah_on_wah.clone(), + }, + ] + ); + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(bridged_asset_at_wah)); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 9228700c019..76e8312921d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -37,16 +37,17 @@ mod imports { xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, TestExt, }, + xcm_helpers::xcm_transact_paid_execution, ASSETS_PALLET_ID, USDT_ID, }; pub use parachains_common::AccountId; pub use rococo_westend_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ - genesis::{AssetHubRococoAssetOwner, ED as ASSET_HUB_ROCOCO_ED}, - AssetHubRococoParaPallet as AssetHubRococoPallet, + genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, }, asset_hub_westend_emulated_chain::{ - genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, + genesis::{AssetHubWestendAssetOwner, ED as ASSET_HUB_WESTEND_ED}, + AssetHubWestendParaPallet as AssetHubWestendPallet, }, bridge_hub_westend_emulated_chain::{ genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendExistentialDeposit, @@ -74,6 +75,7 @@ mod imports { WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, }; + pub const ASSET_ID: u32 = 1; pub const ASSET_MIN_BALANCE: u128 = 1000; } 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 7def637a66e..0856c952600 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 @@ -113,17 +113,26 @@ fn send_assets_from_penpal_westend_through_westend_ah_to_rococo_ah( } #[test] -/// Test transfer of WND from AssetHub Westend to AssetHub Rococo. -fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { +/// Test transfer of WND, USDT and wETH from AssetHub Westend to AssetHub Rococo. +/// +/// This mix of assets should cover the whole range: +/// - native assets: WND, +/// - trust-based assets: USDT (exists only on Westend, Rococo gets it from Westend over bridge), +/// - foreign asset / bridged asset (other bridge / Snowfork): wETH (bridged from Ethereum to +/// Westend over Snowbridge, then bridged over to Rococo through this bridge). +fn send_wnds_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { let amount = ASSET_HUB_WESTEND_ED * 1_000; let sender = AssetHubWestendSender::get(); let receiver = AssetHubRococoReceiver::get(); let wnd_at_asset_hub_westend = wnd_at_ah_westend(); let bridged_wnd_at_asset_hub_rococo = bridged_wnd_at_ah_rococo(); - create_foreign_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), true); + create_foreign_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), true); set_up_pool_with_roc_on_ah_rococo(bridged_wnd_at_asset_hub_rococo.clone(), true); + //////////////////////////////////////////////////////////// + // Let's first send over just some WNDs as a simple example + //////////////////////////////////////////////////////////// let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus( Rococo, AssetHubRococo::para_id(), @@ -161,7 +170,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { ); }); - let sender_wnds_after = ::account_data_of(sender).free; + let sender_wnds_after = ::account_data_of(sender.clone()).free; let receiver_wnds_after = foreign_balance_on_ah_rococo(bridged_wnd_at_asset_hub_rococo, &receiver); let wnds_in_reserve_on_ahw_after = @@ -173,18 +182,83 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() { assert!(receiver_wnds_after > receiver_wnds_before); // Reserve balance is increased by sent amount assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before + amount); + + ///////////////////////////////////////////////////////////// + // Now let's send over USDTs + wETH (and pay fees with USDT) + ///////////////////////////////////////////////////////////// + let usdt_at_asset_hub_westend = usdt_at_ah_westend(); + let bridged_usdt_at_asset_hub_rococo = bridged_usdt_at_ah_rococo(); + // wETH has same relative location on both Westend and Rococo AssetHubs + let bridged_weth_at_ah = weth_at_asset_hubs(); + + // mint USDT in sender's account (USDT already created in genesis) + AssetHubWestend::mint_asset( + ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), + USDT_ID, + sender.clone(), + amount * 2, + ); + // create wETH at src and dest and prefund sender's account + create_foreign_on_ah_westend( + bridged_weth_at_ah.clone(), + true, + vec![(sender.clone(), amount * 2)], + ); + create_foreign_on_ah_rococo(bridged_weth_at_ah.clone(), true); + create_foreign_on_ah_rococo(bridged_usdt_at_asset_hub_rococo.clone(), true); + set_up_pool_with_roc_on_ah_rococo(bridged_usdt_at_asset_hub_rococo.clone(), true); + + let receiver_usdts_before = + foreign_balance_on_ah_rococo(bridged_usdt_at_asset_hub_rococo.clone(), &receiver); + let receiver_weth_before = foreign_balance_on_ah_rococo(bridged_weth_at_ah.clone(), &receiver); + + // send USDTs and wETHs + let assets: Assets = vec![ + (usdt_at_asset_hub_westend.clone(), amount).into(), + (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount).into(), + ] + .into(); + // use USDT for fees + let fee: AssetId = usdt_at_asset_hub_westend.into(); + + // use the more involved transfer extrinsic + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), + }]); + assert_ok!(AssetHubWestend::execute_with(|| { + ::PolkadotXcm::transfer_assets_using_type_and_then( + ::RuntimeOrigin::signed(sender.into()), + bx!(asset_hub_rococo_location().into()), + bx!(assets.into()), + bx!(TransferType::LocalReserve), + bx!(fee.into()), + bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), + WeightLimit::Unlimited, + ) + })); + // verify hops (also advances the message through the hops) + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); + AssetHubRococo::execute_with(|| { + AssetHubRococo::assert_xcmp_queue_success(None); + }); + + let receiver_usdts_after = + foreign_balance_on_ah_rococo(bridged_usdt_at_asset_hub_rococo, &receiver); + let receiver_weth_after = foreign_balance_on_ah_rococo(bridged_weth_at_ah, &receiver); + + // Receiver's USDT balance is increased by almost `amount` (minus fees) + assert!(receiver_usdts_after > receiver_usdts_before); + assert!(receiver_usdts_after < receiver_usdts_before + amount); + // Receiver's wETH balance is increased by sent amount + assert_eq!(receiver_weth_after, receiver_weth_before + amount); } #[test] -/// Send bridged assets "back" from AssetHub Rococo to AssetHub Westend. -/// -/// This mix of assets should cover the whole range: -/// - bridged native assets: ROC, -/// - bridged trust-based assets: USDT (exists only on Rococo, Westend gets it from Rococo over -/// bridge), -/// - bridged foreign asset / double-bridged asset (other bridge / Snowfork): wETH (bridged from -/// Ethereum to Rococo over Snowbridge, then bridged over to Westend through this bridge). -fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { +/// Send bridged ROCs "back" from AssetHub Westend to AssetHub Rococo. +fn send_back_rocs_from_asset_hub_westend_to_asset_hub_rococo() { let prefund_amount = 10_000_000_000_000u128; let amount_to_send = ASSET_HUB_ROCOCO_ED * 1_000; let sender = AssetHubWestendSender::get(); @@ -193,10 +267,6 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { let prefund_accounts = vec![(sender.clone(), prefund_amount)]; create_foreign_on_ah_westend(bridged_roc_at_asset_hub_westend.clone(), true, prefund_accounts); - //////////////////////////////////////////////////////////// - // Let's first send back just some ROCs as a simple example - //////////////////////////////////////////////////////////// - // fund the AHW's SA on AHR with the ROC tokens held in reserve let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( Westend, @@ -257,96 +327,6 @@ fn send_back_rocs_usdt_and_weth_from_asset_hub_westend_to_asset_hub_rococo() { assert!(receiver_rocs_after > receiver_rocs_before); // Reserve balance is reduced by sent amount assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send); - - ////////////////////////////////////////////////////////////////// - // Now let's send back over USDTs + wETH (and pay fees with USDT) - ////////////////////////////////////////////////////////////////// - - // wETH has same relative location on both Rococo and Westend AssetHubs - let bridged_weth_at_ah = weth_at_asset_hubs(); - let bridged_usdt_at_asset_hub_westend = bridged_usdt_at_ah_westend(); - - // set up destination chain AH Rococo: - // create a ROC/USDT pool to be able to pay fees with USDT (USDT created in genesis) - set_up_pool_with_roc_on_ah_rococo(usdt_at_ah_rococo(), false); - // create wETH on Rococo (IRL it's already created by Snowbridge) - create_foreign_on_ah_rococo(bridged_weth_at_ah.clone(), true); - // prefund AHW's sovereign account on AHR to be able to withdraw USDT and wETH from reserves - let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( - Westend, - AssetHubWestend::para_id(), - ); - AssetHubRococo::mint_asset( - ::RuntimeOrigin::signed(AssetHubRococoAssetOwner::get()), - USDT_ID, - sov_ahw_on_ahr.clone(), - amount_to_send * 2, - ); - AssetHubRococo::mint_foreign_asset( - ::RuntimeOrigin::signed(AssetHubRococo::account_id_of(ALICE)), - bridged_weth_at_ah.clone(), - sov_ahw_on_ahr, - amount_to_send * 2, - ); - - // set up source chain AH Westend: - // create wETH and USDT foreign assets on Westend and prefund sender's account - let prefund_accounts = vec![(sender.clone(), amount_to_send * 2)]; - create_foreign_on_ah_westend(bridged_weth_at_ah.clone(), true, prefund_accounts.clone()); - create_foreign_on_ah_westend(bridged_usdt_at_asset_hub_westend.clone(), true, prefund_accounts); - - // check balances before - let receiver_usdts_before = AssetHubRococo::execute_with(|| { - type Assets = ::Assets; - >::balance(USDT_ID, &receiver) - }); - let receiver_weth_before = foreign_balance_on_ah_rococo(bridged_weth_at_ah.clone(), &receiver); - - let usdt_id: AssetId = Location::try_from(bridged_usdt_at_asset_hub_westend).unwrap().into(); - // send USDTs and wETHs - let assets: Assets = vec![ - (usdt_id.clone(), amount_to_send).into(), - (Location::try_from(bridged_weth_at_ah.clone()).unwrap(), amount_to_send).into(), - ] - .into(); - // use USDT for fees - let fee = usdt_id; - - // use the more involved transfer extrinsic - let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { - assets: Wild(AllCounted(assets.len() as u32)), - beneficiary: AccountId32Junction { network: None, id: receiver.clone().into() }.into(), - }]); - assert_ok!(AssetHubWestend::execute_with(|| { - ::PolkadotXcm::transfer_assets_using_type_and_then( - ::RuntimeOrigin::signed(sender.into()), - bx!(asset_hub_rococo_location().into()), - bx!(assets.into()), - bx!(TransferType::DestinationReserve), - bx!(fee.into()), - bx!(TransferType::DestinationReserve), - bx!(VersionedXcm::from(custom_xcm_on_dest)), - WeightLimit::Unlimited, - ) - })); - // verify hops (also advances the message through the hops) - assert_bridge_hub_westend_message_accepted(true); - assert_bridge_hub_rococo_message_received(); - AssetHubRococo::execute_with(|| { - AssetHubRococo::assert_xcmp_queue_success(None); - }); - - let receiver_usdts_after = AssetHubRococo::execute_with(|| { - type Assets = ::Assets; - >::balance(USDT_ID, &receiver) - }); - let receiver_weth_after = foreign_balance_on_ah_rococo(bridged_weth_at_ah, &receiver); - - // Receiver's USDT balance is increased by almost `amount_to_send` (minus fees) - assert!(receiver_usdts_after > receiver_usdts_before); - assert!(receiver_usdts_after < receiver_usdts_before + amount_to_send); - // Receiver's wETH balance is increased by `amount_to_send` - assert_eq!(receiver_weth_after, receiver_weth_before + amount_to_send); } #[test] 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 f037a05a827..af11f0f7ba7 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 @@ -17,6 +17,7 @@ use crate::imports::*; mod asset_transfers; mod claim_assets; +mod register_bridged_assets; mod send_xcm; mod teleport; @@ -47,15 +48,15 @@ pub(crate) fn bridged_roc_at_ah_westend() -> Location { } // USDT and wUSDT -pub(crate) fn usdt_at_ah_rococo() -> Location { +pub(crate) fn usdt_at_ah_westend() -> Location { Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]) } -pub(crate) fn bridged_usdt_at_ah_westend() -> Location { +pub(crate) fn bridged_usdt_at_ah_rococo() -> Location { Location::new( 2, [ - GlobalConsensus(Rococo), - Parachain(AssetHubRococo::para_id().into()), + GlobalConsensus(Westend), + Parachain(AssetHubWestend::para_id().into()), PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into()), ], diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs new file mode 100644 index 00000000000..7a7ad6da2d5 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs @@ -0,0 +1,131 @@ +// 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. + +use crate::{ + imports::*, + tests::{ + snowbridge::{CHAIN_ID, WETH}, + *, + }, +}; + +const XCM_FEE: u128 = 40_000_000_000; + +/// Tests the registering of a Westend Asset as a bridged asset on Rococo Asset Hub. +#[test] +fn register_westend_asset_on_rah_from_wah() { + // Westend Asset Hub asset when bridged to Rococo Asset Hub. + let bridged_asset_at_rah = Location::new( + 2, + [ + GlobalConsensus(Westend), + Parachain(AssetHubWestend::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(ASSET_ID.into()), + ], + ); + // Register above asset on Rococo AH from Westend AH. + register_asset_on_rah_from_wah(bridged_asset_at_rah); +} + +/// Tests the registering of an Ethereum Asset as a bridged asset on Rococo Asset Hub. +#[test] +fn register_ethereum_asset_on_rah_from_wah() { + // Ethereum asset when bridged to Rococo Asset Hub. + let bridged_asset_at_rah = Location::new( + 2, + [ + GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { network: None, key: WETH }, + ], + ); + // Register above asset on Rococo AH from Westend AH. + register_asset_on_rah_from_wah(bridged_asset_at_rah); +} + +fn register_asset_on_rah_from_wah(bridged_asset_at_rah: Location) { + let sa_of_wah_on_rah = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus( + Westend, + AssetHubWestend::para_id(), + ); + + // Encoded `create_asset` call to be executed in Rococo Asset Hub ForeignAssets pallet. + let call = AssetHubRococo::create_foreign_asset_call( + bridged_asset_at_rah.clone(), + ASSET_MIN_BALANCE, + sa_of_wah_on_rah.clone(), + ); + + let origin_kind = OriginKind::Xcm; + let fee_amount = XCM_FEE; + let fees = (Parent, fee_amount).into(); + + let xcm = xcm_transact_paid_execution(call, origin_kind, fees, sa_of_wah_on_rah.clone()); + + // SA-of-WAH-on-RAH needs to have balance to pay for fees and asset creation deposit + AssetHubRococo::fund_accounts(vec![( + sa_of_wah_on_rah.clone(), + ASSET_HUB_ROCOCO_ED * 10000000000, + )]); + + let destination = asset_hub_rococo_location(); + + // fund the WAH's SA on WBH for paying bridge transport fees + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); + + // set XCM versions + AssetHubWestend::force_xcm_version(destination.clone(), XCM_VERSION); + BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); + + let root_origin = ::RuntimeOrigin::root(); + AssetHubWestend::execute_with(|| { + assert_ok!(::PolkadotXcm::send( + root_origin, + bx!(destination.into()), + bx!(xcm), + )); + + AssetHubWestend::assert_xcm_pallet_sent(); + }); + + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + AssetHubRococo::assert_xcmp_queue_success(None); + assert_expected_events!( + AssetHubRococo, + vec![ + // Burned the fee + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == sa_of_wah_on_rah.clone(), + amount: *amount == fee_amount, + }, + // Foreign Asset created + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { asset_id, creator, owner }) => { + asset_id: asset_id == &bridged_asset_at_rah, + creator: *creator == sa_of_wah_on_rah.clone(), + owner: *owner == sa_of_wah_on_rah, + }, + // Unspent fee minted to origin + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { + who: *who == sa_of_wah_on_rah.clone(), + }, + ] + ); + type ForeignAssets = ::ForeignAssets; + assert!(ForeignAssets::asset_exists(bridged_asset_at_rah)); + }); +} 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 595e3d0711a..67f810e1eb6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -420,6 +420,7 @@ impl pallet_assets::Config for Runtime { ( FromSiblingParachain, xcm::v4::Location>, FromNetwork, + xcm_config::bridging::to_westend::WestendOrEthereumAssetFromAssetHubWestend, ), ForeignCreatorsSovereignAccountOf, AccountId, 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 fab0141e6d4..32fbfb6d019 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 @@ -361,8 +361,8 @@ impl xcm_executor::Config for XcmConfig { // to the Westend ecosystem. We also allow Ethereum contracts to act as reserves for the foreign // assets identified by the same respective contracts locations. type IsReserve = ( - bridging::to_westend::WestendAssetFromAssetHubWestend, - bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, + bridging::to_westend::WestendOrEthereumAssetFromAssetHubWestend, + bridging::to_ethereum::EthereumAssetFromEthereum, ); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; @@ -517,6 +517,7 @@ pub type ForeignCreatorsSovereignAccountOf = ( AccountId32Aliases, ParentIsPreset, GlobalConsensusEthereumConvertsFor, + GlobalConsensusParachainConvertsFor, ); /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. @@ -589,7 +590,9 @@ pub mod bridging { ); pub const WestendNetwork: NetworkId = NetworkId::Westend; + pub const EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; pub WestendEcosystem: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get())]); + pub EthereumEcosystem: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); pub WndLocation: Location = Location::new(2, [GlobalConsensus(WestendNetwork::get())]); pub AssetHubWestend: Location = Location::new(2, [ GlobalConsensus(WestendNetwork::get()), @@ -627,9 +630,12 @@ pub mod bridging { } } - /// Allow any asset native to the Westend ecosystem if it comes from Westend Asset Hub. - pub type WestendAssetFromAssetHubWestend = - matching::RemoteAssetFromLocation, AssetHubWestend>; + /// Allow any asset native to the Westend or Ethereum ecosystems if it comes from Westend + /// Asset Hub. + pub type WestendOrEthereumAssetFromAssetHubWestend = matching::RemoteAssetFromLocation< + (StartsWith, StartsWith), + AssetHubWestend, + >; } pub mod to_ethereum { @@ -672,7 +678,7 @@ pub mod bridging { ); } - pub type IsTrustedBridgedReserveLocationForForeignAsset = + pub type EthereumAssetFromEthereum = IsForeignConcreteAsset>; impl Contains<(Location, Junction)> for UniversalAliases { 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 bd3ba10d5df..aaeb3919987 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -418,6 +418,7 @@ impl pallet_assets::Config for Runtime { ( FromSiblingParachain, xcm::v4::Location>, FromNetwork, + xcm_config::bridging::to_rococo::RococoAssetFromAssetHubRococo, ), ForeignCreatorsSovereignAccountOf, AccountId, 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 614ef81a4fa..cfd9fd2fd46 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 @@ -383,8 +383,8 @@ impl xcm_executor::Config for XcmConfig { // held). On Westend Asset Hub, we allow Rococo Asset Hub to act as reserve for any asset native // to the Rococo or Ethereum ecosystems. type IsReserve = ( - bridging::to_rococo::RococoOrEthereumAssetFromAssetHubRococo, - bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, + bridging::to_rococo::RococoAssetFromAssetHubRococo, + bridging::to_ethereum::EthereumAssetFromEthereum, ); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; @@ -540,6 +540,7 @@ pub type ForeignCreatorsSovereignAccountOf = ( AccountId32Aliases, ParentIsPreset, GlobalConsensusEthereumConvertsFor, + GlobalConsensusParachainConvertsFor, ); /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. @@ -604,10 +605,8 @@ pub mod bridging { ); pub const RococoNetwork: NetworkId = NetworkId::Rococo; - pub const EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; pub RococoEcosystem: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get())]); pub RocLocation: Location = Location::new(2, [GlobalConsensus(RococoNetwork::get())]); - pub EthereumEcosystem: Location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); pub AssetHubRococo: Location = Location::new(2, [ GlobalConsensus(RococoNetwork::get()), Parachain(bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID) @@ -644,12 +643,9 @@ pub mod bridging { } } - /// Allow any asset native to the Rococo or Ethereum ecosystems if it comes from Rococo - /// Asset Hub. - pub type RococoOrEthereumAssetFromAssetHubRococo = matching::RemoteAssetFromLocation< - (StartsWith, StartsWith), - AssetHubRococo, - >; + /// Allow any asset native to the Rococo ecosystem if it comes from Rococo Asset Hub. + pub type RococoAssetFromAssetHubRococo = + matching::RemoteAssetFromLocation, AssetHubRococo>; } pub mod to_ethereum { @@ -703,7 +699,7 @@ pub mod bridging { pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable; - pub type IsTrustedBridgedReserveLocationForForeignAsset = + pub type EthereumAssetFromEthereum = IsForeignConcreteAsset>; impl Contains<(Location, Junction)> for UniversalAliases { diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 9bb35d0c532..9ac2056a67f 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -102,19 +102,27 @@ impl< pub struct RemoteAssetFromLocation( core::marker::PhantomData<(AssetsAllowedNetworks, OriginLocation)>, ); -impl, OriginLocation: Get> - ContainsPair for RemoteAssetFromLocation +impl< + L: TryInto + Clone, + AssetsAllowedNetworks: Contains, + OriginLocation: Get, + > ContainsPair for RemoteAssetFromLocation { - fn contains(asset: &Asset, origin: &Location) -> bool { + fn contains(asset: &L, origin: &L) -> bool { + let Ok(asset) = asset.clone().try_into() else { + return false; + }; + let Ok(origin) = origin.clone().try_into() else { + return false; + }; let expected_origin = OriginLocation::get(); // ensure `origin` is expected `OriginLocation` - if !expected_origin.eq(origin) { + if !expected_origin.eq(&origin) { log::trace!( target: "xcm::contains", - "RemoteAssetFromLocation asset: {:?}, origin: {:?} is not from expected {:?}", - asset, origin, expected_origin, + "RemoteAssetFromLocation asset: {asset:?}, origin: {origin:?} is not from expected {expected_origin:?}" ); - return false + return false; } else { log::trace!( target: "xcm::contains", @@ -123,7 +131,14 @@ impl, OriginLocation: Get> } // ensure `asset` is from remote consensus listed in `AssetsAllowedNetworks` - AssetsAllowedNetworks::contains(&asset.id.0) + AssetsAllowedNetworks::contains(&asset) + } +} +impl, OriginLocation: Get> + ContainsPair for RemoteAssetFromLocation +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + >::contains(&asset.id.0, origin) } } diff --git a/prdoc/pr_5435.prdoc b/prdoc/pr_5435.prdoc new file mode 100644 index 00000000000..d3621e385bc --- /dev/null +++ b/prdoc/pr_5435.prdoc @@ -0,0 +1,16 @@ +title: "Support registering assets on Asset Hubs over bridge" + +doc: + - audience: Runtime User + description: | + Allows one Asset Hub on one side, to register assets on the other Asset Hub over the bridge. + Rococo <> Ethereum test bridge will be dropped in favor of Westend <> Ethereum test bridge. + This PR also changes emulated tests to simulate double bridging from Ethereum<>Westend<>Rococo. + +crates: + - name: assets-common + bump: patch + - name: asset-hub-rococo-runtime + bump: patch + - name: asset-hub-westend-runtime + bump: patch -- GitLab From a2c75758034d920f4b163685377df2ef7a1c9299 Mon Sep 17 00:00:00 2001 From: Joseph Zhao <65984904+programskillforverification@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:24:18 +0800 Subject: [PATCH 118/127] Refactor get_account_id_from_seed / get_from_seed to one common place (#5804) Closes #5705 --------- Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- Cargo.lock | 14 +++- .../common/src/genesis_config_helpers.rs | 46 ----------- cumulus/parachains/common/src/lib.rs | 1 - .../assets/asset-hub-rococo/Cargo.toml | 1 + .../assets/asset-hub-rococo/src/genesis.rs | 10 +-- .../assets/asset-hub-westend/Cargo.toml | 1 + .../assets/asset-hub-westend/src/genesis.rs | 12 +-- .../bridges/bridge-hub-rococo/Cargo.toml | 1 + .../bridges/bridge-hub-rococo/src/genesis.rs | 9 ++- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../bridges/bridge-hub-westend/src/genesis.rs | 9 ++- .../parachains/testing/penpal/Cargo.toml | 1 + .../parachains/testing/penpal/src/genesis.rs | 7 +- .../emulated/chains/relays/rococo/Cargo.toml | 1 + .../chains/relays/rococo/src/genesis.rs | 9 +-- .../emulated/common/Cargo.toml | 1 + .../emulated/common/src/lib.rs | 77 ++++-------------- .../tests/assets/asset-hub-rococo/Cargo.toml | 1 + .../tests/assets/asset-hub-rococo/src/lib.rs | 4 +- .../src/tests/reserve_transfer.rs | 4 +- .../tests/assets/asset-hub-westend/Cargo.toml | 1 - .../tests/assets/asset-hub-westend/src/lib.rs | 4 +- .../src/tests/reserve_transfer.rs | 4 +- .../assets/asset-hub-rococo/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 45 +++-------- .../assets/asset-hub-westend/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 43 +++------- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 15 ++-- .../src/genesis_config_presets.rs | 58 +++----------- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 3 +- .../src/genesis_config_presets.rs | 58 +++----------- .../collectives-westend/Cargo.toml | 2 + .../src/genesis_config_presets.rs | 42 +++------- cumulus/polkadot-parachain/Cargo.toml | 1 + .../src/chain_spec/coretime.rs | 40 ++++------ .../src/chain_spec/glutton.rs | 21 ++--- .../polkadot-parachain/src/chain_spec/mod.rs | 14 +--- .../src/chain_spec/penpal.rs | 31 ++----- .../src/chain_spec/people.rs | 62 +++----------- .../src/chain_spec/rococo_parachain.rs | 25 ++---- cumulus/test/service/src/chain_spec.rs | 61 ++------------ cumulus/xcm/xcm-emulator/src/lib.rs | 19 +---- polkadot/node/service/src/chain_spec.rs | 19 ----- polkadot/node/test/service/src/chain_spec.rs | 35 +++----- polkadot/runtime/common/Cargo.toml | 1 + polkadot/runtime/common/src/purchase.rs | 29 ++----- polkadot/runtime/rococo/Cargo.toml | 1 + .../rococo/src/genesis_config_presets.rs | 67 ++++++---------- polkadot/runtime/westend/Cargo.toml | 1 + .../westend/src/genesis_config_presets.rs | 66 ++++++--------- .../xcm-executor/integration-tests/src/lib.rs | 5 +- prdoc/pr_5804.prdoc | 42 ++++++++++ substrate/bin/node/cli/Cargo.toml | 13 +-- substrate/bin/node/cli/src/chain_spec.rs | 70 +++++----------- substrate/bin/node/testing/src/bench.rs | 22 ++--- substrate/bin/node/testing/src/keyring.rs | 35 ++++---- substrate/primitives/core/src/crypto.rs | 15 +++- .../primitives/keyring/src/bandersnatch.rs | 13 +-- substrate/primitives/keyring/src/ed25519.rs | 72 ++++++++++++++++- substrate/primitives/keyring/src/lib.rs | 13 +++ substrate/primitives/keyring/src/sr25519.rs | 80 ++++++++++++++----- templates/parachain/runtime/Cargo.toml | 8 +- .../runtime/src/genesis_config_presets.rs | 58 +++----------- 63 files changed, 536 insertions(+), 892 deletions(-) delete mode 100644 cumulus/parachains/common/src/genesis_config_helpers.rs create mode 100644 prdoc/pr_5804.prdoc diff --git a/Cargo.lock b/Cargo.lock index eb52a1a4c1a..846237e0374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -812,6 +812,7 @@ dependencies = [ "parachains-common", "rococo-emulated-chain", "sp-core 28.0.0", + "sp-keyring", "staging-xcm", "testnet-parachains-constants", ] @@ -837,6 +838,7 @@ dependencies = [ "polkadot-runtime-common", "rococo-runtime-constants", "rococo-system-emulated-network", + "sp-core 28.0.0", "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", @@ -911,6 +913,7 @@ dependencies = [ "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", + "sp-keyring", "sp-offchain", "sp-runtime 31.0.1", "sp-session", @@ -938,6 +941,7 @@ dependencies = [ "frame-support", "parachains-common", "sp-core 28.0.0", + "sp-keyring", "staging-xcm", "testnet-parachains-constants", "westend-emulated-chain", @@ -967,7 +971,6 @@ dependencies = [ "parity-scale-codec", "polkadot-runtime-common", "sp-core 28.0.0", - "sp-keyring", "sp-runtime 31.0.1", "staging-xcm", "staging-xcm-executor", @@ -1043,6 +1046,7 @@ dependencies = [ "sp-core 28.0.0", "sp-genesis-builder", "sp-inherents", + "sp-keyring", "sp-offchain", "sp-runtime 31.0.1", "sp-session", @@ -2194,6 +2198,7 @@ dependencies = [ "frame-support", "parachains-common", "sp-core 28.0.0", + "sp-keyring", "staging-xcm", "testnet-parachains-constants", ] @@ -2385,6 +2390,7 @@ dependencies = [ "frame-support", "parachains-common", "sp-core 28.0.0", + "sp-keyring", "staging-xcm", "testnet-parachains-constants", ] @@ -3175,6 +3181,7 @@ dependencies = [ "sp-genesis-builder", "sp-inherents", "sp-io 30.0.0", + "sp-keyring", "sp-offchain", "sp-runtime 31.0.1", "sp-session", @@ -5567,6 +5574,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-beefy", "sp-core 28.0.0", + "sp-keyring", "sp-runtime 31.0.1", "staging-xcm", "xcm-emulator", @@ -13340,6 +13348,7 @@ dependencies = [ "parachains-common", "penpal-runtime", "sp-core 28.0.0", + "sp-keyring", "staging-xcm", ] @@ -14883,6 +14892,7 @@ dependencies = [ "serde_json", "sp-core 28.0.0", "sp-genesis-builder", + "sp-keyring", "staging-xcm", "substrate-build-script-utils", ] @@ -17593,6 +17603,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-beefy", "sp-core 28.0.0", + "sp-keyring", ] [[package]] @@ -23263,6 +23274,7 @@ dependencies = [ "serde", "serde_json", "soketto 0.8.0", + "sp-keyring", "staging-node-inspect", "substrate-cli-test-utils", "tempfile", diff --git a/cumulus/parachains/common/src/genesis_config_helpers.rs b/cumulus/parachains/common/src/genesis_config_helpers.rs deleted file mode 100644 index cd98c3a729d..00000000000 --- a/cumulus/parachains/common/src/genesis_config_helpers.rs +++ /dev/null @@ -1,46 +0,0 @@ -// 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. - -//! Some common helpers for declaring runtime's presets - -use crate::{AccountId, Signature}; -#[cfg(not(feature = "std"))] -use alloc::format; -use sp_core::{Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; - -/// Helper function to generate a crypto pair from seed. -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{seed}"), None) - .expect("static values are valid; qed") - .public() -} - -type AccountPublic = ::Signer; - -/// Helper function to generate an account id from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - -/// Generate collator keys from seed. -/// -/// This function's return type must always match the session keys of the chain in tuple format. -pub fn get_collator_keys_from_seed(seed: &str) -> ::Public { - get_from_seed::(seed) -} diff --git a/cumulus/parachains/common/src/lib.rs b/cumulus/parachains/common/src/lib.rs index 60040fda992..3cffb69daac 100644 --- a/cumulus/parachains/common/src/lib.rs +++ b/cumulus/parachains/common/src/lib.rs @@ -17,7 +17,6 @@ extern crate alloc; -pub mod genesis_config_helpers; pub mod impls; pub mod message_queue; pub mod xcm_config; diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index 51ce5b18005..25796e7d64b 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } frame-support = { workspace = true } # Cumulus diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs index 5b70ed490c6..606d04060b6 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -15,13 +15,13 @@ // Substrate use frame_support::parameter_types; -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, collators, get_account_id_from_seed, - PenpalSiblingSovereignAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, - SAFE_XCM_VERSION, USDT_ID, + accounts, build_genesis_storage, collators, PenpalSiblingSovereignAccount, + PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, SAFE_XCM_VERSION, USDT_ID, }; use parachains_common::{AccountId, Balance}; @@ -29,7 +29,7 @@ pub const PARA_ID: u32 = 1000; pub const ED: Balance = testnet_parachains_constants::rococo::currency::EXISTENTIAL_DEPOSIT; parameter_types! { - pub AssetHubRococoAssetOwner: AccountId = get_account_id_from_seed::("Alice"); + pub AssetHubRococoAssetOwner: AccountId = Keyring::Alice.to_account_id(); } pub fn genesis() -> Storage { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml index d32f9832170..8e423ebbf9c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } frame-support = { workspace = true } # Cumulus diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index a9cfcda0dac..30e7279a383 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -15,14 +15,14 @@ // Substrate use frame_support::parameter_types; -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, collators, get_account_id_from_seed, - PenpalBSiblingSovereignAccount, PenpalBTeleportableAssetLocation, - PenpalSiblingSovereignAccount, PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, - SAFE_XCM_VERSION, USDT_ID, + accounts, build_genesis_storage, collators, PenpalBSiblingSovereignAccount, + PenpalBTeleportableAssetLocation, PenpalSiblingSovereignAccount, + PenpalTeleportableAssetLocation, RESERVABLE_ASSET_ID, SAFE_XCM_VERSION, USDT_ID, }; use parachains_common::{AccountId, Balance}; @@ -31,7 +31,7 @@ pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTEN pub const USDT_ED: Balance = 70_000; parameter_types! { - pub AssetHubWestendAssetOwner: AccountId = get_account_id_from_seed::("Alice"); + pub AssetHubWestendAssetOwner: AccountId = Keyring::Alice.to_account_id(); } pub fn genesis() -> Storage { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index 266d743ca0c..231265085ed 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } frame-support = { workspace = true } # Polkadot Dependencies diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index b9c0c01101c..0268a6a7a1b 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -14,11 +14,12 @@ // limitations under the License. // Substrate -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, }; use parachains_common::Balance; use xcm::latest::prelude::*; @@ -60,11 +61,11 @@ pub fn genesis() -> Storage { ..Default::default() }, bridge_westend_grandpa: bridge_hub_rococo_runtime::BridgeWestendGrandpaConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), + owner: Some(Keyring::Bob.to_account_id()), ..Default::default() }, bridge_westend_messages: bridge_hub_rococo_runtime::BridgeWestendMessagesConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), + owner: Some(Keyring::Bob.to_account_id()), ..Default::default() }, xcm_over_bridge_hub_westend: bridge_hub_rococo_runtime::XcmOverBridgeHubWestendConfig { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index 88d7348f50f..8292e132809 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } frame-support = { workspace = true } # Polkadot Dependencies diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index 3ffe3d86b2a..f72eaa30026 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -14,11 +14,12 @@ // limitations under the License. // Substrate -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, }; use parachains_common::Balance; use xcm::latest::prelude::*; @@ -60,11 +61,11 @@ pub fn genesis() -> Storage { ..Default::default() }, bridge_rococo_grandpa: bridge_hub_westend_runtime::BridgeRococoGrandpaConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), + owner: Some(Keyring::Bob.to_account_id()), ..Default::default() }, bridge_rococo_messages: bridge_hub_westend_runtime::BridgeRococoMessagesConfig { - owner: Some(get_account_id_from_seed::(accounts::BOB)), + owner: Some(Keyring::Bob.to_account_id()), ..Default::default() }, xcm_over_bridge_hub_rococo: bridge_hub_westend_runtime::XcmOverBridgeHubRococoConfig { diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml index 9e6b14b5859..743cd7dc54a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } frame-support = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index 2c34b7e96f5..63510d233d2 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -15,11 +15,12 @@ // Substrate use frame_support::parameter_types; -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, collators, get_account_id_from_seed, SAFE_XCM_VERSION, + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, }; use parachains_common::{AccountId, Balance}; use penpal_runtime::xcm_config::{LocalReservableFromAssetHub, RelayLocation, UsdtFromAssetHub}; @@ -30,7 +31,7 @@ pub const ED: Balance = penpal_runtime::EXISTENTIAL_DEPOSIT; pub const USDT_ED: Balance = 70_000; parameter_types! { - pub PenpalSudoAccount: AccountId = get_account_id_from_seed::("Alice"); + pub PenpalSudoAccount: AccountId = Keyring::Alice.to_account_id(); pub PenpalAssetOwner: AccountId = PenpalSudoAccount::get(); } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml index 9376687947e..6db1263df8c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true, default-features = true } 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 9cb25b40360..3d8b5b1a500 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 @@ -18,14 +18,15 @@ use sc_consensus_grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; -use sp_core::{sr25519, storage::Storage}; +use sp_core::storage::Storage; +use sp_keyring::Sr25519Keyring as Keyring; // Polkadot use polkadot_primitives::{AssignmentId, ValidatorId}; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, get_account_id_from_seed, get_host_config, validators, + accounts, build_genesis_storage, get_host_config, validators, }; use parachains_common::Balance; use rococo_runtime_constants::currency::UNITS as ROC; @@ -82,9 +83,7 @@ pub fn genesis() -> Storage { epoch_config: rococo_runtime::BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, - sudo: rococo_runtime::SudoConfig { - key: Some(get_account_id_from_seed::("Alice")), - }, + sudo: rococo_runtime::SudoConfig { key: Some(Keyring::Alice.to_account_id()) }, configuration: rococo_runtime::ConfigurationConfig { config: get_host_config() }, registrar: rococo_runtime::RegistrarConfig { next_free_para_id: polkadot_primitives::LOWEST_PUBLIC_ID, diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 981ee5c88b4..23edaf6bfe6 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -21,6 +21,7 @@ sp-runtime = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index c6b8889730e..07fde111d3d 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -25,11 +25,9 @@ use sc_consensus_grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; -use sp_core::{sr25519, storage::Storage, Pair, Public}; -use sp_runtime::{ - traits::{AccountIdConversion, IdentifyAccount, Verify}, - BuildStorage, MultiSignature, -}; +use sp_core::storage::Storage; +use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; +use sp_runtime::{traits::AccountIdConversion, BuildStorage}; // Polakdot use parachains_common::BlockNumber; @@ -49,8 +47,6 @@ pub const PROOF_SIZE_THRESHOLD: u64 = 33; /// The default XCM version to set in genesis config. pub const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -type AccountPublic = ::Signer; - // (trust-backed) Asset registered on AH and reserve-transferred between Parachain and AH pub const RESERVABLE_ASSET_ID: u32 = 1; // ForeignAsset registered on AH and teleported between Penpal and AH @@ -82,21 +78,6 @@ parameter_types! { pub PenpalBSiblingSovereignAccount: AccountId = Sibling::from(PENPAL_B_ID).into_account_truncating(); } -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - pub fn get_host_config() -> HostConfiguration { HostConfiguration { max_upward_queue_count: 10, @@ -130,34 +111,10 @@ pub mod accounts { use super::*; pub const ALICE: &str = "Alice"; pub const BOB: &str = "Bob"; - pub const CHARLIE: &str = "Charlie"; - pub const DAVE: &str = "Dave"; - pub const EVE: &str = "Eve"; - pub const FERDIE: &str = "Ferdie"; - pub const ALICE_STASH: &str = "Alice//stash"; - pub const BOB_STASH: &str = "Bob//stash"; - pub const CHARLIE_STASH: &str = "Charlie//stash"; - pub const DAVE_STASH: &str = "Dave//stash"; - pub const EVE_STASH: &str = "Eve//stash"; - pub const FERDIE_STASH: &str = "Ferdie//stash"; - pub const FERDIE_BEEFY: &str = "Ferdie//stash"; pub const DUMMY_EMPTY: &str = "JohnDoe"; pub fn init_balances() -> Vec { - vec![ - get_account_id_from_seed::(ALICE), - get_account_id_from_seed::(BOB), - get_account_id_from_seed::(CHARLIE), - get_account_id_from_seed::(DAVE), - get_account_id_from_seed::(EVE), - get_account_id_from_seed::(FERDIE), - get_account_id_from_seed::(ALICE_STASH), - get_account_id_from_seed::(BOB_STASH), - get_account_id_from_seed::(CHARLIE_STASH), - get_account_id_from_seed::(DAVE_STASH), - get_account_id_from_seed::(EVE_STASH), - get_account_id_from_seed::(FERDIE_STASH), - ] + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect() } } @@ -166,16 +123,15 @@ pub mod collators { pub fn invulnerables() -> Vec<(AccountId, AuraId)> { vec![ - ( - get_account_id_from_seed::("Alice"), - get_from_seed::("Alice"), - ), - (get_account_id_from_seed::("Bob"), get_from_seed::("Bob")), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ] } } pub mod validators { + use sp_consensus_beefy::test_utils::Keyring; + use super::*; pub fn initial_authorities() -> Vec<( @@ -188,16 +144,15 @@ pub mod validators { AuthorityDiscoveryId, BeefyId, )> { - 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), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::Alice.to_account_id(), + BabeId::from(Sr25519Keyring::Alice.public()), + GrandpaId::from(Ed25519Keyring::Alice.public()), + ValidatorId::from(Sr25519Keyring::Alice.public()), + AssignmentId::from(Sr25519Keyring::Alice.public()), + AuthorityDiscoveryId::from(Sr25519Keyring::Alice.public()), + BeefyId::from(Keyring::::Alice.public()), )] } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index f66a5f1d5fe..3d40db6b03a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -16,6 +16,7 @@ assert_matches = { workspace = true } # Substrate sp-runtime = { workspace = true } +sp-core = { workspace = true } frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-assets = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index 0e43108a417..12f440fdefe 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -36,8 +36,8 @@ mod imports { pub use asset_test_utils::xcm_helpers; pub use emulated_integration_tests_common::{ accounts::DUMMY_EMPTY, - get_account_id_from_seed, test_parachain_is_trusted_teleporter, - test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, + test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, + test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, 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 8aad4b392b2..302f71f89f8 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 @@ -14,6 +14,7 @@ // limitations under the License. use crate::imports::*; +use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -1042,7 +1043,8 @@ fn reserve_transfer_multiple_assets_from_para_to_asset_hub() { ); // Beneficiary is a new (empty) account - let receiver = get_account_id_from_seed::(DUMMY_EMPTY); + let receiver: sp_runtime::AccountId32 = + get_public_from_string_or_panic::(DUMMY_EMPTY).into(); // Init values for Asset Hub let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 6b50b6f473e..872a8ffa6a8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -16,7 +16,6 @@ assert_matches = { workspace = true } # Substrate sp-runtime = { workspace = true } -sp-keyring = { workspace = true } sp-core = { workspace = true } frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index d0016b5a1b1..906768b19b7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -33,8 +33,8 @@ mod imports { pub use asset_test_utils::xcm_helpers; pub use emulated_integration_tests_common::{ accounts::DUMMY_EMPTY, - get_account_id_from_seed, test_parachain_is_trusted_teleporter, - test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter, + test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay, + test_relay_is_trusted_teleporter, xcm_emulator::{ assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test, TestArgs, TestContext, TestExt, 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 0100e8e34ef..10c27c338ec 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 @@ -14,6 +14,7 @@ // limitations under the License. use crate::imports::*; +use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -1043,7 +1044,8 @@ fn reserve_transfer_multiple_assets_from_para_to_asset_hub() { ); // Beneficiary is a new (empty) account - let receiver = get_account_id_from_seed::(DUMMY_EMPTY); + let receiver: sp_runtime::AccountId32 = + get_public_from_string_or_panic::(DUMMY_EMPTY).into(); // Init values for Asset Hub let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id()); let sov_penpal_on_ahr = diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 47e0983a415..00f2cf8f636 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -49,6 +49,7 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } sp-offchain = { workspace = true } @@ -239,6 +240,7 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs index 1dbd92d6bff..dc98d00f8f6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/genesis_config_presets.rs @@ -19,9 +19,10 @@ use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; -use sp_core::{crypto::UncheckedInto, sr25519}; +use parachains_common::{AccountId, AuraId}; +use sp_core::crypto::UncheckedInto; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; use testnet_parachains_constants::rococo::{currency::UNITS as ROC, xcm_version::SAFE_XCM_VERSION}; const ASSET_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); @@ -109,43 +110,21 @@ pub fn get_preset(id: &PresetId) -> Option> { Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => asset_hub_rococo_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - ROC * 1_000_000, + Sr25519Keyring::well_known().map(|x| x.to_account_id()).collect(), + testnet_parachains_constants::rococo::currency::UNITS * 1_000_000, 1000.into(), ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => asset_hub_rococo_genesis( // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], + vec![(Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into())], vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], ROC * 1_000_000, 1000.into(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 1434c3e3b60..72a125cee2a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -49,6 +49,7 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-offchain = { workspace = true } @@ -242,6 +243,7 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs index b287dcd5621..758ce3f4060 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/genesis_config_presets.rs @@ -19,9 +19,10 @@ use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; -use sp_core::{crypto::UncheckedInto, sr25519}; +use parachains_common::{AccountId, AuraId}; +use sp_core::crypto::UncheckedInto; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; use testnet_parachains_constants::westend::{ currency::UNITS as WND, xcm_version::SAFE_XCM_VERSION, }; @@ -107,43 +108,21 @@ pub fn get_preset(id: &PresetId) -> Option> { Ok(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) => asset_hub_westend_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), WND * 1_000_000, 1000.into(), ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => asset_hub_westend_genesis( // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], + vec![(Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into())], vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], WND * 1_000_000, 1000.into(), 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 9a76e61ecb2..fd5782d68e4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -13,14 +13,10 @@ workspace = true substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dependencies] -codec = { features = [ - "derive", -], workspace = true } +codec = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } log = { workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } serde_json = { features = ["alloc"], workspace = true } @@ -47,6 +43,7 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } @@ -74,9 +71,7 @@ cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } -cumulus-pallet-xcmp-queue = { features = [ - "bridging", -], workspace = true } +cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } @@ -126,7 +121,6 @@ bridge-hub-common = { workspace = true } bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } -sp-keyring = { workspace = true, default-features = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] @@ -210,6 +204,7 @@ std = [ "sp-genesis-builder/std", "sp-inherents/std", "sp-io/std", + "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index 07048d54ab1..d1b599967bf 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -18,9 +18,9 @@ use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; -use sp_core::sr25519; +use parachains_common::{AccountId, AuraId}; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; use testnet_parachains_constants::rococo::xcm_version::SAFE_XCM_VERSION; const BRIDGE_HUB_ROCOCO_ED: Balance = ExistentialDeposit::get(); @@ -93,31 +93,12 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option bridge_hub_rococo_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1013.into(), - Some(get_account_id_from_seed::("Bob")), + Some(Sr25519Keyring::Bob.to_account_id()), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), vec![( Location::new(1, [Parachain(1000)]), @@ -128,31 +109,12 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option bridge_hub_rococo_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1013.into(), - Some(get_account_id_from_seed::("Bob")), + Some(Sr25519Keyring::Bob.to_account_id()), rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(), vec![], ), 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 a0233bf2ea4..471158d9645 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -43,6 +43,7 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } @@ -119,7 +120,6 @@ snowbridge-runtime-common = { workspace = true } bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } -sp-keyring = { workspace = true, default-features = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] @@ -200,6 +200,7 @@ std = [ "sp-genesis-builder/std", "sp-inherents/std", "sp-io/std", + "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs index 0b270e58433..2949ae01fdc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/genesis_config_presets.rs @@ -18,9 +18,9 @@ use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; -use sp_core::sr25519; +use parachains_common::{AccountId, AuraId}; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; use testnet_parachains_constants::westend::xcm_version::SAFE_XCM_VERSION; const BRIDGE_HUB_WESTEND_ED: Balance = ExistentialDeposit::get(); @@ -93,31 +93,12 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option bridge_hub_westend_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1002.into(), - Some(get_account_id_from_seed::("Bob")), + Some(Sr25519Keyring::Bob.to_account_id()), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), vec![( Location::new(1, [Parachain(1000)]), @@ -128,31 +109,12 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option bridge_hub_westend_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1002.into(), - Some(get_account_id_from_seed::("Bob")), + Some(Sr25519Keyring::Bob.to_account_id()), westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(), vec![], ), diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 170d6d22605..8a47af18524 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -50,6 +50,7 @@ sp-arithmetic = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } +sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-offchain = { workspace = true } @@ -228,6 +229,7 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-keyring/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs index 30a23d7aaea..aec8e96cedc 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/genesis_config_presets.rs @@ -18,9 +18,9 @@ use crate::*; use alloc::{vec, vec::Vec}; use cumulus_primitives_core::ParaId; -use parachains_common::{genesis_config_helpers::*, AccountId, AuraId}; -use sp_core::sr25519; +use parachains_common::{AccountId, AuraId}; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; use testnet_parachains_constants::westend::xcm_version::SAFE_XCM_VERSION; const COLLECTIVES_WESTEND_ED: Balance = ExistentialDeposit::get(); @@ -73,42 +73,20 @@ pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option collectives_westend_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1001.into(), ), Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => collectives_westend_genesis( // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], + vec![(Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into())], vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], 1001.into(), ), diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 426679ce0cd..5520126d074 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -40,6 +40,7 @@ parachains-common = { workspace = true, default-features = true } # Substrate sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } diff --git a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs index e42e95ad16f..fa865d7458c 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -146,13 +146,11 @@ pub fn chain_type_name(chain_type: &ChainType) -> Cow { /// Sub-module for Rococo setup. pub mod rococo { use super::{chain_type_name, CoretimeRuntimeType, ParaId}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, - }; + use crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId, Balance}; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; - use sp_core::sr25519; + use sp_keyring::Sr25519Keyring; pub(crate) const CORETIME_ROCOCO: &str = "coretime-rococo"; pub(crate) const CORETIME_ROCOCO_LOCAL: &str = "coretime-rococo-local"; @@ -187,15 +185,12 @@ pub mod rococo { .with_chain_type(chain_type) .with_genesis_config_patch(genesis( // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], + vec![(Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into())], vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], para_id, )) @@ -235,7 +230,7 @@ pub mod rococo { "safeXcmVersion": Some(SAFE_XCM_VERSION), }, "sudo": { - "key": Some(get_account_id_from_seed::("Alice")), + "key": Some(Sr25519Keyring::Alice.to_account_id()), }, }) } @@ -244,12 +239,10 @@ pub mod rococo { /// Sub-module for Westend setup. pub mod westend { use super::{chain_type_name, CoretimeRuntimeType, GenericChainSpec, ParaId}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, - }; + use crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId, Balance}; use polkadot_omni_node_lib::chain_spec::Extensions; - use sp_core::sr25519; + use sp_keyring::Sr25519Keyring; pub(crate) const CORETIME_WESTEND: &str = "coretime-westend"; pub(crate) const CORETIME_WESTEND_LOCAL: &str = "coretime-westend-local"; @@ -277,15 +270,12 @@ pub mod westend { .with_chain_type(chain_type) .with_genesis_config_patch(genesis( // initial collators. - vec![( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - )], + vec![(Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into())], vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], para_id, )) diff --git a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs index e70f86f725a..ddfb961370a 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/glutton.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/glutton.rs @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::get_account_id_from_seed; use cumulus_primitives_core::ParaId; use parachains_common::AuraId; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; -use sp_core::sr25519; - -use super::get_collator_keys_from_seed; +use sp_keyring::Sr25519Keyring; fn glutton_genesis(parachain_id: ParaId, collators: Vec) -> serde_json::Value { serde_json::json!( { @@ -29,7 +26,7 @@ fn glutton_genesis(parachain_id: ParaId, collators: Vec) -> serde_json:: "parachainId": parachain_id }, "sudo": { - "key": Some(get_account_id_from_seed::("Alice")), + "key": Some(Sr25519Keyring::Alice.to_account_id()), }, "aura": { "authorities": collators }, }) @@ -45,7 +42,7 @@ pub fn glutton_westend_development_config(para_id: ParaId) -> GenericChainSpec { .with_chain_type(ChainType::Local) .with_genesis_config_patch(glutton_genesis( para_id, - vec![get_collator_keys_from_seed::("Alice")], + vec![Sr25519Keyring::Alice.public().into()], )) .build() } @@ -60,10 +57,7 @@ pub fn glutton_westend_local_config(para_id: ParaId) -> GenericChainSpec { .with_chain_type(ChainType::Local) .with_genesis_config_patch(glutton_genesis( para_id, - vec![ - get_collator_keys_from_seed::("Alice"), - get_collator_keys_from_seed::("Bob"), - ], + vec![Sr25519Keyring::Alice.public().into(), Sr25519Keyring::Bob.public().into()], )) .build() } @@ -81,10 +75,7 @@ pub fn glutton_westend_config(para_id: ParaId) -> GenericChainSpec { .with_chain_type(ChainType::Live) .with_genesis_config_patch(glutton_westend_genesis( para_id, - vec![ - get_collator_keys_from_seed::("Alice"), - get_collator_keys_from_seed::("Bob"), - ], + vec![Sr25519Keyring::Alice.public().into(), Sr25519Keyring::Bob.public().into()], )) .with_protocol_id(format!("glutton-westend-{}", para_id).as_str()) .with_properties(properties) @@ -97,7 +88,7 @@ fn glutton_westend_genesis(parachain_id: ParaId, collators: Vec) -> serd "parachainId": parachain_id }, "sudo": { - "key": Some(get_account_id_from_seed::("Alice")), + "key": Some(Sr25519Keyring::Alice.to_account_id()), }, "aura": { "authorities": collators }, }) diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index a820fdbd13e..00dceabb006 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -15,9 +15,6 @@ // along with Cumulus. If not, see . use cumulus_primitives_core::ParaId; -pub(crate) use parachains_common::genesis_config_helpers::{ - get_account_id_from_seed, get_collator_keys_from_seed, get_from_seed, -}; use polkadot_omni_node_lib::{ chain_spec::{GenericChainSpec, LoadSpec}, runtime::{ @@ -274,7 +271,7 @@ mod tests { use super::*; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup, ChainType, Extension}; use serde::{Deserialize, Serialize}; - use sp_core::sr25519; + use sp_keyring::Sr25519Keyring; #[derive( Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension, Default, @@ -310,12 +307,9 @@ mod tests { .with_id(id) .with_chain_type(ChainType::Local) .with_genesis_config_patch(crate::chain_spec::rococo_parachain::testnet_genesis( - get_account_id_from_seed::("Alice"), - vec![ - get_from_seed::("Alice"), - get_from_seed::("Bob"), - ], - vec![get_account_id_from_seed::("Alice")], + Sr25519Keyring::Alice.to_account_id(), + vec![Sr25519Keyring::Alice.public().into(), Sr25519Keyring::Bob.public().into()], + vec![Sr25519Keyring::Bob.to_account_id()], 1000.into(), )) .build() diff --git a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs index 006c5c9b53c..b60b9982c49 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/penpal.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/penpal.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::chain_spec::{get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION}; +use crate::chain_spec::SAFE_XCM_VERSION; use cumulus_primitives_core::ParaId; use parachains_common::{AccountId, AuraId}; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_service::ChainType; -use sp_core::sr25519; +use sp_keyring::Sr25519Keyring; pub fn get_penpal_chain_spec(id: ParaId, relay_chain: &str) -> GenericChainSpec { // Give your base currency a unit name and decimal places @@ -41,29 +41,10 @@ pub fn get_penpal_chain_spec(id: ParaId, relay_chain: &str) -> GenericChainSpec .with_genesis_config_patch(penpal_testnet_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), id, )) .build() @@ -105,7 +86,7 @@ fn penpal_testnet_genesis( "safeXcmVersion": Some(SAFE_XCM_VERSION), }, "sudo": { - "key": Some(get_account_id_from_seed::("Alice")), + "key": Some(Sr25519Keyring::Alice.to_account_id()), }, }) } diff --git a/cumulus/polkadot-parachain/src/chain_spec/people.rs b/cumulus/polkadot-parachain/src/chain_spec/people.rs index b89f1c3a5fe..1735a904b8e 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/people.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/people.rs @@ -120,13 +120,11 @@ fn ensure_id(id: &str) -> Result<&str, String> { /// Sub-module for Rococo setup. pub mod rococo { use super::{ParaId, PeopleBalance}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, - }; + use crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId}; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; - use sp_core::sr25519; + use sp_keyring::Sr25519Keyring; pub(crate) const PEOPLE_ROCOCO: &str = "people-rococo"; pub(crate) const PEOPLE_ROCOCO_LOCAL: &str = "people-rococo-local"; @@ -155,29 +153,10 @@ pub mod rococo { .with_genesis_config_patch(genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), para_id, )) .with_properties(properties) @@ -230,13 +209,11 @@ pub mod rococo { /// Sub-module for Westend setup. pub mod westend { use super::{ParaId, PeopleBalance}; - use crate::chain_spec::{ - get_account_id_from_seed, get_collator_keys_from_seed, SAFE_XCM_VERSION, - }; + use crate::chain_spec::SAFE_XCM_VERSION; use parachains_common::{AccountId, AuraId}; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use sc_chain_spec::ChainType; - use sp_core::sr25519; + use sp_keyring::Sr25519Keyring; pub(crate) const PEOPLE_WESTEND: &str = "people-westend"; pub(crate) const PEOPLE_WESTEND_LOCAL: &str = "people-westend-local"; @@ -265,29 +242,10 @@ pub mod westend { .with_genesis_config_patch(genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), para_id, )) .with_properties(properties) diff --git a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs index 39762f590cf..68383ac5c23 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/rococo_parachain.rs @@ -16,14 +16,15 @@ //! ChainSpecs dedicated to Rococo parachain setups (for testing and example purposes) -use crate::chain_spec::{get_from_seed, SAFE_XCM_VERSION}; +use crate::chain_spec::SAFE_XCM_VERSION; use cumulus_primitives_core::ParaId; use hex_literal::hex; -use parachains_common::{genesis_config_helpers::get_account_id_from_seed, AccountId}; +use parachains_common::AccountId; use polkadot_omni_node_lib::chain_spec::{Extensions, GenericChainSpec}; use rococo_parachain_runtime::AuraId; use sc_chain_spec::ChainType; -use sp_core::{crypto::UncheckedInto, sr25519}; +use sp_core::crypto::UncheckedInto; +use sp_keyring::Sr25519Keyring; pub fn rococo_parachain_local_config() -> GenericChainSpec { GenericChainSpec::builder( @@ -34,22 +35,12 @@ pub fn rococo_parachain_local_config() -> GenericChainSpec { .with_id("local_testnet") .with_chain_type(ChainType::Local) .with_genesis_config_patch(testnet_genesis( - get_account_id_from_seed::("Alice"), - vec![get_from_seed::("Alice"), get_from_seed::("Bob")], + Sr25519Keyring::Alice.to_account_id(), vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + AuraId::from(Sr25519Keyring::Alice.public()), + AuraId::from(Sr25519Keyring::Bob.public()), ], + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), 1000.into(), )) .build() diff --git a/cumulus/test/service/src/chain_spec.rs b/cumulus/test/service/src/chain_spec.rs index ae71028ad48..3abffcff794 100644 --- a/cumulus/test/service/src/chain_spec.rs +++ b/cumulus/test/service/src/chain_spec.rs @@ -17,24 +17,16 @@ #![allow(missing_docs)] use cumulus_primitives_core::ParaId; -use cumulus_test_runtime::{AccountId, Signature}; +use cumulus_test_runtime::AccountId; use parachains_common::AuraId; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; -use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_keyring::Sr25519Keyring; /// Specialized `ChainSpec` for the normal parachain runtime. pub type ChainSpec = sc_service::GenericChainSpec; -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - /// The extensions for the [`ChainSpec`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] #[serde(deny_unknown_fields)] @@ -50,16 +42,6 @@ impl Extensions { } } -type AccountPublic = ::Signer; - -/// Helper function to generate an account ID from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - /// Get the chain spec for a specific parachain ID. /// The given accounts are initialized with funds in addition /// to the default known accounts. @@ -106,42 +88,11 @@ pub fn testnet_genesis_with_default_endowed( mut extra_endowed_accounts: Vec, self_para_id: Option, ) -> serde_json::Value { - let mut endowed = vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ]; + let mut endowed = Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect::>(); endowed.append(&mut extra_endowed_accounts); - let invulnerables = vec![ - get_collator_keys_from_seed::("Alice"), - get_collator_keys_from_seed::("Bob"), - get_collator_keys_from_seed::("Charlie"), - get_collator_keys_from_seed::("Dave"), - get_collator_keys_from_seed::("Eve"), - get_collator_keys_from_seed::("Ferdie"), - ]; - testnet_genesis( - get_account_id_from_seed::("Alice"), - invulnerables, - endowed, - self_para_id, - ) -} - -/// Generate collator keys from seed. -/// -/// This function's return type must always match the session keys of the chain in tuple format. -pub fn get_collator_keys_from_seed(seed: &str) -> ::Public { - get_from_seed::(seed) + let invulnerables = + Sr25519Keyring::invulnerable().map(|k| k.public().into()).collect::>(); + testnet_genesis(Sr25519Keyring::Alice.to_account_id(), invulnerables, endowed, self_para_id) } /// Creates a local testnet genesis with endowed accounts. diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index bb2945dc267..b91246a7bda 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -49,7 +49,9 @@ pub use frame_system::{ pub use pallet_balances::AccountData; pub use pallet_message_queue; pub use sp_arithmetic::traits::Bounded; -pub use sp_core::{parameter_types, sr25519, storage::Storage, Pair}; +pub use sp_core::{ + crypto::get_public_from_string_or_panic, parameter_types, sr25519, storage::Storage, Pair, +}; pub use sp_crypto_hashing::blake2_256; pub use sp_io::TestExternalities; pub use sp_runtime::BoundedSlice; @@ -226,7 +228,7 @@ pub trait Chain: TestExt { type OriginCaller; fn account_id_of(seed: &str) -> AccountId { - helpers::get_account_id_from_seed::(seed) + get_public_from_string_or_panic::(seed).into() } fn account_data_of(account: AccountIdOf) -> AccountData; @@ -1608,17 +1610,4 @@ pub mod helpers { ref_time_within && proof_size_within } - - /// Helper function to generate an account ID from seed. - pub fn get_account_id_from_seed(seed: &str) -> AccountId - where - sp_runtime::MultiSigner: - From<<::Pair as sp_core::Pair>::Public>, - { - use sp_runtime::traits::IdentifyAccount; - let pubkey = TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public(); - sp_runtime::MultiSigner::from(pubkey).into_account() - } } diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index fe360e7b8c7..3866c6950e0 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -27,10 +27,6 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "westend-native")] use westend_runtime as westend; -use polkadot_primitives::{AccountId, AccountPublic}; -use sp_core::{Pair, Public}; -use sp_runtime::traits::IdentifyAccount; - #[cfg(feature = "westend-native")] const WESTEND_STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; #[cfg(feature = "rococo-native")] @@ -257,18 +253,3 @@ pub fn versi_local_testnet_config() -> Result { .with_protocol_id("versi") .build()) } - -/// Helper function to generate a crypto pair from seed -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index 00d62af0b27..ae4e84b7725 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -21,13 +21,13 @@ use polkadot_primitives::{ AccountId, AssignmentId, SchedulerParams, ValidatorId, MAX_CODE_SIZE, MAX_POV_SIZE, }; use polkadot_service::chain_spec::Extensions; -pub use polkadot_service::chain_spec::{get_account_id_from_seed, get_from_seed}; use polkadot_test_runtime::BABE_GENESIS_EPOCH_CONFIG; use sc_chain_spec::{ChainSpec, ChainType}; use sc_consensus_grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; -use sp_core::sr25519; +use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; +use sp_keyring::Sr25519Keyring; use sp_runtime::Perbill; use test_runtime_constants::currency::DOTS; @@ -65,7 +65,7 @@ pub fn polkadot_local_testnet_config() -> PolkadotChainSpec { pub fn polkadot_local_testnet_genesis() -> serde_json::Value { polkadot_testnet_genesis( vec![get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -75,31 +75,18 @@ fn get_authority_keys_from_seed( seed: &str, ) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { ( - 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_public_from_string_or_panic::(&format!("{}//stash", seed)).into(), + get_public_from_string_or_panic::(seed).into(), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), ) } fn testnet_accounts() -> Vec { - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ] + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect() } /// Helper function to create polkadot `RuntimeGenesisConfig` for testing diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index cda6f3240dd..ad082f179b2 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -27,6 +27,7 @@ sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } +sp-keyring = { workspace = true } sp-npos-elections = { features = ["serde"], workspace = true } pallet-authorship = { workspace = true } diff --git a/polkadot/runtime/common/src/purchase.rs b/polkadot/runtime/common/src/purchase.rs index 9cbb907536d..cec92540654 100644 --- a/polkadot/runtime/common/src/purchase.rs +++ b/polkadot/runtime/common/src/purchase.rs @@ -479,7 +479,8 @@ where mod tests { use super::*; - use sp_core::{crypto::AccountId32, ed25519, Pair, Public, H256}; + use sp_core::{crypto::AccountId32, H256}; + use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use crate::purchase; @@ -488,10 +489,9 @@ mod tests { traits::{Currency, WithdrawReasons}, }; use sp_runtime::{ - traits::{BlakeTwo256, Dispatchable, IdentifyAccount, Identity, IdentityLookup, Verify}, + traits::{BlakeTwo256, Dispatchable, Identity, IdentityLookup}, ArithmeticError, BuildStorage, DispatchError::BadOrigin, - MultiSignature, }; type Block = frame_system::mocking::MockBlock; @@ -602,33 +602,16 @@ mod tests { Balances::make_free_balance_be(&payment_account(), 100_000); } - type AccountPublic = ::Signer; - - /// Helper function to generate a crypto pair from seed - fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() - } - - /// Helper function to generate an account ID from seed - fn get_account_id_from_seed(seed: &str) -> AccountId - where - AccountPublic: From<::Public>, - { - AccountPublic::from(get_from_seed::(seed)).into_account() - } - fn alice() -> AccountId { - get_account_id_from_seed::("Alice") + Sr25519Keyring::Alice.to_account_id() } fn alice_ed25519() -> AccountId { - get_account_id_from_seed::("Alice") + Ed25519Keyring::Alice.to_account_id() } fn bob() -> AccountId { - get_account_id_from_seed::("Bob") + Sr25519Keyring::Bob.to_account_id() } fn alice_signature() -> [u8; 64] { diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 4aaaf94da58..7becf6376c3 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -42,6 +42,7 @@ sp-storage = { workspace = true } sp-version = { workspace = true } sp-transaction-pool = { workspace = true } sp-block-builder = { workspace = true } +sp-keyring = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index c237dfd967f..d609548aed2 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -23,30 +23,15 @@ use crate::{ #[cfg(not(feature = "std"))] use alloc::format; use alloc::{vec, vec::Vec}; -use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, SchedulerParams, ValidatorId}; +use polkadot_primitives::{AccountId, AssignmentId, SchedulerParams, ValidatorId}; use rococo_runtime_constants::currency::UNITS as ROC; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_consensus_grandpa::AuthorityId as GrandpaId; -use sp_core::{sr25519, Pair, Public}; +use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; use sp_genesis_builder::PresetId; -use sp_runtime::traits::IdentifyAccount; - -/// Helper function to generate a crypto pair from seed -fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed -fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} +use sp_keyring::Sr25519Keyring; /// Helper function to generate stash, controller and session key from seed fn get_authority_keys_from_seed( @@ -62,7 +47,16 @@ fn get_authority_keys_from_seed( BeefyId, ) { let keys = get_authority_keys_from_seed_no_beefy(seed); - (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, get_from_seed::(seed)) + ( + keys.0, + keys.1, + keys.2, + keys.3, + keys.4, + keys.5, + keys.6, + get_public_from_string_or_panic::(seed), + ) } /// Helper function to generate stash, controller and session key from seed @@ -70,31 +64,18 @@ fn get_authority_keys_from_seed_no_beefy( seed: &str, ) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { ( - 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_public_from_string_or_panic::(&format!("{}//stash", seed)).into(), + get_public_from_string_or_panic::(seed).into(), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), ) } fn testnet_accounts() -> Vec { - Vec::from([ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ]) + Sr25519Keyring::well_known().map(|x| x.to_account_id()).collect() } fn rococo_session_keys( @@ -478,7 +459,7 @@ fn rococo_staging_testnet_config_genesis() -> serde_json::Value { fn rococo_development_config_genesis() -> serde_json::Value { rococo_testnet_genesis( Vec::from([get_authority_keys_from_seed("Alice")]), - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -487,7 +468,7 @@ fn rococo_development_config_genesis() -> serde_json::Value { fn rococo_local_testnet_genesis() -> serde_json::Value { rococo_testnet_genesis( Vec::from([get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")]), - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -502,7 +483,7 @@ fn versi_local_testnet_genesis() -> serde_json::Value { get_authority_keys_from_seed("Charlie"), get_authority_keys_from_seed("Dave"), ]), - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 16bec1a7702..31c04c26ece 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -42,6 +42,7 @@ sp-version = { workspace = true } sp-transaction-pool = { workspace = true } sp-block-builder = { workspace = true } sp-npos-elections = { workspace = true } +sp-keyring = { workspace = true } frame-election-provider-support = { workspace = true } frame-executive = { workspace = true } diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index f59bacce31b..621ef38f0b7 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -24,31 +24,17 @@ use crate::{ use alloc::format; use alloc::{vec, vec::Vec}; use pallet_staking::{Forcing, StakerStatus}; -use polkadot_primitives::{AccountId, AccountPublic, AssignmentId, SchedulerParams, ValidatorId}; +use polkadot_primitives::{AccountId, AssignmentId, SchedulerParams, ValidatorId}; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_consensus_grandpa::AuthorityId as GrandpaId; -use sp_core::{sr25519, Pair, Public}; +use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; use sp_genesis_builder::PresetId; -use sp_runtime::{traits::IdentifyAccount, Perbill}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::Perbill; use westend_runtime_constants::currency::UNITS as WND; -/// Helper function to generate a crypto pair from seed -fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed -fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - /// Helper function to generate stash, controller and session key from seed fn get_authority_keys_from_seed( seed: &str, @@ -63,7 +49,16 @@ fn get_authority_keys_from_seed( BeefyId, ) { let keys = get_authority_keys_from_seed_no_beefy(seed); - (keys.0, keys.1, keys.2, keys.3, keys.4, keys.5, keys.6, get_from_seed::(seed)) + ( + keys.0, + keys.1, + keys.2, + keys.3, + keys.4, + keys.5, + keys.6, + get_public_from_string_or_panic::(seed), + ) } /// Helper function to generate stash, controller and session key from seed @@ -71,31 +66,18 @@ fn get_authority_keys_from_seed_no_beefy( seed: &str, ) -> (AccountId, AccountId, BabeId, GrandpaId, ValidatorId, AssignmentId, AuthorityDiscoveryId) { ( - 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_public_from_string_or_panic::(&format!("{}//stash", seed)).into(), + get_public_from_string_or_panic::(seed).into(), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), ) } fn testnet_accounts() -> Vec { - Vec::from([ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ]) + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect() } fn westend_session_keys( @@ -420,7 +402,7 @@ fn westend_staging_testnet_config_genesis() -> serde_json::Value { fn westend_development_config_genesis() -> serde_json::Value { westend_testnet_genesis( Vec::from([get_authority_keys_from_seed("Alice")]), - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -429,7 +411,7 @@ fn westend_development_config_genesis() -> serde_json::Value { fn westend_local_testnet_genesis() -> serde_json::Value { westend_testnet_genesis( Vec::from([get_authority_keys_from_seed("Alice"), get_authority_keys_from_seed("Bob")]), - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 6f44cc0a75d..020a4a54285 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -23,8 +23,7 @@ use polkadot_test_client::{ TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; -use polkadot_test_service::{chain_spec::get_account_id_from_seed, construct_extrinsic}; -use sp_core::sr25519; +use polkadot_test_service::construct_extrinsic; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; @@ -342,7 +341,7 @@ fn deposit_reserve_asset_works_for_any_xcm_sender() { 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_id = sp_keyring::Sr25519Keyring::Alice.to_account_id(); 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 diff --git a/prdoc/pr_5804.prdoc b/prdoc/pr_5804.prdoc new file mode 100644 index 00000000000..beef83860cc --- /dev/null +++ b/prdoc/pr_5804.prdoc @@ -0,0 +1,42 @@ +# 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: Refactor get_account_id_from_seed / get_from_seed to one common place + +doc: + - audience: Runtime Dev + description: | + `get_account_id_from_seed / get_from_seed` were copied all over the place. This PR removes unnecessary code duplication. + `Keyring::iter()` provides the same functionality and is used instead. + +crates: + - name: polkadot-runtime-common + bump: patch + - name: polkadot-service + bump: major + - name: sp-keyring + bump: major + - name: rococo-runtime + bump: patch + - name: westend-runtime + bump: patch + - name: parachains-common + bump: major + - name: emulated-integration-tests-common + bump: major + - name: xcm-emulator + bump: major + - name: asset-hub-rococo-runtime + bump: patch + - name: asset-hub-westend-runtime + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + - name: collectives-westend-runtime + bump: patch + - name: polkadot-parachain-bin + bump: patch + - name: sp-core + bump: patch diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 04dcb909b8c..2e53c18645f 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -155,6 +155,7 @@ wait-timeout = { workspace = true } wat = { workspace = true } serde_json = { workspace = true, default-features = true } scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } +sp-keyring = { workspace = true } pretty_assertions.workspace = true # These testing-only dependencies are not exported by the Polkadot-SDK crate: @@ -172,12 +173,7 @@ polkadot-sdk = { features = ["frame-benchmarking-cli", "sc-cli", "sc-storage-mon [features] default = ["cli"] -cli = [ - "clap", - "clap_complete", - "node-inspect", - "polkadot-sdk", -] +cli = ["clap", "clap_complete", "node-inspect", "polkadot-sdk"] runtime-benchmarks = [ "kitchensink-runtime/runtime-benchmarks", "node-inspect?/runtime-benchmarks", @@ -188,10 +184,7 @@ try-runtime = [ "polkadot-sdk/try-runtime", "substrate-cli-test-utils/try-runtime", ] -riscv = [ - "kitchensink-runtime/riscv", - "polkadot-sdk/riscv", -] +riscv = ["kitchensink-runtime/riscv", "polkadot-sdk/riscv"] [[bench]] name = "transaction_pool" diff --git a/substrate/bin/node/cli/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index bc7821bfcf3..6cd9adfd7f2 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -32,18 +32,17 @@ use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_consensus_grandpa::AuthorityId as GrandpaId; -use sp_core::{crypto::UncheckedInto, sr25519, Pair, Public}; -use sp_mixnet::types::AuthorityId as MixnetId; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, - Perbill, +use sp_core::{ + crypto::{get_public_from_string_or_panic, UncheckedInto}, + sr25519, }; +use sp_keyring::Sr25519Keyring; +use sp_mixnet::types::AuthorityId as MixnetId; +use sp_runtime::Perbill; pub use kitchensink_runtime::RuntimeGenesisConfig; pub use node_primitives::{AccountId, Balance, Signature}; -type AccountPublic = ::Signer; - const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; const ENDOWMENT: Balance = 10_000_000 * DOLLARS; const STASH: Balance = ENDOWMENT / 1000; @@ -246,35 +245,20 @@ pub fn staging_testnet_config() -> ChainSpec { .build() } -/// Helper function to generate a crypto pair from seed. -pub fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -/// Helper function to generate an account ID from seed. -pub fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - /// Helper function to generate stash, controller and session key from seed. pub fn authority_keys_from_seed( seed: &str, ) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId, BeefyId) { ( - 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), + get_public_from_string_or_panic::(&format!("{}//stash", seed)).into(), + get_public_from_string_or_panic::(seed).into(), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), + get_public_from_string_or_panic::(seed), ) } @@ -307,22 +291,8 @@ fn configure_accounts( usize, Vec<(AccountId, AccountId, Balance, StakerStatus)>, ) { - let mut endowed_accounts: Vec = endowed_accounts.unwrap_or_else(|| { - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ] - }); + let mut endowed_accounts: Vec = endowed_accounts + .unwrap_or_else(|| Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect()); // endow all authorities and nominators. initial_authorities .iter() @@ -429,7 +399,7 @@ pub fn testnet_genesis( "society": { "pot": 0 }, "assets": { // This asset is used by the NIS pallet as counterpart currency. - "assets": vec![(9, get_account_id_from_seed::("Alice"), true, 1)], + "assets": vec![(9, Sr25519Keyring::Alice.to_account_id(), true, 1)], }, "nominationPools": { "minCreateBond": 10 * DOLLARS, @@ -442,7 +412,7 @@ fn development_config_genesis_json() -> serde_json::Value { testnet_genesis( vec![authority_keys_from_seed("Alice")], vec![], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -461,7 +431,7 @@ fn local_testnet_genesis() -> serde_json::Value { testnet_genesis( vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], vec![], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, ) } @@ -492,7 +462,7 @@ pub(crate) mod tests { .with_genesis_config_patch(testnet_genesis( vec![authority_keys_from_seed("Alice")], vec![], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::Alice.to_account_id(), None, )) .build() diff --git a/substrate/bin/node/testing/src/bench.rs b/substrate/bin/node/testing/src/bench.rs index 007d314684c..fb0504f8fad 100644 --- a/substrate/bin/node/testing/src/bench.rs +++ b/substrate/bin/node/testing/src/bench.rs @@ -47,7 +47,9 @@ use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy}; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_consensus::BlockOrigin; -use sp_core::{ed25519, sr25519, traits::SpawnNamed, Pair, Public}; +use sp_core::{ + crypto::get_public_from_string_or_panic, ed25519, sr25519, traits::SpawnNamed, Pair, +}; use sp_crypto_hashing::blake2_256; use sp_inherents::InherentData; use sp_runtime::{ @@ -288,10 +290,11 @@ impl<'a> Iterator for BlockContentIterator<'a> { } let sender = self.keyring.at(self.iteration); - let receiver = get_account_id_from_seed::(&format!( + let receiver = get_public_from_string_or_panic::(&format!( "random-user//{}", self.iteration - )); + )) + .into(); let signed = self.keyring.sign( CheckedExtrinsic { @@ -630,19 +633,6 @@ pub struct BenchContext { type AccountPublic = ::Signer; -fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) - .expect("static values are valid; qed") - .public() -} - -fn get_account_id_from_seed(seed: &str) -> AccountId -where - AccountPublic: From<::Public>, -{ - AccountPublic::from(get_from_seed::(seed)).into_account() -} - impl BenchContext { /// Import some block. pub fn import_block(&mut self, block: Block) { diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index eab088d9100..312fba8319b 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -20,52 +20,55 @@ use codec::Encode; use kitchensink_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; -use node_cli::chain_spec::get_from_seed; use node_primitives::{AccountId, Balance, Nonce}; -use sp_core::{ecdsa, ed25519, sr25519}; +use sp_core::{crypto::get_public_from_string_or_panic, ecdsa, ed25519, sr25519}; use sp_crypto_hashing::blake2_256; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_runtime::generic::Era; /// Alice's account id. pub fn alice() -> AccountId { - AccountKeyring::Alice.into() + Sr25519Keyring::Alice.into() } /// Bob's account id. pub fn bob() -> AccountId { - AccountKeyring::Bob.into() + Sr25519Keyring::Bob.into() } /// Charlie's account id. pub fn charlie() -> AccountId { - AccountKeyring::Charlie.into() + Sr25519Keyring::Charlie.into() } /// Dave's account id. pub fn dave() -> AccountId { - AccountKeyring::Dave.into() + Sr25519Keyring::Dave.into() } /// Eve's account id. pub fn eve() -> AccountId { - AccountKeyring::Eve.into() + Sr25519Keyring::Eve.into() } /// Ferdie's account id. pub fn ferdie() -> AccountId { - AccountKeyring::Ferdie.into() + Sr25519Keyring::Ferdie.into() } /// Convert keyrings into `SessionKeys`. +/// +/// # Panics +/// +/// Function will panic when invalid string is provided. pub fn session_keys_from_seed(seed: &str) -> SessionKeys { SessionKeys { - grandpa: get_from_seed::(seed).into(), - babe: get_from_seed::(seed).into(), - im_online: get_from_seed::(seed).into(), - authority_discovery: get_from_seed::(seed).into(), - mixnet: get_from_seed::(seed).into(), - beefy: get_from_seed::(seed).into(), + grandpa: get_public_from_string_or_panic::(seed).into(), + babe: get_public_from_string_or_panic::(seed).into(), + im_online: get_public_from_string_or_panic::(seed).into(), + authority_discovery: get_public_from_string_or_panic::(seed).into(), + mixnet: get_public_from_string_or_panic::(seed).into(), + beefy: get_public_from_string_or_panic::(seed).into(), } } @@ -105,7 +108,7 @@ pub fn sign( genesis_hash, metadata_hash, ); - let key = AccountKeyring::from_account_id(&signed).unwrap(); + let key = Sr25519Keyring::from_account_id(&signed).unwrap(); let signature = payload .using_encoded(|b| { diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index fd7fe776720..b04d94e2bf4 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -18,9 +18,9 @@ //! Cryptographic utilities. use crate::{ed25519, sr25519}; +use alloc::{format, str, vec::Vec}; #[cfg(all(not(feature = "std"), feature = "serde"))] -use alloc::{format, string::String, vec}; -use alloc::{str, vec::Vec}; +use alloc::{string::String, vec}; use bip39::{Language, Mnemonic}; use codec::{Decode, Encode, MaxEncodedLen}; use core::hash::Hash; @@ -419,6 +419,17 @@ pub fn set_default_ss58_version(new_default: Ss58AddressFormat) { DEFAULT_VERSION.store(new_default.into(), core::sync::atomic::Ordering::Relaxed); } +/// Interprets the string `s` in order to generate a public key without password. +/// +/// Function will panic when invalid string is provided. +pub fn get_public_from_string_or_panic( + s: &str, +) -> ::Public { + TPublic::Pair::from_string(&format!("//{}", s), None) + .expect("Function expects valid argument; qed") + .public() +} + #[cfg(feature = "std")] impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T { fn from_string(s: &str) -> Result { diff --git a/substrate/primitives/keyring/src/bandersnatch.rs b/substrate/primitives/keyring/src/bandersnatch.rs index 67fc5c47df6..64d3c314124 100644 --- a/substrate/primitives/keyring/src/bandersnatch.rs +++ b/substrate/primitives/keyring/src/bandersnatch.rs @@ -18,6 +18,8 @@ //! A set of well-known keys used for testing. pub use sp_core::bandersnatch; + +use crate::ParseKeyringError; #[cfg(feature = "std")] use sp_core::bandersnatch::Signature; use sp_core::{ @@ -27,7 +29,7 @@ use sp_core::{ }; extern crate alloc; -use alloc::{fmt, format, str::FromStr, string::String, vec::Vec}; +use alloc::{format, str::FromStr, string::String, vec::Vec}; /// Set of test accounts. #[derive( @@ -107,15 +109,6 @@ impl From for &'static str { } } -#[derive(Debug)] -pub struct ParseKeyringError; - -impl fmt::Display for ParseKeyringError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ParseKeyringError") - } -} - impl FromStr for Keyring { type Err = ParseKeyringError; diff --git a/substrate/primitives/keyring/src/ed25519.rs b/substrate/primitives/keyring/src/ed25519.rs index 98ca368e53c..235b5d5c993 100644 --- a/substrate/primitives/keyring/src/ed25519.rs +++ b/substrate/primitives/keyring/src/ed25519.rs @@ -18,6 +18,8 @@ //! Support code for the runtime. A set of test accounts. pub use sp_core::ed25519; + +use crate::ParseKeyringError; #[cfg(feature = "std")] use sp_core::ed25519::Signature; use sp_core::{ @@ -27,7 +29,7 @@ use sp_core::{ use sp_runtime::AccountId32; extern crate alloc; -use alloc::{format, string::String, vec::Vec}; +use alloc::{format, str::FromStr, string::String, vec::Vec}; /// Set of test accounts. #[derive( @@ -105,6 +107,14 @@ impl Keyring { pub fn to_seed(self) -> String { format!("//{}", self) } + + pub fn well_known() -> impl Iterator { + Self::iter().take(12) + } + + pub fn invulnerable() -> impl Iterator { + Self::iter().take(6) + } } impl From for &'static str { @@ -134,6 +144,30 @@ impl From for sp_runtime::MultiSigner { } } +impl FromStr for Keyring { + type Err = ParseKeyringError; + + fn from_str(s: &str) -> Result::Err> { + match s { + "Alice" | "alice" => Ok(Keyring::Alice), + "Bob" | "bob" => Ok(Keyring::Bob), + "Charlie" | "charlie" => Ok(Keyring::Charlie), + "Dave" | "dave" => Ok(Keyring::Dave), + "Eve" | "eve" => Ok(Keyring::Eve), + "Ferdie" | "ferdie" => Ok(Keyring::Ferdie), + "Alice//stash" | "alice//stash" => Ok(Keyring::AliceStash), + "Bob//stash" | "bob//stash" => Ok(Keyring::BobStash), + "Charlie//stash" | "charlie//stash" => Ok(Keyring::CharlieStash), + "Dave//stash" | "dave//stash" => Ok(Keyring::DaveStash), + "Eve//stash" | "eve//stash" => Ok(Keyring::EveStash), + "Ferdie//stash" | "ferdie//stash" => Ok(Keyring::FerdieStash), + "One" | "one" => Ok(Keyring::One), + "Two" | "two" => Ok(Keyring::Two), + _ => Err(ParseKeyringError), + } + } +} + impl From for Public { fn from(k: Keyring) -> Self { Public::from_raw(k.into()) @@ -221,4 +255,40 @@ mod tests { fn verify_static_public_keys() { assert!(Keyring::iter().all(|k| { k.pair().public().as_ref() == <[u8; 32]>::from(k) })); } + + #[test] + fn verify_well_known() { + assert_eq!( + Keyring::well_known().collect::>(), + vec![ + Keyring::Alice, + Keyring::Bob, + Keyring::Charlie, + Keyring::Dave, + Keyring::Eve, + Keyring::Ferdie, + Keyring::AliceStash, + Keyring::BobStash, + Keyring::CharlieStash, + Keyring::DaveStash, + Keyring::EveStash, + Keyring::FerdieStash + ] + ); + } + + #[test] + fn verify_invulnerable() { + assert_eq!( + Keyring::invulnerable().collect::>(), + vec![ + Keyring::Alice, + Keyring::Bob, + Keyring::Charlie, + Keyring::Dave, + Keyring::Eve, + Keyring::Ferdie + ] + ); + } } diff --git a/substrate/primitives/keyring/src/lib.rs b/substrate/primitives/keyring/src/lib.rs index f753bf4b0dd..008c01b150f 100644 --- a/substrate/primitives/keyring/src/lib.rs +++ b/substrate/primitives/keyring/src/lib.rs @@ -19,6 +19,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; +use alloc::fmt; + /// Test account crypto for sr25519. pub mod sr25519; @@ -42,3 +45,13 @@ pub mod test { /// The keyring for use with accounts when using the test runtime. pub use super::ed25519::Keyring as AccountKeyring; } + +#[derive(Debug)] +/// Represents an error that occurs when parsing a string into a `KeyRing`. +pub struct ParseKeyringError; + +impl fmt::Display for ParseKeyringError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ParseKeyringError") + } +} diff --git a/substrate/primitives/keyring/src/sr25519.rs b/substrate/primitives/keyring/src/sr25519.rs index a3a506152d7..5ff9056566b 100644 --- a/substrate/primitives/keyring/src/sr25519.rs +++ b/substrate/primitives/keyring/src/sr25519.rs @@ -18,6 +18,8 @@ //! Support code for the runtime. A set of test accounts. pub use sp_core::sr25519; + +use crate::ParseKeyringError; #[cfg(feature = "std")] use sp_core::sr25519::Signature; use sp_core::{ @@ -28,7 +30,7 @@ use sp_core::{ use sp_runtime::AccountId32; extern crate alloc; -use alloc::{fmt, format, str::FromStr, string::String, vec::Vec}; +use alloc::{format, str::FromStr, string::String, vec::Vec}; /// Set of test accounts. #[derive( @@ -116,6 +118,14 @@ impl Keyring { pub fn numeric_id(idx: usize) -> AccountId32 { (*Self::numeric(idx).public().as_array_ref()).into() } + + pub fn well_known() -> impl Iterator { + Self::iter().take(12) + } + + pub fn invulnerable() -> impl Iterator { + Self::iter().take(6) + } } impl From for &'static str { @@ -145,28 +155,25 @@ impl From for sp_runtime::MultiSigner { } } -#[derive(Debug)] -pub struct ParseKeyringError; - -impl fmt::Display for ParseKeyringError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ParseKeyringError") - } -} - impl FromStr for Keyring { type Err = ParseKeyringError; fn from_str(s: &str) -> Result::Err> { match s { - "alice" => Ok(Keyring::Alice), - "bob" => Ok(Keyring::Bob), - "charlie" => Ok(Keyring::Charlie), - "dave" => Ok(Keyring::Dave), - "eve" => Ok(Keyring::Eve), - "ferdie" => Ok(Keyring::Ferdie), - "one" => Ok(Keyring::One), - "two" => Ok(Keyring::Two), + "Alice" | "alice" => Ok(Keyring::Alice), + "Bob" | "bob" => Ok(Keyring::Bob), + "Charlie" | "charlie" => Ok(Keyring::Charlie), + "Dave" | "dave" => Ok(Keyring::Dave), + "Eve" | "eve" => Ok(Keyring::Eve), + "Ferdie" | "ferdie" => Ok(Keyring::Ferdie), + "Alice//stash" | "alice//stash" => Ok(Keyring::AliceStash), + "Bob//stash" | "bob//stash" => Ok(Keyring::BobStash), + "Charlie//stash" | "charlie//stash" => Ok(Keyring::CharlieStash), + "Dave//stash" | "dave//stash" => Ok(Keyring::DaveStash), + "Eve//stash" | "eve//stash" => Ok(Keyring::EveStash), + "Ferdie//stash" | "ferdie//stash" => Ok(Keyring::FerdieStash), + "One" | "one" => Ok(Keyring::One), + "Two" | "two" => Ok(Keyring::Two), _ => Err(ParseKeyringError), } } @@ -254,8 +261,45 @@ mod tests { &Keyring::Bob.public(), )); } + #[test] fn verify_static_public_keys() { assert!(Keyring::iter().all(|k| { k.pair().public().as_ref() == <[u8; 32]>::from(k) })); } + + #[test] + fn verify_well_known() { + assert_eq!( + Keyring::well_known().collect::>(), + vec![ + Keyring::Alice, + Keyring::Bob, + Keyring::Charlie, + Keyring::Dave, + Keyring::Eve, + Keyring::Ferdie, + Keyring::AliceStash, + Keyring::BobStash, + Keyring::CharlieStash, + Keyring::DaveStash, + Keyring::EveStash, + Keyring::FerdieStash + ] + ); + } + + #[test] + fn verify_invulnerable() { + assert_eq!( + Keyring::invulnerable().collect::>(), + vec![ + Keyring::Alice, + Keyring::Bob, + Keyring::Charlie, + Keyring::Dave, + Keyring::Eve, + Keyring::Ferdie + ] + ); + } } diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index c9c08608f3b..f1d33b4143e 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -17,14 +17,10 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = docify = { workspace = true } [dependencies] -codec = { features = [ - "derive", -], workspace = true } +codec = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } +scale-info = { features = ["derive"], workspace = true } smallvec = { workspace = true, default-features = true } docify = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index ac2aef734f4..322c91f4f13 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -8,10 +8,10 @@ use alloc::{vec, vec::Vec}; use polkadot_sdk::{staging_xcm as xcm, *}; use cumulus_primitives_core::ParaId; -use parachains_common::{genesis_config_helpers::*, AuraId}; +use parachains_common::AuraId; use serde_json::Value; -use sp_core::sr25519; use sp_genesis_builder::PresetId; +use sp_keyring::Sr25519Keyring; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; @@ -71,30 +71,11 @@ fn local_testnet_genesis() -> Value { testnet_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), - ], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), + Sr25519Keyring::Alice.to_account_id(), 1000.into(), ) } @@ -103,30 +84,11 @@ fn development_config_genesis() -> Value { testnet_genesis( // initial collators. vec![ - ( - get_account_id_from_seed::("Alice"), - get_collator_keys_from_seed::("Alice"), - ), - ( - get_account_id_from_seed::("Bob"), - get_collator_keys_from_seed::("Bob"), - ), - ], - vec![ - get_account_id_from_seed::("Alice"), - get_account_id_from_seed::("Bob"), - get_account_id_from_seed::("Charlie"), - get_account_id_from_seed::("Dave"), - get_account_id_from_seed::("Eve"), - get_account_id_from_seed::("Ferdie"), - get_account_id_from_seed::("Alice//stash"), - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - get_account_id_from_seed::("Dave//stash"), - get_account_id_from_seed::("Eve//stash"), - get_account_id_from_seed::("Ferdie//stash"), + (Sr25519Keyring::Alice.to_account_id(), Sr25519Keyring::Alice.public().into()), + (Sr25519Keyring::Bob.to_account_id(), Sr25519Keyring::Bob.public().into()), ], - get_account_id_from_seed::("Alice"), + Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect(), + Sr25519Keyring::Alice.to_account_id(), 1000.into(), ) } -- GitLab From 555c91b974b9869faaf096fd4878532d6e9c506f Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Thu, 17 Oct 2024 16:24:55 +0200 Subject: [PATCH 119/127] remove bogus code Signed-off-by: Andrei Sandu --- .../runtime/parachains/src/runtime_api_impl/v10.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs index 6004867f9ca..443afdb213e 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v10.rs @@ -452,15 +452,7 @@ pub fn backing_state( // Thus, minimum relay parent is ensured to have asynchronous backing enabled. let now = frame_system::Pallet::::block_number(); - // Workaround for issue #64. - let allowed_relay_parents = - if shared::Pallet::::on_chain_storage_version() == StorageVersion::new(0) { - shared::migration::v0::AllowedRelayParents::::get().into() - } else { - shared::AllowedRelayParents::::get() - }; - - let min_relay_parent_number = allowed_relay_parents + let min_relay_parent_number = shared::AllowedRelayParents::::get() .hypothetical_earliest_block_number(now, config.async_backing_params.allowed_ancestry_len); let required_parent = paras::Heads::::get(para_id)?; -- GitLab From 4b2c06fc1397ed6f94df99248c337ae159bff838 Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Thu, 17 Oct 2024 17:43:51 +0200 Subject: [PATCH 120/127] fix build Signed-off-by: Andrei Sandu --- polkadot/node/core/approval-voting/src/lib.rs | 2 +- polkadot/node/core/backing/src/lib.rs | 4 ++-- polkadot/node/core/candidate-validation/src/lib.rs | 2 +- .../node/core/dispute-coordinator/src/participation/mod.rs | 2 +- polkadot/node/subsystem-types/src/messages.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 5ecb07941d0..18e98c5a9d9 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -54,7 +54,7 @@ use polkadot_node_subsystem_util::{ use polkadot_primitives::{ vstaging::CandidateReceiptV2 as CandidateReceipt, ApprovalVoteMultipleCandidates, ApprovalVotingParams, BlockNumber, CandidateHash, CandidateIndex, CoreIndex, ExecutorParams, - GroupIndex, Hash, PvfExecKind, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, + GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, }; use sc_keystore::LocalKeystore; diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index e0b21bfc9e0..d4e742495be 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -114,7 +114,7 @@ use polkadot_primitives::{ CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, CandidateCommitments, CandidateHash, CoreIndex, ExecutorParams, GroupIndex, GroupRotationInfo, - Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, PvfExecKind, + Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, SessionIndex, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, }; @@ -631,7 +631,7 @@ async fn request_candidate_validation( executor_params: ExecutorParams, ) -> Result { let (tx, rx) = oneshot::channel(); - let is_system = candidate_receipt.descriptor.para_id.is_system(); + let is_system = candidate_receipt.descriptor.para_id().is_system(); sender .send_message(CandidateValidationMessage::ValidateFromExhaustive { diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 78ab2a1b027..b264054e60e 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -50,7 +50,7 @@ use polkadot_primitives::{ CandidateReceiptV2 as CandidateReceipt, }, AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, OccupiedCoreAssumption, - PersistedValidationData, PvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, + PersistedValidationData, PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, }; use sp_application_crypto::{AppCrypto, ByteArray}; diff --git a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs index 1cfac560ded..770c44f7d60 100644 --- a/polkadot/node/core/dispute-coordinator/src/participation/mod.rs +++ b/polkadot/node/core/dispute-coordinator/src/participation/mod.rs @@ -33,7 +33,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::runtime::get_validation_code_by_hash; use polkadot_primitives::{ vstaging::CandidateReceiptV2 as CandidateReceipt, BlockNumber, CandidateHash, Hash, - PvfExecKind, SessionIndex, + SessionIndex, }; use crate::LOG_TARGET; diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 98c6b3ea5c6..e4cdb4a1f01 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -52,7 +52,7 @@ use polkadot_primitives::{ CandidateIndex, CollatorId, CoreIndex, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, PvfExecKind, SessionIndex, SessionInfo, + PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; -- GitLab From 320318ab4aef495e0861b89730dceca0b16e7716 Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Thu, 17 Oct 2024 17:55:26 +0200 Subject: [PATCH 121/127] fmt and feedback Signed-off-by: Andrei Sandu --- polkadot/node/core/approval-voting/src/lib.rs | 4 ++-- polkadot/node/core/backing/src/lib.rs | 4 ++-- polkadot/node/core/candidate-validation/src/lib.rs | 4 ++-- .../collator-protocol/src/validator_side/collation.rs | 5 ----- polkadot/node/subsystem-types/src/messages.rs | 6 +++--- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 18e98c5a9d9..2176cc7675b 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -54,8 +54,8 @@ use polkadot_node_subsystem_util::{ use polkadot_primitives::{ vstaging::CandidateReceiptV2 as CandidateReceipt, ApprovalVoteMultipleCandidates, ApprovalVotingParams, BlockNumber, CandidateHash, CandidateIndex, CoreIndex, ExecutorParams, - GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, - ValidatorPair, ValidatorSignature, + GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, + ValidatorSignature, }; use sc_keystore::LocalKeystore; use sp_application_crypto::Pair; diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index d4e742495be..b5362d32ad8 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -114,8 +114,8 @@ use polkadot_primitives::{ CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, CandidateCommitments, CandidateHash, CoreIndex, ExecutorParams, GroupIndex, GroupRotationInfo, - Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, - SessionIndex, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, + Hash, Id as ParaId, IndexedVec, NodeFeatures, PersistedValidationData, SessionIndex, + SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, }; use polkadot_statement_table::{ diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index b264054e60e..72a8d894d34 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -50,8 +50,8 @@ use polkadot_primitives::{ CandidateReceiptV2 as CandidateReceipt, }, AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, OccupiedCoreAssumption, - PersistedValidationData, PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, - ValidationCodeHash, ValidatorId, + PersistedValidationData, PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, + ValidationCode, ValidationCodeHash, ValidatorId, }; use sp_application_crypto::{AppCrypto, ByteArray}; use sp_keystore::KeystorePtr; diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index fe660b313bf..0b3e9f4b343 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -71,10 +71,6 @@ pub struct FetchedCollation { pub para_id: ParaId, /// Candidate hash. pub candidate_hash: CandidateHash, - /// Id of the collator the collation was fetched from. - /// This will be missing when we create the struct from - /// a v2 candidate receipt. instance - pub collator_id: Option, } impl From<&CandidateReceipt> for FetchedCollation { @@ -84,7 +80,6 @@ impl From<&CandidateReceipt> for FetchedCollation { relay_parent: descriptor.relay_parent(), para_id: descriptor.para_id(), candidate_hash: receipt.hash(), - collator_id: descriptor.collator(), } } } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index e4cdb4a1f01..ba1ba5755be 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -52,9 +52,9 @@ use polkadot_primitives::{ CandidateIndex, CollatorId, CoreIndex, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, - SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, ValidatorSignature, + PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, + SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; use polkadot_statement_table::v2::Misbehavior; use std::{ -- GitLab From dfde71ffda824635ea9db3e72fbdff09cc689a6b Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Mon, 21 Oct 2024 15:23:43 +0300 Subject: [PATCH 122/127] more fixes Signed-off-by: Andrei Sandu --- polkadot/node/core/backing/src/tests/mod.rs | 24 +++++++++---------- .../src/tests/prospective_parachains.rs | 2 +- polkadot/node/overseer/src/tests.rs | 4 ++-- polkadot/primitives/src/vstaging/mod.rs | 2 +- prdoc/pr_5679.prdoc | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 46d8e1ddd3b..dbb974a634f 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -22,7 +22,7 @@ use polkadot_node_primitives::{BlockData, InvalidCandidate, SignedFullStatement, use polkadot_node_subsystem::{ errors::RuntimeApiError, messages::{ - AllMessages, CollatorProtocolMessage, RuntimeApiMessage, RuntimeApiRequest, + AllMessages, CollatorProtocolMessage, PvfExecKind, RuntimeApiMessage, RuntimeApiRequest, ValidationFailed, }, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, TimeoutExt, @@ -30,7 +30,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ node_features, vstaging::MutateDescriptorV2, CandidateDescriptor, GroupRotationInfo, HeadData, - PersistedValidationData, PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, + PersistedValidationData, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; use polkadot_primitives_test_helpers::{ dummy_candidate_receipt_bad_sig, dummy_collator, dummy_collator_signature, @@ -434,7 +434,7 @@ async fn assert_validate_from_exhaustive( }, ) if validation_data == *assert_pvd && validation_code == *assert_validation_code && - *pov == *assert_pov && &candidate_receipt.descriptor == assert_candidate.descriptor && + *pov == *assert_pov && candidate_receipt.descriptor == assert_candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_receipt.commitments_hash == assert_candidate.commitments.hash() => { @@ -651,7 +651,7 @@ fn backing_works(#[case] elastic_scaling_mvp: bool) { }, ) if validation_data == pvd_ab && validation_code == validation_code_ab && - *pov == pov_ab && &candidate_receipt.descriptor == candidate_a.descriptor && + *pov == pov_ab && candidate_receipt.descriptor == candidate_a.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_receipt.commitments_hash == candidate_a_commitments_hash => { @@ -1287,7 +1287,7 @@ fn backing_works_while_validation_ongoing() { }, ) if validation_data == pvd_abc && validation_code == validation_code_abc && - *pov == pov_abc && &candidate_receipt.descriptor == candidate_a.descriptor && + *pov == pov_abc && candidate_receipt.descriptor == candidate_a.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { @@ -1454,7 +1454,7 @@ fn backing_misbehavior_works() { }, ) if validation_data == pvd_a && validation_code == validation_code_a && - *pov == pov_a && &candidate_receipt.descriptor == candidate_a.descriptor && + *pov == pov_a && candidate_receipt.descriptor == candidate_a.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_a_commitments_hash == candidate_receipt.commitments_hash => { @@ -1621,7 +1621,7 @@ fn backing_dont_second_invalid() { }, ) if validation_data == pvd_a && validation_code == validation_code_a && - *pov == pov_block_a && &candidate_receipt.descriptor == candidate_a.descriptor && + *pov == pov_block_a && candidate_receipt.descriptor == candidate_a.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_a.commitments.hash() == candidate_receipt.commitments_hash => { @@ -1661,7 +1661,7 @@ fn backing_dont_second_invalid() { }, ) if validation_data == pvd_b && validation_code == validation_code_b && - *pov == pov_block_b && &candidate_receipt.descriptor == candidate_b.descriptor && + *pov == pov_block_b && candidate_receipt.descriptor == candidate_b.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_b.commitments.hash() == candidate_receipt.commitments_hash => { @@ -1788,7 +1788,7 @@ fn backing_second_after_first_fails_works() { }, ) if validation_data == pvd_a && validation_code == validation_code_a && - *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor && + *pov == pov_a && candidate_receipt.descriptor == candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { @@ -1932,7 +1932,7 @@ fn backing_works_after_failed_validation() { }, ) if validation_data == pvd_a && validation_code == validation_code_a && - *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor && + *pov == pov_a && candidate_receipt.descriptor == candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { @@ -2211,7 +2211,7 @@ fn retry_works() { }, ) if validation_data == pvd_a && validation_code == validation_code_a && - *pov == pov_a && &candidate_receipt.descriptor == candidate.descriptor && + *pov == pov_a && candidate_receipt.descriptor == candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash ); @@ -2753,7 +2753,7 @@ fn validator_ignores_statements_from_disabled_validators() { } ) if validation_data == pvd && validation_code == expected_validation_code && - *pov == expected_pov && &candidate_receipt.descriptor == candidate.descriptor && + *pov == expected_pov && candidate_receipt.descriptor == candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate_commitments_hash == candidate_receipt.commitments_hash => { diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index 5dd0d888f21..caddd240805 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -275,7 +275,7 @@ async fn assert_validate_seconded_candidate( }) if &validation_data == assert_pvd && &validation_code == assert_validation_code && &*pov == assert_pov && - &candidate_receipt.descriptor == candidate.descriptor && + candidate_receipt.descriptor == candidate.descriptor && exec_kind == PvfExecKind::BackingSystemParas && candidate.commitments.hash() == candidate_receipt.commitments_hash => { diff --git a/polkadot/node/overseer/src/tests.rs b/polkadot/node/overseer/src/tests.rs index a19009ebbda..c3c47335cd3 100644 --- a/polkadot/node/overseer/src/tests.rs +++ b/polkadot/node/overseer/src/tests.rs @@ -29,11 +29,11 @@ use polkadot_node_subsystem_types::messages::{ }; use polkadot_primitives::{ vstaging::CandidateReceiptV2, CandidateHash, CollatorPair, Id as ParaId, - InvalidDisputeStatementKind, PvfExecKind, SessionIndex, ValidDisputeStatementKind, + InvalidDisputeStatementKind, PersistedValidationData, SessionIndex, ValidDisputeStatementKind, ValidatorIndex, }; use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor, dummy_candidate_receipt_v2, dummy_hash, + dummy_candidate_descriptor, dummy_candidate_receipt_v2, dummy_hash, dummy_validation_code, }; use crate::{ diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 01ed8fcd5e1..265fcd899d7 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -471,7 +471,7 @@ pub enum CandidateReceiptError { macro_rules! impl_getter { ($field:ident, $type:ident) => { - /// Returns the value of `$field`` field. + /// Returns the value of `$field` field. pub fn $field(&self) -> $type { self.$field } diff --git a/prdoc/pr_5679.prdoc b/prdoc/pr_5679.prdoc index 988d6408cec..08656600116 100644 --- a/prdoc/pr_5679.prdoc +++ b/prdoc/pr_5679.prdoc @@ -4,7 +4,7 @@ doc: - audience: Node Dev description: | This change is just plumbing work and updates all crate interfaces to use the new primitives. - Id doesn't alter any functionality and is required before implementing RFC103 on the + It doesn't alter any functionality and is required before implementing RFC103 on the node side. crates: -- GitLab From cb93bb62f4d3462e400ba1f1a268d15566291158 Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Mon, 21 Oct 2024 15:24:42 +0300 Subject: [PATCH 123/127] delete PRDoc Signed-off-by: Andrei Sandu --- prdoc/pr_5679.prdoc | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 prdoc/pr_5679.prdoc diff --git a/prdoc/pr_5679.prdoc b/prdoc/pr_5679.prdoc deleted file mode 100644 index 08656600116..00000000000 --- a/prdoc/pr_5679.prdoc +++ /dev/null @@ -1,42 +0,0 @@ -title: "Switch to new `CandidateReceipt` primitives" - -doc: - - audience: Node Dev - description: | - This change is just plumbing work and updates all crate interfaces to use the new primitives. - It doesn't alter any functionality and is required before implementing RFC103 on the - node side. - -crates: - - name: polkadot-primitives - bump: minor - - name: polkadot-runtime-parachains - bump: minor - - name: cumulus-client-consensus-common - bump: major - - name: cumulus-client-pov-recovery - bump: major - - name: cumulus-relay-chain-interface - bump: major - - name: polkadot-node-core-candidate-validation - bump: major - - name: polkadot-node-core-dispute-coordinator - bump: major - - name: polkadot-node-core-parachains-inherent - bump: major - - name: polkadot-node-core-prospective-parachains - bump: major - - name: polkadot-node-core-provisioner - bump: major - - name: polkadot-node-core-runtime-api - bump: major - - name: cumulus-client-network - bump: major - - name: cumulus-relay-chain-inprocess-interface - bump: major - - name: cumulus-client-consensus-aura - bump: major - - name: cumulus-relay-chain-minimal-node - bump: major - - name: cumulus-relay-chain-rpc-interface - bump: major -- GitLab From a4025099a71555513314d52ec7ad762ad4ec8e7b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Mon, 21 Oct 2024 12:30:48 +0000 Subject: [PATCH 124/127] Update from sandreim running command 'prdoc --audience node_dev runtime_dev --bump major' --- prdoc/pr_5679.prdoc | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 prdoc/pr_5679.prdoc diff --git a/prdoc/pr_5679.prdoc b/prdoc/pr_5679.prdoc new file mode 100644 index 00000000000..9c8de999c8f --- /dev/null +++ b/prdoc/pr_5679.prdoc @@ -0,0 +1,85 @@ +title: Switch node side to v2 candidate receipts +doc: +- audience: + - Node Dev + - Runtime Dev + description: |- + on top of https://github.com/paritytech/polkadot-sdk/pull/5423 + + This PR implements the plumbing work required for https://github.com/paritytech/polkadot-sdk/issues/5047 . I also added additional helper methods gated by feature "test" in primitives. + + TODO: + - [x] PRDoc +crates: +- name: polkadot-primitives + bump: major +- name: polkadot-runtime-parachains + bump: major +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: cumulus-relay-chain-inprocess-interface + bump: major +- name: polkadot-service + bump: major +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot + bump: major +- name: cumulus-client-consensus-aura + bump: major +- name: cumulus-client-network + bump: major +- name: cumulus-client-pov-recovery + bump: major +- name: cumulus-relay-chain-interface + bump: major +- name: cumulus-relay-chain-minimal-node + bump: major +- name: cumulus-relay-chain-rpc-interface + bump: major +- name: polkadot-node-collation-generation + bump: major +- name: polkadot-node-core-approval-voting + bump: major +- name: polkadot-node-core-av-store + bump: major +- name: polkadot-node-core-backing + bump: major +- name: polkadot-node-core-bitfield-signing + bump: major +- name: polkadot-node-core-candidate-validation + bump: major +- name: polkadot-node-core-dispute-coordinator + bump: major +- name: polkadot-node-core-parachains-inherent + bump: major +- name: polkadot-node-core-prospective-parachains + bump: major +- name: polkadot-node-core-provisioner + bump: major +- name: polkadot-node-core-runtime-api + bump: major +- name: polkadot-availability-distribution + bump: major +- name: polkadot-availability-recovery + bump: major +- name: polkadot-collator-protocol + bump: major +- name: polkadot-dispute-distribution + bump: major +- name: polkadot-node-network-protocol + bump: major +- name: polkadot-statement-distribution + bump: major +- name: polkadot-node-primitives + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: polkadot-statement-table + bump: major +- name: polkadot-overseer + bump: major +- name: cumulus-client-consensus-common + bump: major -- GitLab From 6371454b62c4d9db15e8c62d9c4eaec5b992f7dd Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Mon, 21 Oct 2024 15:41:26 +0300 Subject: [PATCH 125/127] adjust generated PRDoc Signed-off-by: Andrei Sandu --- prdoc/pr_5679.prdoc | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/prdoc/pr_5679.prdoc b/prdoc/pr_5679.prdoc index 9c8de999c8f..59c36ecb933 100644 --- a/prdoc/pr_5679.prdoc +++ b/prdoc/pr_5679.prdoc @@ -1,34 +1,29 @@ -title: Switch node side to v2 candidate receipts +title: Switch to new `CandidateReceipt` primitives doc: - audience: - Node Dev - Runtime Dev - description: |- - on top of https://github.com/paritytech/polkadot-sdk/pull/5423 - - This PR implements the plumbing work required for https://github.com/paritytech/polkadot-sdk/issues/5047 . I also added additional helper methods gated by feature "test" in primitives. - - TODO: - - [x] PRDoc + description: | + This change is just plumbing work and updates all crate interfaces to use the new primitives. + It doesn't alter any functionality and is required before implementing RFC103 on the + node side. crates: - name: polkadot-primitives bump: major - name: polkadot-runtime-parachains - bump: major + bump: patch - name: rococo-runtime - bump: major + bump: patch - name: westend-runtime - bump: major + bump: patch - name: cumulus-relay-chain-inprocess-interface bump: major - name: polkadot-service - bump: major + bump: patch - name: polkadot-node-subsystem-types bump: major - name: polkadot - bump: major -- name: cumulus-client-consensus-aura - bump: major + bump: patch - name: cumulus-client-network bump: major - name: cumulus-client-pov-recovery @@ -80,6 +75,6 @@ crates: - name: polkadot-statement-table bump: major - name: polkadot-overseer - bump: major + bump: patch - name: cumulus-client-consensus-common bump: major -- GitLab From 9c5097d845b5139f0e9f3711159e0741ae4369b9 Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Tue, 22 Oct 2024 13:19:50 +0300 Subject: [PATCH 126/127] fix build again Signed-off-by: Andrei Sandu --- polkadot/node/core/candidate-validation/src/lib.rs | 2 +- .../node/network/availability-distribution/src/requester/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 72a8d894d34..551f6a507c8 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -49,7 +49,7 @@ use polkadot_primitives::{ CandidateDescriptorV2 as CandidateDescriptor, CandidateEvent, CandidateReceiptV2 as CandidateReceipt, }, - AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, OccupiedCoreAssumption, + AuthorityDiscoveryId, CandidateCommitments, ExecutorParams, Hash, PersistedValidationData, PvfExecKind as RuntimePvfExecKind, PvfPrepKind, SessionIndex, ValidationCode, ValidationCodeHash, ValidatorId, }; diff --git a/polkadot/node/network/availability-distribution/src/requester/mod.rs b/polkadot/node/network/availability-distribution/src/requester/mod.rs index d39a0c07ac8..79658591b6a 100644 --- a/polkadot/node/network/availability-distribution/src/requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/requester/mod.rs @@ -39,7 +39,7 @@ use polkadot_node_subsystem_util::{ runtime::{get_occupied_cores, RuntimeInfo}, }; use polkadot_primitives::{ - vstaging::OccupiedCore, BlockNumber, CandidateHash, CoreIndex, Hash, SessionIndex, + vstaging::OccupiedCore, CandidateHash, CoreIndex, Hash, SessionIndex, }; use super::{FatalError, Metrics, Result, LOG_TARGET}; -- GitLab From b2818f3e195cc7bf30d81ccba3d1be5d37f4d616 Mon Sep 17 00:00:00 2001 From: Andrei Sandu Date: Tue, 22 Oct 2024 13:45:26 +0300 Subject: [PATCH 127/127] fix test Signed-off-by: Andrei Sandu --- polkadot/node/core/candidate-validation/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 9d927440f25..e679f34792d 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -27,7 +27,7 @@ use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ vstaging::CandidateDescriptorV2, CandidateDescriptor, CoreIndex, GroupIndex, HeadData, - Id as ParaId, SessionInfo, UpwardMessage, ValidatorId, + Id as ParaId, SessionInfo, UpwardMessage, ValidatorId, OccupiedCoreAssumption }; use polkadot_primitives_test_helpers::{ dummy_collator, dummy_collator_signature, dummy_hash, make_valid_candidate_descriptor, -- GitLab
Commits