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: