From 82dda155d22c9fafe032ac2cf66cffb6a76e97c1 Mon Sep 17 00:00:00 2001 From: Adam Spofford <93943719+adamspofford-dfinity@users.noreply.github.com> Date: Thu, 8 Feb 2024 09:54:09 -0800 Subject: [PATCH] fix: Change stored_chunks return type (#512) --- CHANGELOG.md | 1 + Cargo.lock | 8 +- Cargo.toml | 2 +- .../src/interfaces/management_canister.rs | 13 +- .../management_canister/builders.rs | 2 +- .../management_canister/serde_impls.rs | 125 ++++++++++++++++++ 6 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 ic-utils/src/interfaces/management_canister/serde_impls.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c7d33bc..1c852290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +* Changed the return type of `stored_chunks` to a struct. * Added a prime256v1-based `Identity` impl to complement the ed25519 and secp256k1 `Identity` impls. ## [0.32.0] - 2024-01-18 diff --git a/Cargo.lock b/Cargo.lock index a5f1e0f5..3ef46e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d08367fb008cba20d2fe6e5c774aba244e087ac02d3887b72b039106b859fa0" +checksum = "182543fbc03b4ad0bfc384e6b68346e0b0aad0b11d075b71b4fcaa5d07f8862c" dependencies = [ "anyhow", "binread", @@ -2102,9 +2102,9 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 1ca30f1c..83d9e01b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ hex = "0.4.3" leb128 = "0.2.5" ring = "0.16.20" serde = "1.0.162" -serde_bytes = "0.11.9" +serde_bytes = "0.11.13" serde_cbor = "0.11.2" serde_json = "1.0.96" serde_repr = "0.1.12" diff --git a/ic-utils/src/interfaces/management_canister.rs b/ic-utils/src/interfaces/management_canister.rs index 0034c5ad..a4d529cb 100644 --- a/ic-utils/src/interfaces/management_canister.rs +++ b/ic-utils/src/interfaces/management_canister.rs @@ -5,11 +5,12 @@ use crate::{call::AsyncCall, Canister}; use candid::{CandidType, Deserialize, Nat}; use ic_agent::{export::Principal, Agent}; -use std::{convert::AsRef, fmt::Debug, ops::Deref}; +use std::{convert::AsRef, ops::Deref}; use strum_macros::{AsRefStr, EnumString}; pub mod attributes; pub mod builders; +mod serde_impls; #[doc(inline)] pub use builders::{ CreateCanisterBuilder, InstallBuilder, InstallChunkedCodeBuilder, InstallCodeBuilder, @@ -139,6 +140,14 @@ pub struct DefiniteCanisterSettings { #[derive(Clone, Debug, Deserialize, CandidType)] pub struct UploadChunkResult { /// The hash of the uploaded chunk. + #[serde(with = "serde_bytes")] + pub hash: ChunkHash, +} + +/// The result of a [`ManagementCanister::stored_chunks`] call. +#[derive(Clone, Debug)] +pub struct ChunkInfo { + /// The hash of the stored chunk. pub hash: ChunkHash, } @@ -367,7 +376,7 @@ impl<'agent> ManagementCanister<'agent> { pub fn stored_chunks( &self, canister_id: &Principal, - ) -> impl 'agent + AsyncCall<(Vec,)> { + ) -> impl 'agent + AsyncCall<(Vec,)> { #[derive(CandidType)] struct Argument<'a> { canister_id: &'a Principal, diff --git a/ic-utils/src/interfaces/management_canister/builders.rs b/ic-utils/src/interfaces/management_canister/builders.rs index 3ff736c6..7249cea6 100644 --- a/ic-utils/src/interfaces/management_canister/builders.rs +++ b/ic-utils/src/interfaces/management_canister/builders.rs @@ -741,7 +741,7 @@ impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'c ) } else { let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?; - let existing_chunks = existing_chunks.into_iter().collect::>(); + let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::>(); let to_upload_chunks_ordered = self.wasm.chunks(1024 * 1024).map(|x| (<[u8; 32]>::from(Sha256::digest(x)), x)).collect::>(); let to_upload_chunks = to_upload_chunks_ordered.iter().map(|&(k, v)| (k, v)).collect::>(); let (new_chunks, setup) = if existing_chunks.iter().all(|hash| to_upload_chunks.contains_key(hash)) { diff --git a/ic-utils/src/interfaces/management_canister/serde_impls.rs b/ic-utils/src/interfaces/management_canister/serde_impls.rs new file mode 100644 index 00000000..ee4c1d7b --- /dev/null +++ b/ic-utils/src/interfaces/management_canister/serde_impls.rs @@ -0,0 +1,125 @@ +use std::fmt::Formatter; + +use super::ChunkInfo; +use candid::types::{CandidType, Type, TypeInner}; +use serde::de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, SeqAccess, Visitor}; +use serde_bytes::ByteArray; +// ChunkInfo can be deserialized from both `blob` and `record { hash: blob }`. +// This impl can be removed when both mainnet and dfx no longer return `blob`. +impl CandidType for ChunkInfo { + fn _ty() -> Type { + Type(<_>::from(TypeInner::Unknown)) + } + fn idl_serialize(&self, _serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + unimplemented!() + } +} +impl<'de> Deserialize<'de> for ChunkInfo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(ChunkInfoVisitor) + } +} +struct ChunkInfoVisitor; +impl<'de> Visitor<'de> for ChunkInfoVisitor { + type Value = ChunkInfo; + fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str("blob or record {hash: blob}") + } + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + // deserialize_any combined with visit_bytes produces an extra 6 byte for difficult reasons + let v = if v.len() == 33 && v[0] == 6 { + &v[1..] + } else { + v + }; + Ok(ChunkInfo { + hash: v + .try_into() + .map_err(|_| E::invalid_length(v.len(), &"32 bytes"))?, + }) + } + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut hash = [0; 32]; + for (i, n) in hash.iter_mut().enumerate() { + *n = seq + .next_element()? + .ok_or_else(|| A::Error::invalid_length(i, &"32 bytes"))?; + } + if seq.next_element::()?.is_some() { + Err(A::Error::invalid_length( + seq.size_hint().unwrap_or(33), + &"32 bytes", + )) + } else { + Ok(ChunkInfo { hash }) + } + } + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + while let Some(k) = map.next_key::()? { + eprintln!("here"); + if matches!(k, Field::Hash) { + return Ok(ChunkInfo { + hash: map.next_value::>()?.into_array(), + }); + } else { + map.next_value::()?; + } + } + Err(A::Error::missing_field("hash")) + } +} +// Needed because candid cannot infer field names without specifying them in _ty() +enum Field { + Hash, + Other, +} +impl<'de> Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_identifier(FieldVisitor) + } +} +struct FieldVisitor; +impl<'de> Visitor<'de> for FieldVisitor { + type Value = Field; + fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a field name") + } + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + if v == "hash" { + Ok(Field::Hash) + } else { + Ok(Field::Other) + } + } + fn visit_u32(self, v: u32) -> Result + where + E: Error, + { + if v == 1158164430 { + Ok(Field::Hash) + } else { + Ok(Field::Other) + } + } +}