pallet_shards/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::manual_inspect)]
//! # Timechain Shards Pallet
//!
//! The Shards pallet manages the lifecycle of shards in a decentralized network. It handles the
//! creation, commitment, readiness, and offline status of shards, along with managing shard members
//! and their state. The pallet ensures that the commitments of all members are valid and that they
//! are ready before transitioning a shard to an online state. It also provides mechanisms for
//! forcefully taking shards offline when needed. The main callable functions (commit, ready, and
//! force_shard_offline) enable members and administrators to interact with the shards, while hooks
//! like on_initialize ensure timely state updates and cleanup. Events are emitted to signal
//! important state changes and actions taken on shards.
//!
//! ## Call Functions
//!
//! This graph represents the workflow of the `commit`, `ready`, and `force_shard_offline` call
//! functions within the Shards pallet.
//!
//! ### Commit Flow
//!
//! The commit process begins with verifying that the request is from an authenticated user. It then
//! checks the current status of the member to ensure they are eligible to commit. If the status is
//! not as expected, the process returns an `UnexpectedCommit` error. Once the status is validated,
//! the required commitment threshold is retrieved, and the length of the commitment is checked for
//! appropriateness. The commitment is validated, and any invalid commitment results in an
//! `InvalidCommitment` error. A valid commitment is stored, followed by a check to see if all
//! necessary commitments have been received. Once all commitments are collected, they are aggregated
//! into a group commitment, which is then stored. The shard state is updated based on these
//! commitments, and the process concludes with the logging of a `ShardCommitted` event.
//!
//! ### Force Shard Offline Flow
//!
//! The force shard offline process starts with ensuring the request is from a root user. Upon
//! confirmation, the system removes the shard. This involves removing the shard state,
//! retrieving the network details, and scheduling the `shard_offline` task. The process also
//! includes draining and removing shard members, removing members from the `MemberShard`, and
//! concludes with logging the `ShardOffline` event.
//!
//! ### Ready Flow
//!
//! The ready process begins with ensuring the request is from an authenticated user. It checks the
//! current status of the member to confirm they are in the correct state to be marked as ready. If
//! the status is not appropriate, an `UnexpectedReady` error is returned. Once the status is
//! validated, the system retrieves the network and commitment of the member. It is then marked as
//! ready, and a check is performed to see if all members are ready. If all members are ready, the
//! shard state is updated to `Online`, and the `shard_online` task is scheduled. The process ends
//! with the logging of a `ShardOnline` event.
//!
#![doc = simple_mermaid::mermaid!("../docs/shard_callfunctions.mmd")]
//!
//! ## **on_initialize Hook**
//!
//! This graph illustrates the workflow of the `on_initialize` function within the Shards pallet.
//! The `on_initialize` function is triggered at the beginning of each block and iterates over the
//! `DkgTimeout` entries to identify any shards that have timed out. For each entry, the function
//! checks if the timeout condition is met. If the condition is met, the function handles the timeout
//! by either removing the `DkgTimeout` entry or marking the shard as offline. In the case where the
//! shard is neither in a Created nor Committed state, the function removes the timeout entry.
//! If the shard is in a Created or Committed state, it proceeds to handle the shard going offline.
//! This involves removing the state and thresholds entrie of a shard, attempting to retrieve the
//! associated network, and marking the shard as offline in the task scheduler. Additionally, the
//! function drains the shard members, removes their entries, handles the shard going offline in the
//! elections module, and finally emits the `ShardOffline` event.
//!
#![doc = simple_mermaid::mermaid!("../docs/shard_hook.mmd")]

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

pub use pallet::*;

#[polkadot_sdk::frame_support::pallet]
pub mod pallet {
	use polkadot_sdk::{frame_support, frame_system, pallet_balances, sp_runtime, sp_std};

	use frame_support::pallet_prelude::{EnsureOrigin, ValueQuery, *};
	use frame_system::pallet_prelude::*;

	use schnorr_evm::VerifyingKey;
	use sp_runtime::Saturating;
	use sp_std::collections::btree_map::BTreeMap;
	use sp_std::vec;
	use sp_std::vec::Vec;

	use time_primitives::{
		AccountId, Balance, Commitment, ElectionsInterface, MemberStatus, MembersInterface,
		NetworkId, ProofOfKnowledge, ShardId, ShardStatus, ShardsInterface, TasksInterface,
		TssPublicKey,
	};

	/// Trait to define the weights for various extrinsics in the pallet.
	pub trait WeightInfo {
		fn commit() -> Weight;
		fn ready() -> Weight;
		fn force_shard_offline() -> Weight;
		fn timeout_dkgs(b: u32) -> Weight;
	}

	impl WeightInfo for () {
		fn commit() -> Weight {
			Weight::default()
		}

		fn ready() -> Weight {
			Weight::default()
		}

		fn force_shard_offline() -> Weight {
			Weight::default()
		}

		fn timeout_dkgs(_: u32) -> Weight {
			Weight::default()
		}
	}

	#[pallet::pallet]
	#[pallet::without_storage_info]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config:
		polkadot_sdk::frame_system::Config<AccountId = AccountId>
		+ pallet_balances::Config<Balance = Balance>
	{
		type RuntimeEvent: From<Event<Self>>
			+ IsType<<Self as polkadot_sdk::frame_system::Config>::RuntimeEvent>;
		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
		type WeightInfo: WeightInfo;
		type Elections: ElectionsInterface;
		type Members: MembersInterface;
		type Tasks: TasksInterface;
		#[pallet::constant]
		type DkgTimeout: Get<BlockNumberFor<Self>>;
	}

	/// Counter for creating unique shard_ids during on-chain creation
	#[pallet::storage]
	pub type ShardIdCounter<T: Config> = StorageValue<_, ShardId, ValueQuery>;

	/// subxt doesn't allow decoding keys
	#[pallet::storage]
	pub type Shards<T: Config> = StorageMap<_, Blake2_128Concat, ShardId, ShardId, OptionQuery>;

	/// Maps `ShardId` to `NetworkId` indicating the network for which shards can be assigned tasks.
	#[pallet::storage]
	pub type ShardNetwork<T: Config> =
		StorageMap<_, Blake2_128Concat, ShardId, NetworkId, OptionQuery>;

	/// Maps `ShardId` to `ShardStatus` indicating the status of each shard.
	#[pallet::storage]
	pub type ShardState<T: Config> =
		StorageMap<_, Blake2_128Concat, ShardId, ShardStatus, OptionQuery>;

	/// Maps `BlockNumber` to the number of shards scheduled to timeout
	#[pallet::storage]
	pub type DkgTimeoutCounter<T: Config> =
		StorageMap<_, Blake2_128Concat, BlockNumberFor<T>, u32, ValueQuery>;

	/// Tracks `BlockNumber` at which the shard with `ShardId` will DKG timeout.
	#[pallet::storage]
	pub type DkgTimeout<T: Config> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		BlockNumberFor<T>,
		Blake2_128Concat,
		ShardId,
		(),
		OptionQuery,
	>;

	/// Maps `ShardId` to `u16` indicating the threshold for each shard.
	#[pallet::storage]
	pub type ShardThreshold<T: Config> = StorageMap<_, Blake2_128Concat, ShardId, u16, OptionQuery>;

	/// Maps `ShardId` to `Commitment` indicating the commitment of each shard.
	#[pallet::storage]
	pub type ShardCommitment<T: Config> =
		StorageMap<_, Blake2_128Concat, ShardId, Commitment, OptionQuery>;

	/// Maps `AccountId` to `ShardId` indicating the shard a member is part of.
	#[pallet::storage]
	pub type MemberShard<T: Config> =
		StorageMap<_, Blake2_128Concat, AccountId, ShardId, OptionQuery>;

	/// Double map storing the `MemberStatus` of each `AccountId` in a specific ShardId.
	#[pallet::storage]
	pub type ShardMembers<T: Config> = StorageDoubleMap<
		_,
		Blake2_128Concat,
		ShardId,
		Blake2_128Concat,
		AccountId,
		MemberStatus,
		OptionQuery,
	>;

	/// Maps `ShardId` to `u16` indicating the number of online members in each shard.
	#[pallet::storage]
	pub type ShardMembersOnline<T: Config> =
		StorageMap<_, Blake2_128Concat, ShardId, u16, ValueQuery>;

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		/// New shard was created
		ShardCreated(ShardId, NetworkId),
		/// Shard committed
		ShardCommitted(ShardId, Commitment),
		/// Shard completed dkg and submitted public key to runtime
		ShardOnline(ShardId, TssPublicKey),
		/// Shards went offline
		ShardsOffline(Vec<ShardId>),
	}

	#[pallet::error]
	pub enum Error<T> {
		/// Indicates that the specified shard network does not exist.
		UnknownShardNetwork,
		/// Indicates that the specified shard commitment does not exist.
		UnknownShardCommitment,
		/// Indicates that an unexpected commitment was provided for the shard.
		UnexpectedCommit,
		/// Indicates that a peer id cannot be found for the member.
		MemberPeerIdNotFound,
		/// Commitment length not equal to threshold.
		CommitmentLenNotEqualToThreshold,
		/// Verify Key in Commitment was invalid.
		InvalidVerifyingKeyInCommitment,
		/// Indicates that an invalid commitment was provided.
		InvalidCommitment,
		/// Indicates that an invalid proof of knowledge was provided.
		InvalidProofOfKnowledge,
		/// Indicates that an unexpected ready state occurred.
		UnexpectedReady,
		/// Indicates that the maximum number of shards were created this block.
		MaxShardsCreatedThisBlock,
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Allows a member to submit a commitment to a shard.
		/// # Flow
		///   1. Ensure the origin is a signed transaction and the sender is a member of the shard.
		///   2. Validate the commitment length against the shard threshold.
		///   3. Validate each commitment element.
		///   4. Verify the proof of knowledge using the peer ID of the member.
		///   5. Update the status of the member to `Committed` and store the commitment.
		///   6. If all members have committed, update the state of the shards to `Committed` and store the group commitment.
		///   7. Emit the [`Event::ShardCommitted`] event.
		#[pallet::call_index(0)]
		#[pallet::weight((
			<T as Config>::WeightInfo::commit(),
			DispatchClass::Normal,
			Pays::No
		))]
		pub fn commit(
			origin: OriginFor<T>,
			shard_id: ShardId,
			commitment: Commitment,
			proof_of_knowledge: ProofOfKnowledge,
		) -> DispatchResult {
			let member = ensure_signed(origin)?;
			Self::execute_commit(member, shard_id, commitment, proof_of_knowledge)
		}

		/// Marks a shard as ready when a member indicates readiness after commitment.
		///
		/// # Flow
		///   1. Ensure the origin is a signed transaction and the sender has committed.
		///   2. Retrieve the network and commitment of the shard.
		///   3. Update the status of the shard to `Ready`.
		///   4. If all members are ready, update the state of the shard to `Online` and emit the [`Event::ShardOnline`] event.
		///   5. Notify the task scheduler that the shard is online.
		#[pallet::call_index(1)]
		#[pallet::weight((
			<T as Config>::WeightInfo::ready(),
			DispatchClass::Normal,
			Pays::No
		))]
		pub fn ready(origin: OriginFor<T>, shard_id: ShardId) -> DispatchResult {
			let member = ensure_signed(origin)?;
			Self::execute_ready(member, shard_id)
		}

		/// Forces a shard to go offline, used primarily by the root.
		/// # Flow
		///   1. Ensure the origin is the root.
		///   2. Call the internal `remove_shards_offline` function to handle the shard offline process.
		#[pallet::call_index(2)]
		#[pallet::weight(<T as Config>::WeightInfo::force_shard_offline())]
		pub fn force_shard_offline(origin: OriginFor<T>, shard_id: ShardId) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			Self::remove_shards_offline(vec![shard_id]);
			Ok(())
		}
	}

	#[pallet::hooks]
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
			log::info!("on_initialize begin");
			let weight_consumed = Self::timeout_dkgs(n);
			log::info!("on_initialize end");
			weight_consumed
		}
	}

	impl<T: Config> Pallet<T> {
		fn execute_commit(
			member: AccountId,
			shard_id: ShardId,
			commitment: Commitment,
			proof_of_knowledge: ProofOfKnowledge,
		) -> DispatchResult {
			ensure!(
				ShardMembers::<T>::get(shard_id, &member) == Some(MemberStatus::Added),
				Error::<T>::UnexpectedCommit
			);
			let threshold = ShardThreshold::<T>::get(shard_id).unwrap_or_default();
			ensure!(
				commitment.0.len() == threshold as usize,
				Error::<T>::CommitmentLenNotEqualToThreshold
			);
			for c in &commitment.0 {
				ensure!(
					VerifyingKey::from_bytes(*c).is_ok(),
					Error::<T>::InvalidVerifyingKeyInCommitment
				);
			}
			let peer_id =
				T::Members::member_peer_id(&member).ok_or(Error::<T>::MemberPeerIdNotFound)?;
			schnorr_evm::proof_of_knowledge::verify_proof_of_knowledge(
				&peer_id,
				&commitment.0,
				proof_of_knowledge,
			)
			.map_err(|_| Error::<T>::InvalidProofOfKnowledge)?;
			ShardMembers::<T>::insert(shard_id, member, MemberStatus::Committed(commitment));
			if ShardMembers::<T>::iter_prefix(shard_id).all(|(_, status)| status.is_committed()) {
				let commitment = ShardMembers::<T>::iter_prefix(shard_id)
					.filter_map(|(_, status)| status.commitment().cloned())
					.reduce(|mut group_commitment, commitment| {
						for (group_commitment, commitment) in
							group_commitment.0.iter_mut().zip(commitment.0.iter())
						{
							*group_commitment = VerifyingKey::new(
								VerifyingKey::from_bytes(*group_commitment)
									.expect("GroupCommitment output is invalid")
									.to_element() + VerifyingKey::from_bytes(*commitment)
									.expect("Commitment is invalid")
									.to_element(),
							)
							.to_bytes()
							.expect("Group commitment construction failed");
						}
						group_commitment
					})
					.ok_or(Error::<T>::InvalidCommitment)?;
				ShardCommitment::<T>::insert(shard_id, commitment.clone());
				ShardState::<T>::insert(shard_id, ShardStatus::Committed);
				Self::deposit_event(Event::ShardCommitted(shard_id, commitment))
			}
			Ok(())
		}
		fn execute_ready(member: AccountId, shard_id: ShardId) -> DispatchResult {
			ensure!(
				matches!(
					ShardMembers::<T>::get(shard_id, &member),
					Some(MemberStatus::Committed(_))
				),
				Error::<T>::UnexpectedReady,
			);
			let network =
				ShardNetwork::<T>::get(shard_id).ok_or(Error::<T>::UnknownShardNetwork)?;
			let commitment =
				ShardCommitment::<T>::get(shard_id).ok_or(Error::<T>::UnknownShardCommitment)?;
			ShardMembers::<T>::insert(shard_id, member, MemberStatus::Ready);
			if ShardMembers::<T>::iter_prefix(shard_id)
				.all(|(_, status)| status == MemberStatus::Ready)
			{
				<ShardState<T>>::insert(shard_id, ShardStatus::Online);
				Self::deposit_event(Event::ShardOnline(shard_id, commitment.0[0]));
				T::Tasks::shard_online(shard_id, network);
			}
			Ok(())
		}
		/// Handles the internal logic for removing shards and setting state to offline.
		/// Sets shards' statuses to offline and keeps shards' public keys if already submitted
		/// # Flow
		///   1. Update the state of the shard to `Offline`.
		///   2. Remove the threshold of the shard.
		///   3. Notify the task scheduler and elections module that the shard is offline.
		///   4. Drain the members of the shard and remove their corresponding entries.
		///   5. Emit the [`Event::ShardsOffline`] event.
		fn remove_shards_offline(shards: Vec<ShardId>) {
			let mut network_updates = BTreeMap::<NetworkId, Vec<AccountId>>::new();

			// First pass: collect data for batch operations
			for &shard_id in &shards {
				ShardState::<T>::insert(shard_id, ShardStatus::Offline);
				ShardThreshold::<T>::remove(shard_id);

				if let Some(network) = ShardNetwork::<T>::take(shard_id) {
					Shards::<T>::remove(shard_id);
					T::Tasks::shard_offline(shard_id, network);

					// Collect all members for election update
					let members = ShardMembers::<T>::drain_prefix(shard_id)
						.map(|(m, _)| {
							MemberShard::<T>::remove(&m);
							m
						})
						.collect::<Vec<_>>();

					network_updates.entry(network).or_default().extend(members);
				}
			}

			// Batch update elections for all networks
			for (network, members) in network_updates {
				T::Elections::shard_offline(network, members);
			}

			// Emit events in batch after applying all changes
			Self::deposit_event(Event::ShardsOffline(shards));
		}
		/// Checks for DKG timeouts and handles shard state transitions accordingly.
		/// # Flow
		///   1. Iterate over the [`DkgTimeout`] storage.
		///   2. Check if the DKG process of any shard has timed out.
		///   3. For timed-out shards, update their status to offline and emit the [`Event::ShardsOffline`] event.
		///   4. Remove DKG timeout entries for shards that are no longer in `Created` or `Committed` states.
		pub(crate) fn timeout_dkgs(n: BlockNumberFor<T>) -> Weight {
			let mut num_timeouts = 0u32;
			let mut shards_to_remove = Vec::new();

			// Collect all shard IDs that need to be removed
			DkgTimeout::<T>::drain_prefix(n).for_each(|(shard_id, _)| {
				if let Some(status) = ShardState::<T>::get(shard_id) {
					if matches!(status, ShardStatus::Created | ShardStatus::Committed) {
						shards_to_remove.push(shard_id);
						num_timeouts = num_timeouts.saturating_add(1);
					}
				}
			});

			// Batch remove offline shards
			if !shards_to_remove.is_empty() {
				Self::remove_shards_offline(shards_to_remove);
			}

			// Remove the timeout counter for this block
			DkgTimeoutCounter::<T>::remove(n);
			<T as Config>::WeightInfo::timeout_dkgs(num_timeouts)
		}
		/// Fetches all shards associated with a given account.
		/// # Flow
		///   1. Iterate over [`ShardMembers`] storage to find all shards the account is a member of.
		///   2. Collect and return the shard IDs.
		pub fn shards(account: &AccountId) -> Vec<ShardId> {
			ShardMembers::<T>::iter()
				.filter_map(
					|(shard_id, member, _)| {
						if member == *account {
							Some(shard_id)
						} else {
							None
						}
					},
				)
				.collect()
		}
		/// Retrieves all members of a specified shard.
		/// # Flow
		///   1. Iterate over [`ShardMembers`] storage for the given shard ID.
		///   2. Collect and return the member statuses.
		pub fn shard_members(shard_id: ShardId) -> Vec<(AccountId, MemberStatus)> {
			ShardMembers::<T>::iter_prefix(shard_id).collect()
		}
		/// Retrieves the threshold value of a specified shard.
		///
		/// # Flow
		///   1. Retrieve and return the threshold value from [`ShardThreshold`] storage.
		pub fn shard_threshold(shard_id: ShardId) -> u16 {
			ShardThreshold::<T>::get(shard_id).unwrap_or_default()
		}
		/// Retrieves the current status of a specified shard.
		/// # Flow
		///   1. Retrieve and return the status from [`ShardState`] storage.
		pub fn shard_status(shard_id: ShardId) -> ShardStatus {
			ShardState::<T>::get(shard_id).unwrap_or_default()
		}
		/// Retrieves the commitment of a specified shard, if available.
		///
		/// # Flow
		///   1. Retrieve and return the commitment from [`ShardCommitment`] storage.
		pub fn shard_commitment(shard_id: ShardId) -> Option<Commitment> {
			ShardCommitment::<T>::get(shard_id)
		}
	}

	impl<T: Config> ShardsInterface for Pallet<T> {
		/// Updates shard state when a member comes online.
		///
		/// # Flow
		///   1. Retrieves the `shard_id` associated with the member `id`.
		///   2. Retrieves the current old_status of the shard.
		///   3. Increments the count of online members [`ShardMembersOnline`].
		///   4. Updates [`ShardState`] to `Offline` if the previous status was `Created` or `Committed`.
		fn member_online(id: &AccountId, _network: NetworkId) {
			let Some(shard_id) = MemberShard::<T>::get(id) else { return };
			let Some(old_status) = ShardState::<T>::get(shard_id) else { return };
			ShardMembersOnline::<T>::mutate(shard_id, |x| *x = x.saturating_plus_one());
			match old_status {
				ShardStatus::Created | ShardStatus::Committed => {
					ShardState::<T>::insert(shard_id, ShardStatus::Offline)
				},
				_ => (),
			}
		}

		fn members_offline(members: Vec<AccountId>) {
			let mut shard_updates: BTreeMap<ShardId, (u32, Option<ShardStatus>)> = BTreeMap::new();
			let mut shards_to_remove_offline = Vec::new();

			// First pass: Collect updates for each shard
			for member in members {
				let Some(shard_id) = MemberShard::<T>::get(member) else { continue };

				// Get or initialize the shard entry in the update map
				let (online_count, status_update) =
					shard_updates.entry(shard_id).or_insert_with(|| {
						let online = ShardMembersOnline::<T>::get(shard_id);
						let status = ShardState::<T>::get(shard_id);
						(online.into(), status)
					});

				*online_count = online_count.saturating_sub(1);

				// If we haven't checked shard state yet, do it once
				if let Some(old_status) = *status_update {
					let Some(shard_threshold) = ShardThreshold::<T>::get(shard_id) else {
						continue;
					};

					// Determine new status
					let new_status = match old_status {
						ShardStatus::Created | ShardStatus::Committed => ShardStatus::Offline,
						ShardStatus::Online if *online_count < shard_threshold.into() => {
							ShardStatus::Offline
						},
						_ => old_status,
					};

					if matches!(new_status, ShardStatus::Offline)
						&& !matches!(old_status, ShardStatus::Offline)
					{
						shards_to_remove_offline.push(shard_id);
					}

					// Update only if status changes
					if new_status != old_status {
						*status_update = Some(new_status);
					}
				}
			}

			// Batch storage updates
			for (shard_id, (new_online_count, status_update)) in shard_updates {
				ShardMembersOnline::<T>::insert(
					shard_id,
					TryInto::<u16>::try_into(new_online_count).unwrap_or_default(),
				);

				if let Some(new_status) = status_update {
					ShardState::<T>::insert(shard_id, new_status);
				}
			}

			// Remove offline shards in batch
			if !shards_to_remove_offline.is_empty() {
				Self::remove_shards_offline(shards_to_remove_offline);
			}
		}

		/// Checks if a specified shard is currently online.
		///
		/// # Flow
		///   1. Retrieves the `ShardState` for the given `shard_id`.
		///   2. Returns `true` if the shard status is [`Some(ShardStatus::Online)`], indicating the shard is online; otherwise, returns `false`.
		fn is_shard_online(shard_id: ShardId) -> bool {
			matches!(ShardState::<T>::get(shard_id), Some(ShardStatus::Online))
		}
		/// Checks if a specified account is a member of any shard.
		///
		/// # Flow
		///   1. Retrieves the shard `ID` associated with the member account from [`MemberShard`].
		///   2. Returns `true` if the shard `ID` is present (`Some`), indicating the account is a member; otherwise, returns `false`.
		fn is_shard_member(member: &AccountId) -> bool {
			MemberShard::<T>::contains_key(member)
		}
		/// Retrieves the network identifier associated with a specified shard.
		///
		/// # Flow
		///   1. Retrieves and returns the network ID stored in [`ShardNetwork`] for the given `shard_id`.
		fn shard_network(shard_id: ShardId) -> Option<NetworkId> {
			ShardNetwork::<T>::get(shard_id)
		}
		/// Retrieves the list of account identifiers that are members of a specified shard.
		///
		/// # Flow
		///   1. Iterates over `ShardMembers` entries with the prefix `shard_id`.
		///   2. Collects and returns the list of account identifiers (`AccountId`) associated with the shard.
		fn shard_members(shard_id: ShardId) -> Vec<AccountId> {
			ShardMembers::<T>::iter_prefix(shard_id).map(|(a, _)| a).collect::<Vec<_>>()
		}
		/// Creates a new shard with specified network, members, and threshold, initializing its state and storing relevant data.
		///
		/// # Flow
		///   1. Generates a new `shard_id` using [`ShardIdCounter`].
		///   2. Stores the network ID in [`ShardNetwork`] for the `shard_id`.
		///   3. Initializes the ShardState to [`ShardStatus::Created`].
		///   4. Sets the creation time in [`DkgTimeout`].
		///   5. Stores the threshold in [`ShardThreshold`].
		///   6. Inserts each member into ShardMembers and associates them with [`MemberStatus::Added`].
		///   7. Registers each member in `MemberShard` with the `shard_id`.
		///   8. Emits a [`Event::ShardCreated`] event with the `shard_id` and network.
		fn create_shard(
			network: NetworkId,
			members: Vec<AccountId>,
			threshold: u16,
		) -> Result<ShardId, DispatchError> {
			let dkg_timeout_block =
				frame_system::Pallet::<T>::block_number().saturating_add(T::DkgTimeout::get());
			let dkg_timeout_counter = <DkgTimeoutCounter<T>>::get(dkg_timeout_block);
			ensure!(
				dkg_timeout_counter
					< <<T as Config>::Elections as ElectionsInterface>::MaxElectionsPerBlock::get(),
				Error::<T>::MaxShardsCreatedThisBlock
			);
			<DkgTimeoutCounter<T>>::insert(
				dkg_timeout_block,
				dkg_timeout_counter.saturating_plus_one(),
			);
			let shard_id = <ShardIdCounter<T>>::get();
			<ShardIdCounter<T>>::put(shard_id.saturating_plus_one());
			<Shards<T>>::insert(shard_id, shard_id);
			<ShardNetwork<T>>::insert(shard_id, network);
			<ShardState<T>>::insert(shard_id, ShardStatus::Created);
			<DkgTimeout<T>>::insert(dkg_timeout_block, shard_id, ());
			<ShardThreshold<T>>::insert(shard_id, threshold);
			for member in &members {
				ShardMembers::<T>::insert(shard_id, member, MemberStatus::Added);
				MemberShard::<T>::insert(member, shard_id);
			}
			ShardMembersOnline::<T>::insert(shard_id, members.len() as u16);
			Self::deposit_event(Event::ShardCreated(shard_id, network));
			Ok(shard_id)
		}
		/// Retrieves the TSS public key associated with the specified shard, if available.
		///
		/// # Flow
		///   1. Retrieves the commitment [`Vec<TssPublicKey>`] associated with the `shard_id` from [`ShardCommitment`].
		///   2. Returns the first element of the commitment [`TssPublicKey`] if it exists; otherwise, returns `None`.
		fn tss_public_key(shard_id: ShardId) -> Option<TssPublicKey> {
			ShardCommitment::<T>::get(shard_id).map(|commitment| commitment.0[0])
		}

		fn num_sessions(shard_id: ShardId) -> Option<u16> {
			let threshold = <ShardThreshold<T>>::get(shard_id)?;
			let size = ShardCommitment::<T>::get(shard_id)?.0.len() as u16;
			Some(size - threshold + 1)
		}

		fn force_shard_offline(shard_id: ShardId) {
			Self::remove_shards_offline(vec![shard_id]);
		}
	}
}