Skip to content

Commit abf4540

Browse files
fix: make asset canister reserve space when saving to stable storage (#4036)
https://dfinity.atlassian.net/browse/SDK-1827
1 parent a6a137d commit abf4540

File tree

6 files changed

+114
-4
lines changed

6 files changed

+114
-4
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ Your principal for ICP wallets and decentralized exchanges: ueuar-wxbnk-bdcsr-dn
6969

7070
## Dependencies
7171

72+
### Frontend canister
73+
74+
### fix: 'unreachable' error when trying to upgrade an asset canister with over 1GB data
75+
76+
The asset canister now estimates the size of the data to be serialized to stable memory,
77+
and reserves that much space for the ValueSerializer's buffer.
78+
79+
- Module hash: bba3181888f3c59b4a5f608aedef05be6fa37276fb7dc394cbadf9cf6e10359b
80+
- https://github.com/dfinity/sdk/pull/4036
81+
7282
### Motoko
7383

7484
Updated Motoko to [0.13.5](https://github.com/dfinity/motoko/releases/tag/0.13.5)

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ rust-version = "1.75.0"
1919
license = "Apache-2.0"
2020

2121
[workspace.dependencies]
22-
candid = "0.10.4"
22+
candid = "0.10.11"
2323
candid_parser = "0.1.4"
2424
dfx-core = { path = "src/dfx-core", version = "0.1.0" }
2525
ic-agent = "0.39"

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ pub struct AssetEncoding {
8282
}
8383

8484
impl AssetEncoding {
85+
fn estimate_size(&self) -> usize {
86+
let mut size = 0;
87+
size += 8; // modified
88+
size += self.total_length + self.content_chunks.len() * 4;
89+
size += 5; // total_length
90+
size += 1; // certified
91+
size += self.sha256.len();
92+
size += 1 + self
93+
.certificate_expression
94+
.as_ref()
95+
.map_or(0, |ce| 2 + ce.expression.len() + ce.expression_hash.len());
96+
size += 1 + self.response_hashes.as_ref().map_or(0, |hashes| {
97+
hashes.iter().fold(2, |acc, (_k, v)| acc + 2 + v.len())
98+
});
99+
size
100+
}
85101
fn asset_hash_path_v2(&self, path: &AssetPath, status_code: u16) -> Option<HashTreePath> {
86102
self.certificate_expression.as_ref().and_then(|ce| {
87103
self.response_hashes.as_ref().and_then(|hashes| {
@@ -206,6 +222,25 @@ pub struct Configuration {
206222
pub max_bytes: Option<u64>,
207223
}
208224

225+
impl Configuration {
226+
fn estimate_size(&self) -> usize {
227+
1 + self
228+
.max_batches
229+
.as_ref()
230+
.map_or(0, |_| std::mem::size_of::<u64>())
231+
+ 1
232+
+ self
233+
.max_chunks
234+
.as_ref()
235+
.map_or(0, |_| std::mem::size_of::<u64>())
236+
+ 1
237+
+ self
238+
.max_bytes
239+
.as_ref()
240+
.map_or(0, |_| std::mem::size_of::<u64>())
241+
}
242+
}
243+
209244
#[derive(Default)]
210245
pub struct State {
211246
assets: HashMap<AssetKey, Asset>,
@@ -232,6 +267,16 @@ pub struct StableStatePermissions {
232267
manage_permissions: BTreeSet<Principal>,
233268
}
234269

270+
impl StableStatePermissions {
271+
fn estimate_size(&self) -> usize {
272+
8 + self.commit.len() * std::mem::size_of::<Principal>()
273+
+ 8
274+
+ self.prepare.len() * std::mem::size_of::<Principal>()
275+
+ 8
276+
+ self.manage_permissions.len() * std::mem::size_of::<Principal>()
277+
}
278+
}
279+
235280
#[derive(Clone, Debug, CandidType, Deserialize)]
236281
pub struct StableState {
237282
authorized: Vec<Principal>, // ignored if permissions is Some(_)
@@ -242,7 +287,46 @@ pub struct StableState {
242287
configuration: Option<Configuration>,
243288
}
244289

290+
impl StableState {
291+
pub fn estimate_size(&self) -> usize {
292+
let mut size = 0;
293+
size += 2 + self.authorized.len() * std::mem::size_of::<Principal>();
294+
size += 1 + self.permissions.as_ref().map_or(0, |p| p.estimate_size());
295+
size += self.stable_assets.iter().fold(2, |acc, (name, asset)| {
296+
acc + 2 + name.len() + asset.estimate_size()
297+
});
298+
size += 1 + self.next_batch_id.as_ref().map_or(0, |_| 8);
299+
size += 1 + self.configuration.as_ref().map_or(0, |c| c.estimate_size());
300+
size
301+
}
302+
}
303+
245304
impl Asset {
305+
fn estimate_size(&self) -> usize {
306+
let mut size = 0;
307+
size += 1 + self.content_type.len();
308+
size += self.encodings.iter().fold(1, |acc, (name, encoding)| {
309+
acc + 1 + name.len() + encoding.estimate_size()
310+
});
311+
size += 1 + self
312+
.max_age
313+
.as_ref()
314+
.map_or(0, |_| std::mem::size_of::<u64>());
315+
size += 1 + self.headers.as_ref().map_or(0, |hm| {
316+
hm.iter()
317+
.fold(2, |acc, (k, v)| acc + 1 + k.len() + 2 + v.len())
318+
});
319+
size += 1 + self
320+
.is_aliased
321+
.as_ref()
322+
.map_or(0, |_| std::mem::size_of::<bool>());
323+
size += 1 + self
324+
.allow_raw_access
325+
.as_ref()
326+
.map_or(0, |_| std::mem::size_of::<bool>());
327+
size
328+
}
329+
246330
fn allow_raw_access(&self) -> bool {
247331
self.allow_raw_access.unwrap_or(true)
248332
}

src/canisters/frontend/ic-frontend-canister/src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use candid::ser::IDLBuilder;
2+
use ic_cdk::api::stable;
13
use ic_cdk::{init, post_upgrade, pre_upgrade};
24
use ic_certified_assets::types::AssetCanisterArgs;
35

@@ -8,10 +10,24 @@ fn init(args: Option<AssetCanisterArgs>) {
810

911
#[pre_upgrade]
1012
fn pre_upgrade() {
11-
ic_cdk::storage::stable_save((ic_certified_assets::pre_upgrade(),))
13+
let stable_state = ic_certified_assets::pre_upgrade();
14+
let value_serializer_estimate = stable_state.estimate_size();
15+
stable_save_with_capacity((stable_state,), value_serializer_estimate)
1216
.expect("failed to save stable state");
1317
}
1418

19+
// this is the same as ic_cdk::storage::stable_save,
20+
// but reserves the capacity for the value serializer
21+
fn stable_save_with_capacity<T>(t: T, value_capacity: usize) -> Result<(), candid::Error>
22+
where
23+
T: candid::utils::ArgumentEncoder,
24+
{
25+
let mut ser = IDLBuilder::new();
26+
ser.try_reserve_value_serializer_capacity(value_capacity)?;
27+
t.encode(&mut ser)?;
28+
ser.serialize(stable::StableWriter::default())
29+
}
30+
1531
#[post_upgrade]
1632
fn post_upgrade(args: Option<AssetCanisterArgs>) {
1733
let (stable_state,): (ic_certified_assets::StableState,) =
-696 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)