1use crate::{
34 Block, BlockBuilderError, RuntimeExecutor,
35 inherent::InherentProvider,
36 strings::inherent::timestamp::{
37 self as strings,
38 slot_duration::{
39 PARACHAIN_FALLBACK_MS as DEFAULT_PARA_SLOT_DURATION_MS,
40 RELAY_CHAIN_FALLBACK_MS as DEFAULT_RELAY_SLOT_DURATION_MS,
41 },
42 },
43};
44use async_trait::async_trait;
45use scale::{Compact, Decode, Encode};
46use std::sync::atomic::{AtomicU64, Ordering};
47use subxt::Metadata;
48
49const EXTRINSIC_FORMAT_VERSION: u8 = 5;
52
53pub struct TimestampInherent {
72 slot_duration_ms: u64,
74 cached_slot_duration: AtomicU64,
78}
79
80impl TimestampInherent {
81 pub fn new(slot_duration_ms: u64) -> Self {
87 Self { slot_duration_ms, cached_slot_duration: AtomicU64::new(0) }
88 }
89
90 pub fn default_relay() -> Self {
92 Self::new(DEFAULT_RELAY_SLOT_DURATION_MS)
93 }
94
95 pub fn default_para() -> Self {
97 Self::new(DEFAULT_PARA_SLOT_DURATION_MS)
98 }
99
100 pub fn timestamp_now_key() -> Vec<u8> {
108 let pallet_hash = sp_core::twox_128(strings::storage_keys::PALLET_NAME);
109 let storage_hash = sp_core::twox_128(strings::storage_keys::NOW);
110 [pallet_hash.as_slice(), storage_hash.as_slice()].concat()
111 }
112
113 fn encode_timestamp_set_call(pallet_index: u8, call_index: u8, timestamp: u64) -> Vec<u8> {
117 let mut call = vec![pallet_index, call_index];
118 call.extend(Compact(timestamp).encode());
120 call
121 }
122
123 fn encode_inherent_extrinsic(call: Vec<u8>) -> Vec<u8> {
130 let mut extrinsic = vec![EXTRINSIC_FORMAT_VERSION];
131 extrinsic.extend(call);
132
133 let len = Compact(extrinsic.len() as u32);
135 let mut result = len.encode();
136 result.extend(extrinsic);
137 result
138 }
139
140 pub async fn get_slot_duration_from_runtime(
158 executor: &RuntimeExecutor,
159 storage: &crate::LocalStorageLayer,
160 metadata: &Metadata,
161 fallback: u64,
162 ) -> u64 {
163 if let Some(duration) = executor
165 .call(strings::slot_duration::AURA_API_METHOD, &[], storage)
166 .await
167 .ok()
168 .and_then(|r| u64::decode(&mut r.output.as_slice()).ok())
169 {
170 return duration;
171 }
172
173 if let Some(duration) = Self::get_constant_from_metadata(
175 metadata,
176 strings::slot_duration::BABE_PALLET,
177 strings::slot_duration::BABE_EXPECTED_BLOCK_TIME,
178 ) {
179 return duration;
180 }
181
182 fallback
184 }
185
186 fn get_constant_from_metadata(
188 metadata: &Metadata,
189 pallet_name: &str,
190 constant_name: &str,
191 ) -> Option<u64> {
192 metadata
193 .pallet_by_name(pallet_name)?
194 .constant_by_name(constant_name)
195 .and_then(|c| u64::decode(&mut &c.value()[..]).ok())
196 }
197}
198
199impl Default for TimestampInherent {
200 fn default() -> Self {
201 Self::default_relay()
202 }
203}
204
205#[async_trait]
206impl InherentProvider for TimestampInherent {
207 fn identifier(&self) -> &'static str {
208 strings::IDENTIFIER
209 }
210
211 async fn provide(
212 &self,
213 parent: &Block,
214 executor: &RuntimeExecutor,
215 ) -> Result<Vec<Vec<u8>>, BlockBuilderError> {
216 let metadata = parent.metadata().await?;
218
219 let pallet = metadata.pallet_by_name(strings::metadata::PALLET_NAME).ok_or_else(|| {
220 BlockBuilderError::InherentProvider {
221 provider: self.identifier().to_string(),
222 message: format!(
223 "{}: {}",
224 strings::errors::PALLET_NOT_FOUND,
225 strings::metadata::PALLET_NAME
226 ),
227 }
228 })?;
229
230 let pallet_index = pallet.index();
231
232 let call_variant = pallet
233 .call_variant_by_name(strings::metadata::SET_CALL_NAME)
234 .ok_or_else(|| BlockBuilderError::InherentProvider {
235 provider: self.identifier().to_string(),
236 message: format!(
237 "{}: {}",
238 strings::errors::CALL_NOT_FOUND,
239 strings::metadata::SET_CALL_NAME
240 ),
241 })?;
242
243 let call_index = call_variant.index;
244
245 let storage = parent.storage();
247 let slot_duration = match self.cached_slot_duration.load(Ordering::Acquire) {
248 0 => {
249 let duration = Self::get_slot_duration_from_runtime(
250 executor,
251 storage,
252 &metadata,
253 self.slot_duration_ms,
254 )
255 .await;
256 self.cached_slot_duration.store(duration, Ordering::Release);
257 duration
258 },
259 cached => cached,
260 };
261
262 let key = Self::timestamp_now_key();
264
265 let current_timestamp = match storage.get(parent.number, &key).await? {
266 Some(value) if value.value.is_some() => u64::decode(
267 &mut value
268 .value
269 .as_ref()
270 .expect("The match guard ensures it's Some; qed;")
271 .as_slice(),
272 )
273 .map_err(|e| BlockBuilderError::InherentProvider {
274 provider: self.identifier().to_string(),
275 message: format!("{}: {}", strings::errors::DECODE_FAILED, e),
276 })?,
277 _ => {
278 std::time::SystemTime::now()
281 .duration_since(std::time::UNIX_EPOCH)
282 .map(|d| d.as_millis() as u64)
283 .unwrap_or(0)
284 },
285 };
286
287 let new_timestamp = current_timestamp.saturating_add(slot_duration);
289
290 log::debug!(
291 "[Timestamp] current_timestamp={current_timestamp}, slot_duration={slot_duration}, new_timestamp={new_timestamp}"
292 );
293
294 let call = Self::encode_timestamp_set_call(pallet_index, call_index, new_timestamp);
296
297 let extrinsic = Self::encode_inherent_extrinsic(call);
299
300 Ok(vec![extrinsic])
301 }
302
303 async fn warmup(&self, parent: &Block, executor: &RuntimeExecutor) {
304 let metadata = match parent.metadata().await {
305 Ok(m) => m,
306 Err(e) => {
307 log::warn!("[Timestamp] Warmup: failed to get metadata: {e}");
308 return;
309 },
310 };
311 let storage = parent.storage();
312 let duration = Self::get_slot_duration_from_runtime(
313 executor,
314 storage,
315 &metadata,
316 self.slot_duration_ms,
317 )
318 .await;
319 self.cached_slot_duration.store(duration, Ordering::Release);
320 log::debug!("[Timestamp] Warmup: cached slot_duration={duration}ms");
321 }
322
323 fn invalidate_cache(&self) {
324 self.cached_slot_duration.store(0, Ordering::Release);
325 log::debug!("[Timestamp] Cache invalidated (runtime upgrade detected)");
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 const TEST_SLOT_DURATION_MS: u64 = 1_000;
335
336 const TEST_PALLET_INDEX: u8 = 3;
338
339 const TEST_CALL_INDEX: u8 = 0;
341
342 #[test]
343 fn new_creates_provider_with_slot_duration() {
344 let provider = TimestampInherent::new(TEST_SLOT_DURATION_MS);
345 assert_eq!(provider.slot_duration_ms, TEST_SLOT_DURATION_MS);
346 }
347
348 #[test]
349 fn default_relay_uses_configured_slot_duration() {
350 let provider = TimestampInherent::default_relay();
351 assert_eq!(provider.slot_duration_ms, DEFAULT_RELAY_SLOT_DURATION_MS);
352 }
353
354 #[test]
355 fn default_para_uses_configured_slot_duration() {
356 let provider = TimestampInherent::default_para();
357 assert_eq!(provider.slot_duration_ms, DEFAULT_PARA_SLOT_DURATION_MS);
358 }
359
360 #[test]
361 fn timestamp_now_key_is_32_bytes() {
362 let key = TimestampInherent::timestamp_now_key();
363 const TWOX128_OUTPUT_BYTES: usize = 16;
365 const STORAGE_KEY_LEN: usize = TWOX128_OUTPUT_BYTES * 2;
366 assert_eq!(key.len(), STORAGE_KEY_LEN);
367 }
368
369 #[test]
370 fn encode_timestamp_set_call_produces_valid_encoding() {
371 let call = TimestampInherent::encode_timestamp_set_call(
372 TEST_PALLET_INDEX,
373 TEST_CALL_INDEX,
374 1_000_000,
375 );
376
377 assert_eq!(call[0], TEST_PALLET_INDEX);
379 assert_eq!(call[1], TEST_CALL_INDEX);
381 assert!(call.len() > 2);
383 }
384
385 #[test]
386 fn encode_inherent_extrinsic_includes_version_and_length() {
387 let call = vec![TEST_PALLET_INDEX, TEST_CALL_INDEX, 1, 2, 3];
389 let extrinsic = TimestampInherent::encode_inherent_extrinsic(call.clone());
390
391 const EXPECTED_COMPACT_LEN: u8 = 0x18;
394 assert_eq!(extrinsic[0], EXPECTED_COMPACT_LEN);
395 assert_eq!(extrinsic[1], EXTRINSIC_FORMAT_VERSION);
397 assert_eq!(&extrinsic[2..], &call[..]);
399 }
400
401 #[test]
402 fn identifier_returns_timestamp() {
403 let provider = TimestampInherent::default();
404 assert_eq!(provider.identifier(), strings::IDENTIFIER);
405 }
406}