Skip to content

Commit 54897b5

Browse files
authored
feat(frontend-canister): create_chunks (#3898)
1 parent d838a38 commit 54897b5

File tree

9 files changed

+145
-30
lines changed

9 files changed

+145
-30
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ This applies to the following:
3636
- tech stack value computation
3737
- packtool (vessel, mops etc)
3838

39+
## Dependencies
40+
41+
### Frontend canister
42+
43+
Added `create_chunks`. It has the same behavior as `create_chunk`, except that it takes a `vec blob` and returns a `vec BatchId` instead of non-`vec` variants.
44+
45+
Module hash: 3a533f511b3960b4186e76cf9abfbd8222a2c507456a66ec55671204ee70cae3
46+
3947
# 0.23.0
4048

4149
### feat: Add canister snapshots

docs/design/asset-canister-interface.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ Required Permission: [Prepare](#permission-prepare)
267267
create_chunk: (
268268
record {
269269
batch_id: BatchId;
270-
content: blob
270+
content: blob
271271
}
272272
) -> (record {
273273
chunk_id: ChunkId
@@ -286,6 +286,31 @@ Preconditions:
286286

287287
Required Permission: [Prepare](#permission-prepare)
288288

289+
### Method: `create_chunks`
290+
291+
```candid
292+
create_chunks: (
293+
record {
294+
batch_id: BatchId;
295+
content: vec blob;
296+
}
297+
) -> (
298+
chunk_ids: vec ChunkId;
299+
);
300+
```
301+
302+
This method stores a number of chunks and extends the batch expiry time.
303+
304+
When creating chunks for a given content encoding, the size of each chunk except the last must be the same.
305+
306+
The asset canister must retain all data related to a batch for at least the [Minimum Batch Retention Duration](#constant-minimum-batch-retention-duration) after creating a chunk in a batch.
307+
308+
Preconditions:
309+
- The batch exists.
310+
- Creation of the chunk would not exceed chunk creation limits.
311+
312+
Required Permission: [Prepare](#permission-prepare)
313+
289314
### Method: `commit_batch`
290315

291316
```candid

src/canisters/frontend/ic-certified-assets/assets.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ service: (asset_canister_args: opt AssetCanisterArgs) -> {
199199
create_batch : (record {}) -> (record { batch_id: BatchId });
200200

201201
create_chunk: (record { batch_id: BatchId; content: blob }) -> (record { chunk_id: ChunkId });
202+
create_chunks: (record { batch_id: BatchId; content: vec blob }) -> (record { chunk_ids: vec ChunkId });
202203

203204
// Perform all operations successfully, or reject
204205
commit_batch: (CommitBatchArguments) -> ();

src/canisters/frontend/ic-certified-assets/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ fn create_chunk(arg: CreateChunkArg) -> CreateChunkResponse {
167167
})
168168
}
169169

170+
#[update(guard = "can_prepare")]
171+
#[candid_method(update)]
172+
fn create_chunks(arg: CreateChunksArg) -> CreateChunksResponse {
173+
STATE.with(|s| match s.borrow_mut().create_chunks(arg, time()) {
174+
Ok(chunk_ids) => CreateChunksResponse { chunk_ids },
175+
Err(msg) => trap(&msg),
176+
})
177+
}
178+
170179
#[update(guard = "can_commit")]
171180
#[candid_method(update)]
172181
fn create_asset(arg: CreateAssetArguments) {

src/canisters/frontend/ic-certified-assets/src/state_machine.rs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -582,43 +582,73 @@ impl State {
582582
}
583583

584584
pub fn create_chunk(&mut self, arg: CreateChunkArg, now: u64) -> Result<ChunkId, String> {
585+
let ids = self.create_chunks_helper(arg.batch_id, vec![arg.content], now)?;
586+
ids.into_iter()
587+
.next()
588+
.ok_or_else(|| "Bug: created chunk did not return a chunk id.".to_string())
589+
}
590+
591+
pub fn create_chunks(
592+
&mut self,
593+
CreateChunksArg {
594+
batch_id,
595+
content: chunks,
596+
}: CreateChunksArg,
597+
now: u64,
598+
) -> Result<Vec<ChunkId>, String> {
599+
self.create_chunks_helper(batch_id, chunks, now)
600+
}
601+
602+
/// Post-condition: `chunks.len() == output_chunk_ids.len()`
603+
fn create_chunks_helper(
604+
&mut self,
605+
batch_id: Nat,
606+
chunks: Vec<ByteBuf>,
607+
now: u64,
608+
) -> Result<Vec<ChunkId>, String> {
585609
if let Some(max_chunks) = self.configuration.max_chunks {
586-
if self.chunks.len() + 1 > max_chunks as usize {
610+
if self.chunks.len() + chunks.len() > max_chunks as usize {
587611
return Err("chunk limit exceeded".to_string());
588612
}
589613
}
590614
if let Some(max_bytes) = self.configuration.max_bytes {
591615
let current_total_bytes = &self.batches.iter().fold(0, |acc, (_batch_id, batch)| {
592616
acc + batch.chunk_content_total_size
593617
});
594-
595-
if current_total_bytes + arg.content.as_ref().len() > max_bytes as usize {
618+
let new_bytes: usize = chunks.iter().map(|chunk| chunk.len()).sum();
619+
if current_total_bytes + new_bytes > max_bytes as usize {
596620
return Err("byte limit exceeded".to_string());
597621
}
598622
}
599623
let batch = self
600624
.batches
601-
.get_mut(&arg.batch_id)
625+
.get_mut(&batch_id)
602626
.ok_or_else(|| "batch not found".to_string())?;
603627
if batch.commit_batch_arguments.is_some() {
604628
return Err("batch has been proposed".to_string());
605629
}
606630

607631
batch.expires_at = Int::from(now + BATCH_EXPIRY_NANOS);
608632

609-
let chunk_id = self.next_chunk_id.clone();
610-
self.next_chunk_id += 1_u8;
611-
batch.chunk_content_total_size += arg.content.as_ref().len();
612-
613-
self.chunks.insert(
614-
chunk_id.clone(),
615-
Chunk {
616-
batch_id: arg.batch_id,
617-
content: RcBytes::from(arg.content),
618-
},
619-
);
633+
let chunks_len = chunks.len();
634+
635+
let mut chunk_ids = Vec::with_capacity(chunks.len());
636+
for chunk in chunks {
637+
let chunk_id = self.next_chunk_id.clone();
638+
self.next_chunk_id += 1_u8;
639+
batch.chunk_content_total_size += chunk.len();
640+
self.chunks.insert(
641+
chunk_id.clone(),
642+
Chunk {
643+
batch_id: batch_id.clone(),
644+
content: RcBytes::from(chunk),
645+
},
646+
);
647+
chunk_ids.push(chunk_id);
648+
}
620649

621-
Ok(chunk_id)
650+
debug_assert!(chunks_len == chunk_ids.len());
651+
Ok(chunk_ids)
622652
}
623653

624654
pub fn commit_batch(&mut self, arg: CommitBatchArguments, now: u64) -> Result<(), String> {

src/canisters/frontend/ic-certified-assets/src/tests.rs

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::types::{
99
SetAssetPropertiesArguments,
1010
};
1111
use crate::url_decode::{url_decode, UrlDecodeError};
12+
use crate::CreateChunksArg;
1213
use candid::{Nat, Principal};
1314
use ic_certification_testing::CertificateBuilder;
1415
use ic_crypto_tree_hash::Digest;
@@ -724,14 +725,24 @@ fn cannot_create_chunk_in_proposed_batch_() {
724725
const BODY: &[u8] = b"<!DOCTYPE html><html></html>";
725726
match state.create_chunk(
726727
CreateChunkArg {
727-
batch_id: batch_1,
728+
batch_id: batch_1.clone(),
728729
content: ByteBuf::from(BODY.to_vec()),
729730
},
730731
time_now,
731732
) {
732733
Err(err) if err == *"batch has been proposed" => {}
733734
other => panic!("expected batch already proposed error, got: {:?}", other),
734735
}
736+
match state.create_chunks(
737+
CreateChunksArg {
738+
batch_id: batch_1,
739+
content: vec![ByteBuf::from(BODY.to_vec())],
740+
},
741+
time_now,
742+
) {
743+
Err(err) if err == *"batch has been proposed" => {}
744+
other => panic!("expected batch already proposed error, got: {:?}", other),
745+
}
735746
}
736747

737748
#[test]
@@ -3765,6 +3776,18 @@ mod enforce_limits {
37653776
time_now,
37663777
)
37673778
.unwrap();
3779+
assert_eq!(
3780+
state
3781+
.create_chunks(
3782+
CreateChunksArg {
3783+
batch_id: batch_2.clone(),
3784+
content: vec![ByteBuf::new(), ByteBuf::new()]
3785+
},
3786+
time_now
3787+
)
3788+
.unwrap_err(),
3789+
"chunk limit exceeded"
3790+
);
37683791
state
37693792
.create_chunk(
37703793
CreateChunkArg {
@@ -3818,20 +3841,27 @@ mod enforce_limits {
38183841

38193842
let batch_1 = state.create_batch(time_now).unwrap();
38203843
let batch_2 = state.create_batch(time_now).unwrap();
3844+
assert_eq!(
3845+
state
3846+
.create_chunks(
3847+
CreateChunksArg {
3848+
batch_id: batch_1.clone(),
3849+
content: vec![
3850+
ByteBuf::from(c0.clone()),
3851+
ByteBuf::from(c1.clone()),
3852+
ByteBuf::from(c2.clone())
3853+
]
3854+
},
3855+
time_now
3856+
)
3857+
.unwrap_err(),
3858+
"byte limit exceeded"
3859+
);
38213860
state
3822-
.create_chunk(
3823-
CreateChunkArg {
3861+
.create_chunks(
3862+
CreateChunksArg {
38243863
batch_id: batch_1.clone(),
3825-
content: ByteBuf::from(c0),
3826-
},
3827-
time_now,
3828-
)
3829-
.unwrap();
3830-
state
3831-
.create_chunk(
3832-
CreateChunkArg {
3833-
batch_id: batch_2.clone(),
3834-
content: ByteBuf::from(c1),
3864+
content: vec![ByteBuf::from(c0), ByteBuf::from(c1)],
38353865
},
38363866
time_now,
38373867
)

src/canisters/frontend/ic-certified-assets/src/types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ pub struct CreateChunkResponse {
134134
pub chunk_id: ChunkId,
135135
}
136136

137+
#[derive(Clone, Debug, CandidType, Deserialize)]
138+
pub struct CreateChunksArg {
139+
pub batch_id: BatchId,
140+
pub content: Vec<ByteBuf>,
141+
}
142+
143+
#[derive(Clone, Debug, CandidType, Deserialize)]
144+
pub struct CreateChunksResponse {
145+
pub chunk_ids: Vec<ChunkId>,
146+
}
147+
137148
#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)]
138149
pub struct AssetProperties {
139150
pub max_age: Option<u64>,

src/distributed/assetstorage.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ service: (asset_canister_args: opt AssetCanisterArgs) -> {
199199
create_batch : (record {}) -> (record { batch_id: BatchId });
200200

201201
create_chunk: (record { batch_id: BatchId; content: blob }) -> (record { chunk_id: ChunkId });
202+
create_chunks: (record { batch_id: BatchId; content: vec blob }) -> (record { chunk_ids: vec ChunkId });
202203

203204
// Perform all operations successfully, or reject
204205
commit_batch: (CommitBatchArguments) -> ();

src/distributed/assetstorage.wasm.gz

3.95 KB
Binary file not shown.

0 commit comments

Comments
 (0)