diff --git a/crates/canister-core/src/lib.rs b/crates/canister-core/src/lib.rs index 4f93871..6b197d1 100644 --- a/crates/canister-core/src/lib.rs +++ b/crates/canister-core/src/lib.rs @@ -65,18 +65,6 @@ pub fn retrieve(key: AssetKey) -> RcBytes { }) } -pub fn store(arg: StoreArg) { - let system_context = SystemContext::new(); - - with_state_mut(|s| { - if let Err(msg) = s.store(arg, &system_context) { - trap(&msg); - } - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }); -} - pub fn create_batch() -> CreateBatchResponse { let system_context = SystemContext::new(); @@ -95,54 +83,6 @@ pub fn create_chunks(arg: CreateChunksArg) -> CreateChunksResponse { }) } -pub fn create_asset(arg: CreateAssetArguments) { - with_state_mut(|s| { - if let Err(msg) = s.create_asset(arg) { - trap(&msg); - } - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }) -} - -pub fn set_asset_content(arg: SetAssetContentArguments) { - let system_context = SystemContext::new(); - - with_state_mut(|s| { - if let Err(msg) = s.set_asset_content(arg, &system_context) { - trap(&msg); - } - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }) -} - -pub fn unset_asset_content(arg: UnsetAssetContentArguments) { - with_state_mut(|s| { - if let Err(msg) = s.unset_asset_content(arg) { - trap(&msg); - } - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }) -} - -pub fn delete_asset(arg: DeleteAssetArguments) { - with_state_mut(|s| { - s.delete_asset(arg); - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }); -} - -pub fn clear() { - with_state_mut(|s| { - s.clear(); - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }); -} - pub async fn commit_batch(arg: CommitBatchArguments) { let system_context = SystemContext::new(); let arg_ref = &arg; @@ -236,16 +176,6 @@ pub fn get_asset_properties(key: AssetKey) -> AssetProperties { with_state(|s| s.get_asset_properties(key).unwrap_or_else(|msg| trap(&msg))) } -pub fn set_asset_properties(arg: SetAssetPropertiesArguments) { - with_state_mut(|s| { - if let Err(msg) = s.set_asset_properties(arg) { - trap(&msg); - } - s.on_redirect_rules_change(); - certified_data_set(s.root_hash()); - }) -} - pub fn get_configuration() -> ConfigurationResponse { with_state(|s| s.get_configuration()) } diff --git a/crates/canister-core/src/state.rs b/crates/canister-core/src/state.rs index 440a8a3..ac9871e 100644 --- a/crates/canister-core/src/state.rs +++ b/crates/canister-core/src/state.rs @@ -239,30 +239,6 @@ impl State { Ok(id_enc.content_chunks[0].clone()) } - pub fn store(&mut self, arg: StoreArg, system_context: &SystemContext) -> Result<(), String> { - let dependent_keys = self.dependent_keys(&arg.key); - let asset = self.assets.entry(arg.key.clone()).or_default(); - asset.content_type = arg.content_type; - - let hash = sha2::Sha256::digest(&arg.content).into(); - if let Some(provided_hash) = arg.sha256 { - if hash != provided_hash.as_ref() { - return Err("sha256 mismatch".to_string()); - } - } - - let encoding = asset.encodings.entry(arg.content_encoding).or_default(); - encoding.total_length = arg.content.len(); - encoding.content_chunks = vec![RcBytes::from(arg.content)]; - encoding.modified = Int::from(system_context.current_timestamp_ns); - encoding.sha256 = hash; - - on_asset_change(&mut self.asset_hashes, &arg.key, asset, dependent_keys); - self.last_state_update_timestamp_ns = system_context.current_timestamp_ns; - - Ok(()) - } - pub fn compute_state_hash(&mut self) -> ComputationStatus { if self.last_state_hash_timestamp != self.last_state_update_timestamp_ns { self.state_hash_computation = None; diff --git a/crates/canister-core/src/tests.rs b/crates/canister-core/src/tests.rs index 9ebf7f4..4ec4d29 100644 --- a/crates/canister-core/src/tests.rs +++ b/crates/canister-core/src/tests.rs @@ -1854,7 +1854,6 @@ mod enforce_limits { #[cfg(test)] mod last_state_update_timestamp { use super::*; - use crate::types::StoreArg; #[test] fn timestamp_updates_on_commit_batch() { @@ -1891,35 +1890,6 @@ mod last_state_update_timestamp { ); } - #[test] - fn timestamp_updates_on_store() { - let mut state = State::default(); - let system_context = mock_system_context(); - - // Initial timestamp should be 0 - assert_eq!(state.last_state_update_timestamp_ns(), 0); - - // Store an asset - state - .store( - StoreArg { - key: "/test.txt".to_string(), - content_type: "text/plain".to_string(), - content_encoding: "identity".to_string(), - content: ByteBuf::from(b"test content".to_vec()), - sha256: None, - }, - &system_context, - ) - .unwrap(); - - // Timestamp should be updated - assert_eq!( - state.last_state_update_timestamp_ns(), - system_context.current_timestamp_ns - ); - } - #[test] fn timestamp_updates_on_multiple_operations() { let mut state = State::default(); @@ -1928,20 +1898,25 @@ mod last_state_update_timestamp { // Initial timestamp should be 0 assert_eq!(state.last_state_update_timestamp_ns(), 0); - // First operation at time T1 + // First operation at time T1: create an asset. let initial_time = system_context.current_timestamp_ns; - state - .store( - StoreArg { - key: "/test.txt".to_string(), - content_type: "text/plain".to_string(), - content_encoding: "identity".to_string(), - content: ByteBuf::from(b"test content".to_vec()), - sha256: None, + let batch_id = state.create_batch(&system_context).unwrap(); + run_computation_until_completion(|progress| { + state.commit_batch( + &CommitBatchArguments { + batch_id: batch_id.clone(), + operations: vec![BatchOperation::CreateAsset(CreateAssetArguments { + key: "/test.txt".to_string(), + content_type: "text/plain".to_string(), + max_age: None, + headers: None, + })], }, + progress, &system_context, ) - .unwrap(); + }) + .unwrap(); assert_eq!(state.last_state_update_timestamp_ns(), initial_time); // Second operation at time T2 (advanced) @@ -1980,19 +1955,24 @@ mod last_state_update_timestamp { let mut state = State::default(); let system_context = mock_system_context(); - // Store an asset to update timestamp - state - .store( - StoreArg { - key: "/test.txt".to_string(), - content_type: "text/plain".to_string(), - content_encoding: "identity".to_string(), - content: ByteBuf::from(b"test content".to_vec()), - sha256: None, + // Commit a batch to update the timestamp. + let batch_id = state.create_batch(&system_context).unwrap(); + run_computation_until_completion(|progress| { + state.commit_batch( + &CommitBatchArguments { + batch_id: batch_id.clone(), + operations: vec![BatchOperation::CreateAsset(CreateAssetArguments { + key: "/test.txt".to_string(), + content_type: "text/plain".to_string(), + max_age: None, + headers: None, + })], }, + progress, &system_context, ) - .unwrap(); + }) + .unwrap(); let expected_timestamp = state.last_state_update_timestamp_ns(); assert_eq!(expected_timestamp, system_context.current_timestamp_ns); diff --git a/crates/canister-core/src/types.rs b/crates/canister-core/src/types.rs index 3ec1162..51816d6 100644 --- a/crates/canister-core/src/types.rs +++ b/crates/canister-core/src/types.rs @@ -90,15 +90,6 @@ pub struct DeleteBatchArguments { pub batch_id: BatchId, } -#[derive(Clone, Debug, CandidType, Deserialize)] -pub struct StoreArg { - pub key: AssetKey, - pub content_type: String, - pub content_encoding: String, - pub content: ByteBuf, - pub sha256: Option, -} - #[derive(Clone, Debug, CandidType, Deserialize)] pub struct GetArg { pub key: AssetKey, diff --git a/crates/canister/assets.did b/crates/canister/assets.did index fa18f69..ec34556 100644 --- a/crates/canister/assets.did +++ b/crates/canister/assets.did @@ -191,27 +191,12 @@ service: () -> { tree: blob; }) query; - // ───────── Asset mutations ───────── - - // Single call to create an asset with content for a single content encoding that - // fits within the message ingress limit. - store: (record { - key: Key; - content_type: text; - content_encoding: text; - content: blob; - sha256: opt blob; - }) -> (); - - create_asset: (CreateAssetArguments) -> (); - set_asset_content: (SetAssetContentArguments) -> (); - unset_asset_content: (UnsetAssetContentArguments) -> (); - set_asset_properties: (SetAssetPropertiesArguments) -> (); - - delete_asset: (DeleteAssetArguments) -> (); - clear: () -> (); - // ───────── Batch upload ───────── + // + // Assets are only ever mutated through committed batches — there are no + // single-asset mutation endpoints. A batch's `operations` carry the + // per-asset changes (create, set/unset content, set properties, delete, + // clear), applied atomically when the batch is committed. create_batch : () -> (record { batch_id: BatchId }); create_chunks: (record { batch_id: BatchId; content: vec blob }) -> (record { chunk_ids: vec ChunkId }); diff --git a/crates/canister/src/lib.rs b/crates/canister/src/lib.rs index a4f1ae9..4112441 100644 --- a/crates/canister/src/lib.rs +++ b/crates/canister/src/lib.rs @@ -11,10 +11,8 @@ use canister_core::{ state::CertifiedTree, types::{ AssetProperties, CommitBatchArguments, ConfigurationResponse, ConfigureArguments, - CreateAssetArguments, CreateBatchResponse, CreateChunksArg, CreateChunksResponse, - DeleteAssetArguments, DeleteBatchArguments, GetArg, GetChunkArg, GetChunkResponse, - ListRequest, SetAssetContentArguments, SetAssetPropertiesArguments, StateInfo, StoreArg, - UnsetAssetContentArguments, + CreateBatchResponse, CreateChunksArg, CreateChunksResponse, DeleteBatchArguments, GetArg, + GetChunkArg, GetChunkResponse, ListRequest, StateInfo, }, }; use ic_cdk::{init, post_upgrade, pre_upgrade, query, update}; @@ -126,11 +124,6 @@ fn can_sync() -> bool { canister_core::can_sync() } -#[update(guard = "guard_can_sync")] -fn store(arg: StoreArg) { - canister_core::store(arg) -} - #[update(guard = "guard_can_sync")] fn create_batch() -> CreateBatchResponse { canister_core::create_batch() @@ -141,31 +134,6 @@ fn create_chunks(arg: CreateChunksArg) -> CreateChunksResponse { canister_core::create_chunks(arg) } -#[update(guard = "guard_can_sync")] -fn create_asset(arg: CreateAssetArguments) { - canister_core::create_asset(arg) -} - -#[update(guard = "guard_can_sync")] -fn set_asset_content(arg: SetAssetContentArguments) { - canister_core::set_asset_content(arg) -} - -#[update(guard = "guard_can_sync")] -fn unset_asset_content(arg: UnsetAssetContentArguments) { - canister_core::unset_asset_content(arg) -} - -#[update(guard = "guard_can_sync")] -fn delete_asset(arg: DeleteAssetArguments) { - canister_core::delete_asset(arg) -} - -#[update(guard = "guard_can_sync")] -fn clear() { - canister_core::clear() -} - #[update(guard = "guard_can_sync")] async fn commit_batch(arg: CommitBatchArguments) { canister_core::commit_batch(arg).await @@ -181,11 +149,6 @@ fn delete_batch(arg: DeleteBatchArguments) { canister_core::delete_batch(arg) } -#[update(guard = "guard_can_sync")] -fn set_asset_properties(arg: SetAssetPropertiesArguments) { - canister_core::set_asset_properties(arg) -} - #[update(guard = "guard_can_sync")] fn get_configuration() -> ConfigurationResponse { canister_core::get_configuration()