diff --git a/CHANGELOG.md b/CHANGELOG.md index 63bb23f585..63f9d088aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ The `dfx cycles` command no longer needs nor accepts the `--cycles-ledger-canist Updated to candid 0.10, ic-cdk 0.12, and ic-cdk-timers 0.6 +### fix: store playground canister acquisition timestamps with nanosecond precision on all platforms + +They've always been stored with nanosecond precisions on Linux and Macos. +Now they are stored with nanosecond precision on Windows too. + # 0.15.3 ### fix: allow `http://localhost:*` as `connect-src` in the asset canister's CSP diff --git a/Cargo.lock b/Cargo.lock index daf1bd1949..da90985982 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1485,6 +1485,7 @@ dependencies = [ "clap", "dialoguer", "directories-next", + "dunce", "flate2", "hex", "humantime-serde", diff --git a/src/canisters/frontend/ic-asset/src/sync.rs b/src/canisters/frontend/ic-asset/src/sync.rs index 2962c2f75d..e892225c8a 100644 --- a/src/canisters/frontend/ic-asset/src/sync.rs +++ b/src/canisters/frontend/ic-asset/src/sync.rs @@ -239,7 +239,7 @@ pub(crate) fn gather_asset_descriptors( let entries = WalkDir::new(&dir) .into_iter() .filter_entry(|entry| { - if let Ok(canonical_path) = &entry.path().canonicalize() { + if let Ok(canonical_path) = &dfx_core::fs::canonicalize(entry.path()) { let config = configuration .get_asset_config(canonical_path) .unwrap_or_default(); diff --git a/src/dfx-core/Cargo.toml b/src/dfx-core/Cargo.toml index f1b9fef3f3..c55966067d 100644 --- a/src/dfx-core/Cargo.toml +++ b/src/dfx-core/Cargo.toml @@ -17,6 +17,7 @@ candid = { workspace = true, features = ["random"] } clap = { workspace = true, features = ["string"] } dialoguer = "0.10.0" directories-next.workspace = true +dunce = "1.0" flate2 = { workspace = true, default-features = false, features = ["zlib-ng"] } hex = { workspace = true, features = ["serde"] } humantime-serde = "1.1.1" diff --git a/src/dfx-core/src/config/model/canister_id_store.rs b/src/dfx-core/src/config/model/canister_id_store.rs index 76485bef5a..68693ac0f1 100644 --- a/src/dfx-core/src/config/model/canister_id_store.rs +++ b/src/dfx-core/src/config/model/canister_id_store.rs @@ -23,11 +23,14 @@ pub type CanisterIds = BTreeMap; pub type CanisterTimestamps = BTreeMap; +// OffsetDateTime has nanosecond precision, while SystemTime is OS-dependent (100ns on Windows) +pub type AcquisitionDateTime = OffsetDateTime; + #[derive(Debug, Clone, Default)] -pub struct NetworkNametoCanisterTimestamp(BTreeMap); +pub struct NetworkNametoCanisterTimestamp(BTreeMap); impl Deref for NetworkNametoCanisterTimestamp { - type Target = BTreeMap; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 @@ -48,7 +51,7 @@ impl Serialize for NetworkNametoCanisterTimestamp { let out = self.0.iter().map(|(key, time)| { ( key, - OffsetDateTime::from(*time) + AcquisitionDateTime::from(*time) .format(&Rfc3339) .expect("Failed to serialise timestamp"), ) @@ -63,12 +66,12 @@ impl<'de> Deserialize<'de> for NetworkNametoCanisterTimestamp { D: serde::Deserializer<'de>, { let map: BTreeMap = Deserialize::deserialize(deserializer)?; - let btree: BTreeMap = map + let btree: BTreeMap = map .into_iter() - .map(|(key, timestamp)| (key, OffsetDateTime::parse(×tamp, &Rfc3339))) + .map(|(key, timestamp)| (key, AcquisitionDateTime::parse(×tamp, &Rfc3339))) .try_fold(BTreeMap::new(), |mut map, (key, result)| match result { Ok(value) => { - map.insert(key, SystemTime::from(value)); + map.insert(key, value); Ok(map) } Err(err) => Err(err), @@ -178,7 +181,7 @@ impl CanisterIdStore { Ok(store) } - pub fn get_timestamp(&self, canister_name: &str) -> Option<&SystemTime> { + pub fn get_timestamp(&self, canister_name: &str) -> Option<&AcquisitionDateTime> { self.acquisition_timestamps .get(canister_name) .and_then(|timestamp_map| timestamp_map.get(&self.network_descriptor.name)) @@ -277,7 +280,7 @@ impl CanisterIdStore { &mut self, canister_name: &str, canister_id: &str, - timestamp: Option, + timestamp: Option, ) -> Result<(), CanisterIdStoreError> { let network_name = &self.network_descriptor.name; match self.ids.get_mut(canister_name) { diff --git a/src/dfx-core/src/fs/mod.rs b/src/dfx-core/src/fs/mod.rs index 83eb3d412c..adddaf3d49 100644 --- a/src/dfx-core/src/fs/mod.rs +++ b/src/dfx-core/src/fs/mod.rs @@ -11,7 +11,7 @@ use std::fs::{Metadata, Permissions, ReadDir}; use std::path::{Path, PathBuf}; pub fn canonicalize(path: &Path) -> Result { - path.canonicalize() + dunce::canonicalize(path) .map_err(|err| FsError::new(CanonicalizePathFailed(path.to_path_buf(), err))) } diff --git a/src/dfx/src/lib/builders/mod.rs b/src/dfx/src/lib/builders/mod.rs index 231192b513..1fe50f1cef 100644 --- a/src/dfx/src/lib/builders/mod.rs +++ b/src/dfx/src/lib/builders/mod.rs @@ -104,12 +104,13 @@ pub trait CanisterBuilder { .context("`output` must not be None")?; if generate_output_dir.exists() { - let generate_output_dir = generate_output_dir.canonicalize().with_context(|| { - format!( - "Failed to canonicalize output dir {}.", - generate_output_dir.to_string_lossy() - ) - })?; + let generate_output_dir = dfx_core::fs::canonicalize(generate_output_dir) + .with_context(|| { + format!( + "Failed to canonicalize output dir {}.", + generate_output_dir.to_string_lossy() + ) + })?; if !generate_output_dir.starts_with(info.get_workspace_root()) { bail!( "Directory at '{}' is outside the workspace root.", @@ -321,9 +322,7 @@ fn ensure_trailing_newline(s: String) -> String { pub fn run_command(args: Vec, vars: &[Env<'_>], cwd: &Path) -> DfxResult<()> { let (command_name, arguments) = args.split_first().unwrap(); - let canonicalized = cwd - .join(command_name) - .canonicalize() + let canonicalized = dfx_core::fs::canonicalize(&cwd.join(command_name)) .or_else(|_| which::which(command_name)) .map_err(|_| anyhow!("Cannot find command or file {command_name}"))?; let mut cmd = Command::new(&canonicalized); diff --git a/src/dfx/src/lib/canister_info/assets.rs b/src/dfx/src/lib/canister_info/assets.rs index 0befbdf01a..f66601ca15 100644 --- a/src/dfx/src/lib/canister_info/assets.rs +++ b/src/dfx/src/lib/canister_info/assets.rs @@ -38,7 +38,7 @@ impl AssetsCanisterInfo { let input_root = &self.input_root; let source_paths: Vec = source_paths.iter().map(|x| input_root.join(x)).collect(); for source_path in &source_paths { - let canonical = source_path.canonicalize().with_context(|| { + let canonical = dfx_core::fs::canonicalize(source_path).with_context(|| { format!( "Unable to determine canonical location of asset source path {}", source_path.to_string_lossy() diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index 1bd13cd42b..3f94596cec 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -414,9 +414,7 @@ fn run_post_install_task( let cwd = canister.get_workspace_root(); let words = shell_words::split(task) .with_context(|| format!("Error interpreting post-install task `{task}`"))?; - let canonicalized = cwd - .join(&words[0]) - .canonicalize() + let canonicalized = dfx_core::fs::canonicalize(&cwd.join(&words[0])) .or_else(|_| which::which(&words[0])) .map_err(|_| anyhow!("Cannot find command or file {}", &words[0]))?; let mut command = Command::new(&canonicalized); diff --git a/src/dfx/src/lib/operations/canister/motoko_playground.rs b/src/dfx/src/lib/operations/canister/motoko_playground.rs index e9f4d33dd4..8e8f9ea506 100644 --- a/src/dfx/src/lib/operations/canister/motoko_playground.rs +++ b/src/dfx/src/lib/operations/canister/motoko_playground.rs @@ -1,17 +1,16 @@ +use crate::lib::{environment::Environment, error::DfxResult}; +use anyhow::{bail, Context}; +use candid::{encode_args, CandidType, Decode, Deserialize, Encode, Principal}; +use dfx_core::config::model::canister_id_store::AcquisitionDateTime; use dfx_core::config::model::network_descriptor::{ NetworkTypeDescriptor, MAINNET_MOTOKO_PLAYGROUND_CANISTER_ID, }; -use num_traits::ToPrimitive; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use anyhow::{bail, Context}; -use candid::{encode_args, CandidType, Decode, Deserialize, Encode, Principal}; use fn_error_context::context; use ic_utils::interfaces::management_canister::builders::InstallMode; +use num_traits::ToPrimitive; use rand::Rng; use slog::{debug, info}; - -use crate::lib::{environment::Environment, error::DfxResult}; +use std::time::SystemTime; /// Arguments for the `getCanisterId` call. #[derive(CandidType)] @@ -28,19 +27,17 @@ pub struct CanisterInfo { } impl CanisterInfo { - #[context("Failed to construct playground canister info.")] - pub fn from(id: Principal, timestamp: &SystemTime) -> DfxResult { - let timestamp = candid::Int::from(timestamp.duration_since(UNIX_EPOCH)?.as_nanos()); - Ok(Self { id, timestamp }) + pub fn from(id: Principal, timestamp: &AcquisitionDateTime) -> Self { + let timestamp = candid::Int::from(timestamp.unix_timestamp_nanos()); + Self { id, timestamp } } - #[context("Failed to turn CanisterInfo into SystemTime")] - pub fn get_timestamp(&self) -> DfxResult { - UNIX_EPOCH - .checked_add(Duration::from_nanos( - self.timestamp.0.to_u64().context("u64 overflow")?, - )) - .context("Failed to make absolute time from offset") + #[context("Failed to get timestamp from CanisterInfo")] + pub fn get_timestamp(&self) -> DfxResult { + AcquisitionDateTime::from_unix_timestamp_nanos( + self.timestamp.0.to_i128().context("i128 overflow")?, + ) + .context("Failed to make unix timestamp from nanos") } } @@ -122,7 +119,7 @@ pub async fn reserve_canister_with_playground( pub async fn authorize_asset_uploader( env: &dyn Environment, canister_id: Principal, - canister_timestamp: &SystemTime, + canister_timestamp: &AcquisitionDateTime, principal_to_authorize: &Principal, ) -> DfxResult { let agent = env.get_agent(); @@ -135,7 +132,7 @@ pub async fn authorize_asset_uploader( } else { bail!("Trying to authorize asset uploader on non-playground network.") }; - let canister_info = CanisterInfo::from(canister_id, canister_timestamp)?; + let canister_info = CanisterInfo::from(canister_id, canister_timestamp); let nested_arg = Encode!(&principal_to_authorize)?; let call_arg = Encode!(&canister_info, &"authorize", &nested_arg)?; @@ -152,13 +149,13 @@ pub async fn authorize_asset_uploader( pub async fn playground_install_code( env: &dyn Environment, canister_id: Principal, - canister_timestamp: &SystemTime, + canister_timestamp: &AcquisitionDateTime, arg: &[u8], wasm_module: &[u8], mode: InstallMode, is_asset_canister: bool, -) -> DfxResult { - let canister_info = CanisterInfo::from(canister_id, canister_timestamp)?; +) -> DfxResult { + let canister_info = CanisterInfo::from(canister_id, canister_timestamp); let agent = env.get_agent(); let playground_canister = match env.get_network_descriptor().r#type { NetworkTypeDescriptor::Playground {