diff --git a/rust/signed_doc/Cargo.toml b/rust/signed_doc/Cargo.toml index 127a5dd5114..4af954baef3 100644 --- a/rust/signed_doc/Cargo.toml +++ b/rust/signed_doc/Cargo.toml @@ -33,6 +33,7 @@ futures = "0.3.31" ed25519-bip32 = "0.4.1" # used by the `mk_signed_doc` cli tool tracing = "0.1.40" thiserror = "2.0.11" +chrono = "0.4.42" [dev-dependencies] base64-url = "3.0.0" diff --git a/rust/signed_doc/src/validator/rules/chain/tests.rs b/rust/signed_doc/src/validator/rules/chain/tests.rs index c92f9642ec7..9b3e12956e6 100644 --- a/rust/signed_doc/src/validator/rules/chain/tests.rs +++ b/rust/signed_doc/src/validator/rules/chain/tests.rs @@ -8,19 +8,17 @@ use crate::{ }; mod helper { - use std::time::{Duration, SystemTime, UNIX_EPOCH}; - use catalyst_types::uuid::UuidV7; + use chrono::{Duration, Utc}; use uuid::{Timestamp, Uuid}; - pub(super) fn get_now_plus_uuidv7(secs: u64) -> UuidV7 { - let future_time = SystemTime::now() - .checked_add(Duration::from_secs(secs)) - .unwrap(); - let duration_since_epoch = future_time.duration_since(UNIX_EPOCH).unwrap(); + pub(super) fn get_now_plus_uuidv7(secs: i64) -> UuidV7 { + let future_time = Utc::now() + .checked_add_signed(Duration::seconds(secs)) + .expect("time overflow in future_time calculation"); - let unix_secs = duration_since_epoch.as_secs(); - let nanos = duration_since_epoch.subsec_nanos(); + let unix_secs = u64::try_from(future_time.timestamp()).unwrap_or(0); + let nanos = future_time.timestamp_subsec_nanos(); let ts = Timestamp::from_unix(uuid::NoContext, unix_secs, nanos); let uuid = Uuid::new_v7(ts); diff --git a/rust/signed_doc/src/validator/rules/id/mod.rs b/rust/signed_doc/src/validator/rules/id/mod.rs index d094002f877..4546aee54ea 100644 --- a/rust/signed_doc/src/validator/rules/id/mod.rs +++ b/rust/signed_doc/src/validator/rules/id/mod.rs @@ -3,9 +3,8 @@ #[cfg(test)] mod tests; -use std::time::{Duration, SystemTime}; - use anyhow::Context; +use chrono::{DateTime, Utc}; use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument}; @@ -16,11 +15,10 @@ pub(crate) struct IdRule; impl IdRule { /// Validates document `id` field on the timestamps: /// 1. If `provider.future_threshold()` not `None`, document `id` cannot be too far in - /// the future (`future_threshold` arg) from `SystemTime::now()` based on the - /// provide threshold - /// 2. If `provider.future_threshold()` not `None`, document `id` cannot be too far - /// behind (`past_threshold` arg) from `SystemTime::now()` based on the provide + /// the future (`future_threshold` arg) from `Utc::now()` based on the provided /// threshold + /// 2. If `provider.past_threshold()` not `None`, document `id` cannot be too far + /// behind (`past_threshold` arg) from `Utc::now()` based on the provided threshold #[allow(clippy::unused_async)] pub(crate) async fn check( &self, @@ -46,21 +44,23 @@ impl IdRule { .ok_or(anyhow::anyhow!("Document `id` field must be a UUIDv7"))? .to_unix(); - let Some(id_time) = - SystemTime::UNIX_EPOCH.checked_add(Duration::new(id_time_secs, id_time_nanos)) + let Some(id_time) = i64::try_from(id_time_secs) + .ok() + .and_then(|id_time_secs| DateTime::from_timestamp(id_time_secs, id_time_nanos)) else { doc.report().invalid_value( "id", &id.to_string(), - "Must a valid duration since `UNIX_EPOCH`", - "Cannot instantiate a valid `SystemTime` value from the provided `id` field timestamp.", + "Must a valid UTC date time since `UNIX_EPOCH`", + "Cannot instantiate a valid `DateTime` value from the provided `id` field timestamp.", ); return Ok(false); }; - let now = SystemTime::now(); + let now = Utc::now(); + let time_delta = id_time.signed_duration_since(now); - if let Ok(id_age) = id_time.duration_since(now) { + if let Ok(id_age) = time_delta.to_std() { // `now` is earlier than `id_time` if let Some(future_threshold) = provider.future_threshold() { if id_age > future_threshold { @@ -68,15 +68,16 @@ impl IdRule { "id", &id.to_string(), "id < now + future_threshold", - &format!("Document Version timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now:?}"), + &format!("Document ID timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now}"), ); is_valid = false; } } } else { // `id_time` is earlier than `now` - let id_age = now - .duration_since(id_time) + let id_age = time_delta + .abs() + .to_std() .context("BUG! `id_time` must be earlier than `now` at this place")?; if let Some(past_threshold) = provider.past_threshold() { @@ -85,7 +86,7 @@ impl IdRule { "id", &id.to_string(), "id > now - past_threshold", - &format!("Document Version timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",), + &format!("Document ID timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",), ); is_valid = false; } diff --git a/rust/signed_doc/src/validator/rules/id/tests.rs b/rust/signed_doc/src/validator/rules/id/tests.rs index 864c2e912b7..42f27bbcc89 100644 --- a/rust/signed_doc/src/validator/rules/id/tests.rs +++ b/rust/signed_doc/src/validator/rules/id/tests.rs @@ -1,5 +1,4 @@ -use std::time::SystemTime; - +use chrono::Utc; use test_case::test_case; use uuid::{Timestamp, Uuid}; @@ -22,46 +21,46 @@ use crate::{ #[test_case( #[allow(clippy::arithmetic_side_effects)] |provider| { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let to_far_in_past = Uuid::new_v7(Timestamp::from_unix_time( - now - provider.past_threshold().unwrap().as_secs() - 1, - 0, - 0, - 0, - )) - .try_into() - .unwrap(); + let now = Utc::now().timestamp(); + let past_threshold_secs = i64::try_from(provider.past_threshold().unwrap().as_secs()).unwrap_or(0); + + let too_far_in_past = Uuid::new_v7(Timestamp::from_unix_time( + u64::try_from(now - past_threshold_secs - 1).unwrap_or(0), + 0, + 0, + 0, + )) + .try_into() + .unwrap(); + Builder::new() - .with_metadata_field(SupportedField::Id(to_far_in_past)) + .with_metadata_field(SupportedField::Id(too_far_in_past)) .build() } => false; - "`id` to far in past" + "`id` too far in past" )] #[test_case( #[allow(clippy::arithmetic_side_effects)] |provider| { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let to_far_in_future = Uuid::new_v7(Timestamp::from_unix_time( - now + provider.future_threshold().unwrap().as_secs() + 1, - 0, - 0, - 0, - )) - .try_into() - .unwrap(); + let now = Utc::now().timestamp(); + let future_threshold_secs = i64::try_from(provider.future_threshold().unwrap().as_secs()).unwrap_or(0); + + let too_far_in_future = Uuid::new_v7(Timestamp::from_unix_time( + u64::try_from(now + future_threshold_secs + 1).unwrap_or(0), + 0, + 0, + 0, + )) + .try_into() + .unwrap(); + Builder::new() - .with_metadata_field(SupportedField::Id(to_far_in_future)) + .with_metadata_field(SupportedField::Id(too_far_in_future)) .build() } => false; - "`id` to far in future" + "`id` too far in future" )] #[test_case( |_| { diff --git a/rust/signed_doc/src/validator/rules/ver/tests.rs b/rust/signed_doc/src/validator/rules/ver/tests.rs index 38ad696b420..b2870e0a011 100644 --- a/rust/signed_doc/src/validator/rules/ver/tests.rs +++ b/rust/signed_doc/src/validator/rules/ver/tests.rs @@ -1,5 +1,4 @@ -use std::time::SystemTime; - +use chrono::Utc; use test_case::test_case; use uuid::{Timestamp, Uuid}; @@ -24,13 +23,12 @@ use crate::{ #[allow(clippy::arithmetic_side_effects)] |provider| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let first_doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) @@ -38,9 +36,10 @@ use crate::{ .build(); provider.add_document(None, &first_doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver)) @@ -54,13 +53,12 @@ use crate::{ #[allow(clippy::arithmetic_side_effects)] |provider| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let first_doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) @@ -68,9 +66,10 @@ use crate::{ .build(); provider.add_document(None, &first_doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver)) @@ -84,13 +83,12 @@ use crate::{ #[allow(clippy::arithmetic_side_effects)] |provider| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) @@ -98,43 +96,42 @@ use crate::{ .build(); provider.add_document(None, &doc).unwrap(); - - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 3, 0, 0, 0)) + let ver_1 = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 3).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); let doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) - .with_metadata_field(SupportedField::Ver(ver)) + .with_metadata_field(SupportedField::Ver(ver_1)) .with_metadata_field(SupportedField::Type(doc_type.into())) .build(); provider.add_document(None, &doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 2, 0, 0, 0)) + let ver_2 = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 2).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) - .with_metadata_field(SupportedField::Ver(ver)) + .with_metadata_field(SupportedField::Ver(ver_2)) .with_metadata_field(SupportedField::Type(doc_type.into())) .build() } => false; - "`ver` less than `ver` field for of the latest known document" + "`ver` less than `ver` field for the latest known document" )] #[test_case( #[allow(clippy::arithmetic_side_effects)] |_| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver)) @@ -148,13 +145,12 @@ use crate::{ #[allow(clippy::arithmetic_side_effects)] |provider| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let first_doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) @@ -162,9 +158,10 @@ use crate::{ .build(); provider.add_document(None, &first_doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver)) @@ -177,22 +174,22 @@ use crate::{ #[allow(clippy::arithmetic_side_effects)] |provider| { let doc_type = UuidV4::new(); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let first_doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) .build(); provider.add_document(None, &first_doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver)) @@ -205,13 +202,12 @@ use crate::{ #[test_case( #[allow(clippy::arithmetic_side_effects)] |provider| { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let id = Uuid::new_v7(Timestamp::from_unix_time(now - 1, 0, 0, 0)) + let now = Utc::now().timestamp(); + + let id = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now - 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + let first_doc = Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(id)) @@ -219,9 +215,10 @@ use crate::{ .build(); provider.add_document(None, &first_doc).unwrap(); - let ver = Uuid::new_v7(Timestamp::from_unix_time(now + 1, 0, 0, 0)) + let ver = Uuid::new_v7(Timestamp::from_unix_time(u64::try_from(now + 1).unwrap_or(0), 0, 0, 0)) .try_into() .unwrap(); + Builder::new() .with_metadata_field(SupportedField::Id(id)) .with_metadata_field(SupportedField::Ver(ver))