diff --git a/Cargo.lock b/Cargo.lock index 95bdf735f4f42..a1fd24fadd74f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4342,6 +4342,7 @@ dependencies = [ "cumulus-relay-chain-interface", "cumulus-test-client", "cumulus-test-relay-sproof-builder", + "frame-support", "futures", "parity-scale-codec", "parking_lot 0.12.3", diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 8dca303ffebdb..3a29c193aed2f 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -21,6 +21,7 @@ tokio = { workspace = true, features = ["macros"] } tracing = { workspace = true, default-features = true } # Substrate +frame-support = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index e372162f21332..3a3d1a4efc823 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -345,9 +345,10 @@ pub async fn claim_slot( client: &C, parent_hash: B::Hash, relay_parent_header: &PHeader, - slot_duration: SlotDuration, + _slot_duration: SlotDuration, relay_chain_slot_duration: Duration, keystore: &KeystorePtr, + slot_tracker: &crate::slot_tracker::IncrementalSlotTracker, ) -> Result>, Box> where B: BlockT, @@ -366,15 +367,16 @@ where relay_chain_slot_duration, ) { Some((r_s, t)) => { - let our_slot = Slot::from_timestamp(t, slot_duration); + // Use incremental slot calculation: S_i = S_{i-1} + 1 + // Client-side tracker eliminates expensive runtime API calls + let our_slot = slot_tracker.current_slot(); + tracing::debug!( target: crate::LOG_TARGET, relay_slot = ?r_s, para_slot = ?our_slot, timestamp = ?t, - ?slot_duration, - ?relay_chain_slot_duration, - "Adjusted relay-chain slot to parachain slot" + "Using client-side incremental slot calculation" ); (our_slot, t) }, diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index a66abf979d683..374f7e0c7671b 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -29,6 +29,7 @@ use cumulus_client_collator::{ }; use cumulus_client_consensus_common::ParachainBlockImportMarker; use cumulus_client_consensus_proposer::ProposerInterface; +use cumulus_primitives_aura::Slot; use cumulus_primitives_core::{relay_chain::BlockId as RBlockId, CollectCollationInfo}; use cumulus_relay_chain_interface::RelayChainInterface; @@ -113,6 +114,10 @@ where P::Signature: TryFrom> + Member + Codec, { async move { + // Create a global slot tracker for this collator instance + // We'll initialize it with the first slot_duration we get + let slot_tracker = std::sync::Arc::new(std::sync::Mutex::new(None::)); + let mut collation_requests = match params.collation_request_receiver { Some(receiver) => receiver, None => @@ -198,6 +203,41 @@ where Err(e) => reject_with_error!(e), }; + // Initialize global slot tracker once if needed + { + let mut tracker_guard = slot_tracker.lock().unwrap(); + if tracker_guard.is_none() { + // Initialize with storage reading to get proper slot continuation + if let Err(e) = crate::slot_tracker::init_global_slot_tracker_with_storage( + &*params.para_client, + parent_hash, + slot_duration, + ) { + tracing::warn!( + target: crate::LOG_TARGET, + error = ?e, + "Failed to initialize slot tracker from storage, falling back to zero" + ); + crate::slot_tracker::init_global_slot_tracker(Some(Slot::from(0)), slot_duration); + } + + // Create local tracker instance for this collator + *tracker_guard = Some(crate::slot_tracker::IncrementalSlotTracker::new(Some(Slot::from(0)), slot_duration)); + } + } + + // Get current slot from the global tracker + let slot_tracker_ref = { + let tracker_guard = slot_tracker.lock().unwrap(); + if let Some(ref tracker) = *tracker_guard { + tracker.clone() + } else { + // This should not happen as we initialize above + panic!("Slot tracker should be initialized"); + } + }; + + // Use incremental slot tracker for proper slot calculation let claim = match collator_util::claim_slot::<_, _, P>( &*params.para_client, parent_hash, @@ -205,6 +245,7 @@ where slot_duration, params.relay_chain_slot_duration, ¶ms.keystore, + &slot_tracker_ref, ) .await { diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 55835ef4dcb8b..a9d5f5621c9c6 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -36,7 +36,7 @@ use codec::{Codec, Encode}; use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface; use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; -use cumulus_primitives_aura::AuraUnincludedSegmentApi; +use cumulus_primitives_aura::{AuraUnincludedSegmentApi, Slot}; use cumulus_primitives_core::{CollectCollationInfo, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; @@ -52,7 +52,7 @@ use sc_consensus::BlockImport; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; -use sp_consensus_aura::{AuraApi, Slot}; +use sp_consensus_aura::AuraApi; use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; @@ -109,11 +109,13 @@ where + AuxStore + HeaderBackend + BlockBackend + + sp_api::CallApiAt + Send + Sync + 'static, - Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + Client::Api: AuraApi + + CollectCollationInfo + + AuraUnincludedSegmentApi, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -161,11 +163,13 @@ where + AuxStore + HeaderBackend + BlockBackend + + sp_api::CallApiAt + Send + Sync + 'static, - Client::Api: - AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + Client::Api: AuraApi + + CollectCollationInfo + + AuraUnincludedSegmentApi, Backend: sc_client_api::Backend + 'static, RClient: RelayChainInterface + Clone + 'static, CIDP: CreateInherentDataProviders + 'static, @@ -179,6 +183,10 @@ where P::Signature: TryFrom> + Member + Codec, { async move { + // Create a global slot tracker for this collator instance + // We'll initialize it with the first slot_duration we get + let slot_tracker = std::sync::Arc::new(std::sync::Mutex::new(None::)); + cumulus_client_collator::initialize_collator_subsystems( &mut params.overseer_handle, params.collator_key, @@ -262,43 +270,75 @@ where None => continue, }; - let para_client = &*params.para_client; - let keystore = ¶ms.keystore; - let can_build_upon = |block_hash| { - let slot_duration = match sc_consensus_aura::standalone::slot_duration_at( - &*params.para_client, - block_hash, - ) { - Ok(sd) => sd, - Err(err) => { - tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration"); - return None - }, - }; - tracing::debug!(target: crate::LOG_TARGET, ?slot_duration, ?block_hash, "Parachain slot duration acquired"); + let para_client = params.para_client.clone(); + let keystore = params.keystore.clone(); + let relay_parent_header_clone = relay_parent_header.clone(); + let relay_parent_header_for_closure = relay_parent_header.clone(); + let relay_chain_slot_duration = params.relay_chain_slot_duration; + let slot_tracker_clone = slot_tracker.clone(); + + // Get slot duration once and initialize tracker if needed + let slot_duration = match sc_consensus_aura::standalone::slot_duration_at( + &*para_client, + initial_parent.hash, + ) { + Ok(sd) => sd, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration"); + continue; + }, + }; + + // Initialize slot tracker once if needed + { + let mut tracker_guard = slot_tracker_clone.lock().unwrap(); + if tracker_guard.is_none() { + // Initialize with storage reading to get proper slot continuation + if let Err(e) = crate::slot_tracker::init_global_slot_tracker_with_storage( + &*para_client, + initial_parent.hash, + slot_duration, + ) { + tracing::warn!( + target: crate::LOG_TARGET, + error = ?e, + "Failed to initialize slot tracker from storage, falling back to zero" + ); + crate::slot_tracker::init_global_slot_tracker(Some(Slot::from(0)), slot_duration); + } + + // Create local tracker instance for this collator + *tracker_guard = Some(crate::slot_tracker::IncrementalSlotTracker::new(Some(Slot::from(0)), slot_duration)); + } + } + + let can_build_upon = move |block_hash| { let (relay_slot, timestamp) = consensus_common::relay_slot_and_timestamp( - &relay_parent_header, - params.relay_chain_slot_duration, + &relay_parent_header_for_closure, + relay_chain_slot_duration, )?; - let slot_now = Slot::from_timestamp(timestamp, slot_duration); + + // Use the global slot tracker + let slot_now = { + let tracker_guard = slot_tracker_clone.lock().unwrap(); + if let Some(ref tracker) = *tracker_guard { + tracker.current_slot() + } else { + // This should not happen as we initialize above + return None; + } + }; + tracing::debug!( target: crate::LOG_TARGET, ?relay_slot, para_slot = ?slot_now, ?timestamp, - ?slot_duration, - relay_chain_slot_duration = ?params.relay_chain_slot_duration, - "Adjusted relay-chain slot to parachain slot" + ?block_hash, + "Using incremental slot tracker for slot calculation" ); - Some(super::can_build_upon::<_, _, P>( - slot_now, - relay_slot, - timestamp, - block_hash, - included_block.hash(), - para_client, - &keystore, - )) + + Some((slot_now, relay_slot, timestamp, block_hash)) }; // Build in a loop until not allowed. Note that the authorities can change @@ -316,9 +356,19 @@ where // scheduled chains this ensures that the backlog will grow steadily. for n_built in 0..2 { let slot_claim = match can_build_upon(parent_hash) { - Some(fut) => match fut.await { - None => break, - Some(c) => c, + Some((slot_now, relay_slot, timestamp, block_hash)) => { + match super::can_build_upon::<_, _, P>( + slot_now, + relay_slot, + timestamp, + block_hash, + included_block.hash(), + &*para_client, + &keystore, + ).await { + None => break, + Some(c) => c, + } }, None => break, }; @@ -332,8 +382,8 @@ where let validation_data = PersistedValidationData { parent_head: parent_header.encode().into(), - relay_parent_number: *relay_parent_header.number(), - relay_parent_storage_root: *relay_parent_header.state_root(), + relay_parent_number: *relay_parent_header_clone.number(), + relay_parent_storage_root: *relay_parent_header_clone.state_root(), max_pov_size, }; diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/slot_timer.rs b/cumulus/client/consensus/aura/src/collators/slot_based/slot_timer.rs index 53ef8eb3300af..02e4d8d00cebe 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/slot_timer.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/slot_timer.rs @@ -97,6 +97,8 @@ fn compute_next_wake_up_time( let (duration, timestamp) = time_until_next_attempt(time_now, block_production_interval, time_offset); + // Note: For slot_timer, we keep using timestamp-based calculation for timing + // but the actual slot index used for block authoring comes from the incremental API let aura_slot = Slot::from_timestamp(timestamp, para_slot_duration); (duration, aura_slot) } diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs index b5bfc1f0b2418..052976304e8aa 100644 --- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs +++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs @@ -258,7 +258,22 @@ where } fn slot_now(slot_duration: SlotDuration) -> Slot { + // Try to use global incremental slot tracker first + if let Some(slot) = crate::slot_tracker::get_global_current_slot() { + tracing::debug!( + target: crate::LOG_TARGET, + para_slot = ?slot, + "Using incremental slot from global tracker" + ); + return slot; + } + + // Fallback to timestamp-based calculation if global tracker is not initialized let timestamp = sp_timestamp::InherentDataProvider::from_system_time().timestamp(); + tracing::warn!( + target: crate::LOG_TARGET, + "Global slot tracker not initialized, falling back to timestamp-based calculation" + ); Slot::from_timestamp(timestamp, slot_duration) } diff --git a/cumulus/client/consensus/aura/src/lib.rs b/cumulus/client/consensus/aura/src/lib.rs index 422a593d91555..e8b9a59087a7c 100644 --- a/cumulus/client/consensus/aura/src/lib.rs +++ b/cumulus/client/consensus/aura/src/lib.rs @@ -70,6 +70,7 @@ pub use sc_consensus_slots::InherentDataProviderExt; pub mod collator; pub mod collators; pub mod equivocation_import_queue; +pub mod slot_tracker; const LOG_TARGET: &str = "aura::cumulus"; diff --git a/cumulus/client/consensus/aura/src/slot_tracker.rs b/cumulus/client/consensus/aura/src/slot_tracker.rs new file mode 100644 index 0000000000000..3fe27cc202f30 --- /dev/null +++ b/cumulus/client/consensus/aura/src/slot_tracker.rs @@ -0,0 +1,318 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// 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 . + +//! Client-side incremental slot tracking. +//! +//! This module implements the new slot calculation logic: S_i = S_{i-1} + 1 +//! instead of the time-based calculation S_i = T/D. + +use cumulus_primitives_aura::Slot; +use sp_consensus_aura::SlotDuration; +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, Mutex, +}; +use std::time::{SystemTime, UNIX_EPOCH}; +use codec::Decode; +use sp_core::storage::StorageKey; +use sp_runtime::traits::Block as BlockT; +use sp_state_machine::Backend; + +// Global singleton for slot tracker +static GLOBAL_SLOT_TRACKER: Mutex> = Mutex::new(None); + +/// Client-side tracker for incremental slot calculation. +/// +/// This tracker implements the S_i = S_{i-1} + 1 logic on the client side, +/// with time-based progression independent of relay chain slots. +#[derive(Debug, Clone)] +pub struct IncrementalSlotTracker { + /// Last parachain slot index used + last_para_slot: Arc, + /// Last timestamp when slot was updated + last_slot_timestamp: Arc, + /// Parachain slot duration + slot_duration: SlotDuration, +} + +impl IncrementalSlotTracker { + /// Create a new slot tracker starting from the given slot. + pub fn new(initial_slot: Option, slot_duration: SlotDuration) -> Self { + let initial_value = initial_slot.map(|s| *s).unwrap_or(0); + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + Self { + last_para_slot: Arc::new(AtomicU64::new(initial_value)), + last_slot_timestamp: Arc::new(AtomicU64::new(current_timestamp)), + slot_duration, + } + } + + /// Get the current parachain slot based on time progression. + /// + /// Logic: + /// - Calculate how many slot durations have passed since last update + /// - Increment parachain slot by the number of elapsed slots (S_i = S_{i-1} + elapsed_slots) + /// - Update timestamp to current time + /// + /// This ensures monotonic growth based on parachain's own slot duration, + /// independent of relay chain timing. + pub fn current_slot(&self) -> Slot { + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + let last_timestamp = self.last_slot_timestamp.load(Ordering::Relaxed); + let time_elapsed = current_timestamp.saturating_sub(last_timestamp); + + // Convert slot duration to milliseconds + let slot_duration_ms = self.slot_duration.as_millis() as u64; + + if time_elapsed >= slot_duration_ms { + // Calculate how many slots have elapsed + let elapsed_slots = time_elapsed / slot_duration_ms; + + // Update the slot by the number of elapsed slots + let new_slot = self.last_para_slot.fetch_add(elapsed_slots, Ordering::SeqCst) + elapsed_slots; + + // Update timestamp to current time + self.last_slot_timestamp.store(current_timestamp, Ordering::Relaxed); + + tracing::debug!( + target: "cumulus::aura::slot_tracker", + elapsed_slots = %elapsed_slots, + para_slot = %new_slot, + time_elapsed = %time_elapsed, + "Incremented parachain slot based on time progression" + ); + + Slot::from(new_slot) + } else { + // Not enough time has passed - return current slot + let current_slot = self.last_para_slot.load(Ordering::Relaxed); + + tracing::trace!( + target: "cumulus::aura::slot_tracker", + para_slot = %current_slot, + time_elapsed = %time_elapsed, + "Returning current parachain slot (not enough time elapsed)" + ); + + Slot::from(current_slot) + } + } + + /// Get the current parachain slot without modifying it. + pub fn get_current_slot(&self) -> Slot { + Slot::from(self.last_para_slot.load(Ordering::Relaxed)) + } + + /// Force update the parachain slot (for initialization purposes). + pub fn update_slot(&self, new_slot: Slot) { + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + self.last_para_slot.store(*new_slot, Ordering::SeqCst); + self.last_slot_timestamp.store(current_timestamp, Ordering::SeqCst); + + tracing::debug!( + target: "cumulus::aura::slot_tracker", + para_slot = %new_slot, + "Force updated parachain slot" + ); + } + + /// Update tracker from a known slot value. + /// + /// This method can be used to sync the tracker with a slot value + /// extracted from block digest or other sources. + pub fn update_from_slot(&self, slot: Slot) { + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + let slot_u64 = *slot; + + // Update our internal state to match the provided slot + self.last_para_slot.store(slot_u64, Ordering::SeqCst); + self.last_slot_timestamp.store(current_timestamp, Ordering::SeqCst); + + tracing::debug!( + target: "cumulus::aura::slot_tracker", + slot = %slot_u64, + "Updated slot tracker from external slot" + ); + } + + /// Get stats for debugging. + pub fn stats(&self) -> (Slot, u64) { + let para_slot = self.last_para_slot.load(Ordering::Relaxed); + let last_timestamp = self.last_slot_timestamp.load(Ordering::Relaxed); + (Slot::from(para_slot), last_timestamp) + } +} + +/// Read current slot from pallet_aura storage at given block. +/// +/// This function reads the `CurrentSlot` storage value from the aura pallet +/// to get the actual slot value stored on-chain. +pub fn read_current_slot_from_storage( + client: &Client, + at: Block::Hash, +) -> Result, Box> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + sp_api::CallApiAt + Send + Sync, +{ + // Storage key for pallet_aura::CurrentSlot + // Generated using frame_support::storage::storage_prefix for the storage item + let storage_key = StorageKey( + frame_support::storage::storage_prefix(b"Aura", b"CurrentSlot").to_vec() + ); + + // Read the storage value + let state = client.state_at(at).map_err(|e| format!("Failed to get state: {:?}", e))?; + let storage_data = state + .storage(&storage_key.0) + .map_err(|e| format!("Failed to read storage: {:?}", e))?; + + match storage_data { + Some(data) => { + let slot = Slot::decode(&mut &data[..]) + .map_err(|e| format!("Failed to decode slot: {:?}", e))?; + Ok(Some(slot)) + }, + None => Ok(None), // Storage not initialized yet + } +} + +/// Initialize the global slot tracker with proper storage initialization +pub fn init_global_slot_tracker_with_storage( + client: &Client, + at: Block::Hash, + slot_duration: SlotDuration, +) -> Result<(), Box> +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi + sp_api::CallApiAt + Send + Sync, +{ + // Try to read current slot from storage first + let storage_slot = read_current_slot_from_storage(client, at)?; + + let initial_slot = storage_slot.or(Some(Slot::from(0u64))); + + tracing::info!( + target: "cumulus::aura::slot_tracker", + storage_slot = ?storage_slot, + initialized_slot = ?initial_slot, + "Initializing global slot tracker from storage" + ); + + let mut tracker = GLOBAL_SLOT_TRACKER.lock().unwrap(); + *tracker = Some(IncrementalSlotTracker::new(initial_slot, slot_duration)); + Ok(()) +} + +/// Initialize the global slot tracker (legacy function for compatibility) +pub fn init_global_slot_tracker(initial_slot: Option, slot_duration: SlotDuration) { + let mut tracker = GLOBAL_SLOT_TRACKER.lock().unwrap(); + *tracker = Some(IncrementalSlotTracker::new(initial_slot, slot_duration)); +} + +/// Get current slot from global tracker +pub fn get_global_current_slot() -> Option { + let tracker = GLOBAL_SLOT_TRACKER.lock().unwrap(); + if let Some(ref tracker) = *tracker { + Some(tracker.current_slot()) + } else { + None + } +} + +/// Update global slot tracker from external slot +pub fn update_global_slot_tracker(slot: Slot) { + let tracker = GLOBAL_SLOT_TRACKER.lock().unwrap(); + if let Some(ref tracker) = *tracker { + tracker.update_from_slot(slot); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn incremental_slot_tracker_basic() { + let slot_duration = SlotDuration::from_millis(1000); // 1 second + let tracker = IncrementalSlotTracker::new(Some(Slot::from(100)), slot_duration); + + // Should return current slot without time progression + assert_eq!(tracker.get_current_slot(), Slot::from(100)); + + // Test force update + tracker.update_slot(Slot::from(200)); + assert_eq!(tracker.get_current_slot(), Slot::from(200)); + } + + #[test] + fn incremental_slot_tracker_initialization() { + let slot_duration = SlotDuration::from_millis(1000); // 1 second + let tracker = IncrementalSlotTracker::new(None, slot_duration); + + // Should start from 0 + assert_eq!(tracker.get_current_slot(), Slot::from(0)); + + // Test force update + tracker.update_slot(Slot::from(50)); + assert_eq!(tracker.get_current_slot(), Slot::from(50)); + } + + #[test] + fn incremental_slot_tracker_force_update() { + let slot_duration = SlotDuration::from_millis(1000); // 1 second + let tracker = IncrementalSlotTracker::new(Some(Slot::from(50)), slot_duration); + + // Force update to a different value + tracker.update_slot(Slot::from(200)); + assert_eq!(tracker.get_current_slot(), Slot::from(200)); + } + + #[test] + fn incremental_slot_tracker_external_sync() { + let slot_duration = SlotDuration::from_millis(1000); // 1 second + let tracker = IncrementalSlotTracker::new(Some(Slot::from(100)), slot_duration); + + // Test basic functionality + assert_eq!(tracker.get_current_slot(), Slot::from(100)); + + // Test external slot update + tracker.update_from_slot(Slot::from(150)); + assert_eq!(tracker.get_current_slot(), Slot::from(150)); + + // Test force update + tracker.update_slot(Slot::from(200)); + assert_eq!(tracker.get_current_slot(), Slot::from(200)); + } +} + diff --git a/cumulus/pallets/aura-ext/src/consensus_hook.rs b/cumulus/pallets/aura-ext/src/consensus_hook.rs index 1c3e373ef85ef..f2ac9a8782d76 100644 --- a/cumulus/pallets/aura-ext/src/consensus_hook.rs +++ b/cumulus/pallets/aura-ext/src/consensus_hook.rs @@ -16,7 +16,7 @@ //! The definition of a [`FixedVelocityConsensusHook`] for consensus logic to manage //! block velocity. -use super::{pallet, Aura}; +use super::pallet; use core::{marker::PhantomData, num::NonZeroU32}; use cumulus_pallet_parachain_system::{ self as parachain_system, @@ -24,7 +24,7 @@ use cumulus_pallet_parachain_system::{ relay_state_snapshot::RelayChainStateProof, }; use frame_support::pallet_prelude::*; -use sp_consensus_aura::{Slot, SlotDuration}; +use sp_consensus_aura::Slot; /// A consensus hook that enforces fixed block production velocity and unincluded segment capacity. /// @@ -75,13 +75,17 @@ where let velocity = V.max(1); let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot"); - let (relay_chain_slot, authored_in_relay) = match pallet::RelaySlotInfo::::get() { - Some((slot, authored)) if slot == relay_chain_slot => (slot, authored), - Some((slot, _)) if slot < relay_chain_slot => (relay_chain_slot, 0), + let (previous_relay_slot, relay_slot_is_new) = match pallet::RelaySlotInfo::::get() { + Some((slot, authored)) if slot == relay_chain_slot => (slot, false), + Some((slot, _)) if slot < relay_chain_slot => (slot, true), Some((slot, _)) => { panic!("Slot moved backwards: stored_slot={slot:?}, relay_chain_slot={relay_chain_slot:?}") }, - None => (relay_chain_slot, 0), + None => (relay_chain_slot, true), + }; + + let authored_in_relay = if relay_slot_is_new { 0 } else { + pallet::RelaySlotInfo::::get().map(|(_, authored)| authored).unwrap_or(0) }; // We need to allow one additional block to be built to fill the unincluded segment. @@ -91,25 +95,8 @@ where pallet::RelaySlotInfo::::put((relay_chain_slot, authored_in_relay + 1)); - let para_slot = pallet_aura::CurrentSlot::::get(); - - // Convert relay chain timestamp. - let relay_chain_timestamp = - u64::from(RELAY_CHAIN_SLOT_DURATION_MILLIS).saturating_mul(*relay_chain_slot); - - let para_slot_duration = SlotDuration::from_millis(Aura::::slot_duration().into()); - let para_slot_from_relay = - Slot::from_timestamp(relay_chain_timestamp.into(), para_slot_duration); - - if *para_slot > *para_slot_from_relay { - panic!( - "Parachain slot is too far in the future: parachain_slot={:?}, derived_from_relay_slot={:?} velocity={:?}, relay_chain_slot={:?}", - para_slot, - para_slot_from_relay, - velocity, - relay_chain_slot - ); - } + // Note: Slot index calculation is now handled client-side via IncrementalSlotTracker + // This consensus hook only manages velocity and relay chain synchronization let weight = T::DbWeight::get().reads(1); diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index 496cbb694a87f..a08ad0263f8f1 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -36,7 +36,7 @@ use frame_support::traits::{ExecuteBlock, FindAuthor}; use sp_application_crypto::RuntimeAppPublic; -use sp_consensus_aura::{digests::CompatibleDigestItem, Slot}; +use sp_consensus_aura::digests::CompatibleDigestItem; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub mod consensus_hook; @@ -51,9 +51,10 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; +use super::*; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use sp_consensus_aura::Slot; /// The configuration trait. #[pallet::config] @@ -97,6 +98,8 @@ pub mod pallet { #[pallet::storage] pub(crate) type RelaySlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; + // Note: LastParachainSlot storage removed - slot tracking moved to client-side + #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { @@ -111,6 +114,10 @@ pub mod pallet { Authorities::::put(authorities); } } + + // Note: Slot management functions removed - now handled client-side by IncrementalSlotTracker + + // Note: Runtime API implementation is done in the runtime, not here } /// The block executor used when validating a PoV at the relay chain.