From c8380735ae3ff7a5b415c39936f145ef5b9d7671 Mon Sep 17 00:00:00 2001 From: Igor Papandinas <26460174+ipapandinas@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:19:52 +0000 Subject: [PATCH 1/4] cleanup migration --- pallets/collator-selection/src/lib.rs | 1 + pallets/collator-selection/src/migrations.rs | 101 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 pallets/collator-selection/src/migrations.rs diff --git a/pallets/collator-selection/src/lib.rs b/pallets/collator-selection/src/lib.rs index efcb97ddc..ead4a768a 100644 --- a/pallets/collator-selection/src/lib.rs +++ b/pallets/collator-selection/src/lib.rs @@ -81,6 +81,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; +pub mod migrations; #[cfg(test)] mod mock; diff --git a/pallets/collator-selection/src/migrations.rs b/pallets/collator-selection/src/migrations.rs new file mode 100644 index 000000000..a621ebd4f --- /dev/null +++ b/pallets/collator-selection/src/migrations.rs @@ -0,0 +1,101 @@ +// This file is part of Astar. + +// Copyright (C) Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use super::*; +use frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_std::marker::PhantomData; + +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +/// One-time migration that removes outdated LastAuthoredBlock entries. +/// It keeps entries only for accounts that are currently: +/// - active candidates +/// - invulnerables +/// +/// All other accounts are removed. +pub struct LastAuthoredBlockCleanup(PhantomData); + +impl OnRuntimeUpgrade for LastAuthoredBlockCleanup { + fn on_runtime_upgrade() -> Weight { + log::info!("Running CollatorSelection::LastAuthoredBlockCleanup..."); + + // Snapshot active identifiers for faster membership checks + let invulnerables = Invulnerables::::get(); + let candidate_accounts: sp_std::collections::btree_set::BTreeSet = + Candidates::::get() + .into_iter() + .map(|c| c.who) + .collect(); + + let mut removed = 0u64; + let mut read = 0u64; + let mut write = 0u64; + + // Use translate to selectively keep or drop keys + LastAuthoredBlock::::translate::, _>(|account, old_value| { + read += 1; + + let is_invulnerable = invulnerables.contains(&account); + let is_candidate = candidate_accounts.contains(&account); + + if is_invulnerable || is_candidate { + // keep entry untouched + Some(old_value) + } else { + // remove this entry + removed += 1; + write += 1; + None + } + }); + + log::info!( + "LastAuthoredBlockCleanup completed: removed {} stale collator entries. Reads: {}. Writes: {}.", + removed, read, write + ); + + let db = ::DbWeight::get(); + db.reads_writes(read, write) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let old_count = LastAuthoredBlock::::iter().count() as u64; + Ok(old_count.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(data: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let old_count: u64 = Decode::decode(&mut &data[..]) + .map_err(|_| sp_runtime::TryRuntimeError::Other("Failed to decode pre-upgrade count"))?; + + let new_count = LastAuthoredBlock::::iter().count() as u64; + + assert!( + new_count < old_count, + "LastAuthoredBlockCleanup: new count > old count (should only decrease)" + ); + + Ok(()) + } +} \ No newline at end of file From d3a84c03f31644ba39c6fcbf913b3fe619f886d7 Mon Sep 17 00:00:00 2001 From: Igor Papandinas <26460174+ipapandinas@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:22:21 +0000 Subject: [PATCH 2/4] fmt --- pallets/collator-selection/src/migrations.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pallets/collator-selection/src/migrations.rs b/pallets/collator-selection/src/migrations.rs index a621ebd4f..81d3b3a27 100644 --- a/pallets/collator-selection/src/migrations.rs +++ b/pallets/collator-selection/src/migrations.rs @@ -17,10 +17,7 @@ // along with Astar. If not, see . use super::*; -use frame_support::{ - pallet_prelude::*, - traits::{OnRuntimeUpgrade}, -}; +use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade}; use frame_system::pallet_prelude::BlockNumberFor; use sp_std::marker::PhantomData; @@ -42,10 +39,7 @@ impl OnRuntimeUpgrade for LastAuthoredBlockCleanup { // Snapshot active identifiers for faster membership checks let invulnerables = Invulnerables::::get(); let candidate_accounts: sp_std::collections::btree_set::BTreeSet = - Candidates::::get() - .into_iter() - .map(|c| c.who) - .collect(); + Candidates::::get().into_iter().map(|c| c.who).collect(); let mut removed = 0u64; let mut read = 0u64; @@ -86,8 +80,9 @@ impl OnRuntimeUpgrade for LastAuthoredBlockCleanup { #[cfg(feature = "try-runtime")] fn post_upgrade(data: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - let old_count: u64 = Decode::decode(&mut &data[..]) - .map_err(|_| sp_runtime::TryRuntimeError::Other("Failed to decode pre-upgrade count"))?; + let old_count: u64 = Decode::decode(&mut &data[..]).map_err(|_| { + sp_runtime::TryRuntimeError::Other("Failed to decode pre-upgrade count") + })?; let new_count = LastAuthoredBlock::::iter().count() as u64; @@ -98,4 +93,4 @@ impl OnRuntimeUpgrade for LastAuthoredBlockCleanup { Ok(()) } -} \ No newline at end of file +} From a47b8090ce23e8d9b28e7b29265948a8c2bbcd36 Mon Sep 17 00:00:00 2001 From: Igor Papandinas <26460174+ipapandinas@users.noreply.github.com> Date: Fri, 5 Dec 2025 10:01:38 +0000 Subject: [PATCH 3/4] sanity limit --- pallets/collator-selection/src/migrations.rs | 45 +++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/pallets/collator-selection/src/migrations.rs b/pallets/collator-selection/src/migrations.rs index 81d3b3a27..16a87d442 100644 --- a/pallets/collator-selection/src/migrations.rs +++ b/pallets/collator-selection/src/migrations.rs @@ -18,7 +18,6 @@ use super::*; use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade}; -use frame_system::pallet_prelude::BlockNumberFor; use sp_std::marker::PhantomData; #[cfg(feature = "try-runtime")] @@ -38,38 +37,42 @@ impl OnRuntimeUpgrade for LastAuthoredBlockCleanup { // Snapshot active identifiers for faster membership checks let invulnerables = Invulnerables::::get(); - let candidate_accounts: sp_std::collections::btree_set::BTreeSet = + let candidates: sp_std::collections::btree_set::BTreeSet = Candidates::::get().into_iter().map(|c| c.who).collect(); - let mut removed = 0u64; let mut read = 0u64; let mut write = 0u64; + let mut stale = Vec::new(); - // Use translate to selectively keep or drop keys - LastAuthoredBlock::::translate::, _>(|account, old_value| { + // Sanity limit + const MAX_SCAN: u64 = 200; + + for (account, _) in LastAuthoredBlock::::iter() { + if read >= MAX_SCAN { + log::warn!("LastAuthoredBlockCleanup: scan limit {} reached.", MAX_SCAN); + break; + } read += 1; - let is_invulnerable = invulnerables.contains(&account); - let is_candidate = candidate_accounts.contains(&account); - - if is_invulnerable || is_candidate { - // keep entry untouched - Some(old_value) - } else { - // remove this entry - removed += 1; - write += 1; - None + let keep = invulnerables.contains(&account) || candidates.contains(&account); + if !keep { + stale.push(account); } - }); + } + + for account in stale { + LastAuthoredBlock::::remove(account); + write += 1; + } log::info!( - "LastAuthoredBlockCleanup completed: removed {} stale collator entries. Reads: {}. Writes: {}.", - removed, read, write + "LastAuthoredBlockCleanup completed: removed {} entries (reads {}, writes {}).", + write, + read, + write ); - let db = ::DbWeight::get(); - db.reads_writes(read, write) + ::DbWeight::get().reads_writes(read, write) } #[cfg(feature = "try-runtime")] From ba69bf1b749654f0fc90348e82be417666f602b7 Mon Sep 17 00:00:00 2001 From: Igor Papandinas <26460174+ipapandinas@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:57:13 +0000 Subject: [PATCH 4/4] fix deps --- pallets/collator-selection/src/migrations.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/collator-selection/src/migrations.rs b/pallets/collator-selection/src/migrations.rs index 16a87d442..ad341f963 100644 --- a/pallets/collator-selection/src/migrations.rs +++ b/pallets/collator-selection/src/migrations.rs @@ -18,10 +18,7 @@ use super::*; use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade}; -use sp_std::marker::PhantomData; - -#[cfg(feature = "try-runtime")] -use sp_std::vec::Vec; +use sp_std::{marker::PhantomData, vec::Vec}; /// One-time migration that removes outdated LastAuthoredBlock entries. /// It keeps entries only for accounts that are currently: