Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 0 additions & 70 deletions crates/canister-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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;
Expand Down Expand Up @@ -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())
}
Expand Down
24 changes: 0 additions & 24 deletions crates/canister-core/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, (), ()> {
if self.last_state_hash_timestamp != self.last_state_update_timestamp_ns {
self.state_hash_computation = None;
Expand Down
80 changes: 30 additions & 50 deletions crates/canister-core/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 0 additions & 9 deletions crates/canister-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ByteBuf>,
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct GetArg {
pub key: AssetKey,
Expand Down
25 changes: 5 additions & 20 deletions crates/canister/assets.did
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
41 changes: 2 additions & 39 deletions crates/canister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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()
Expand Down