freenet_stdlib/
versioning.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3use std::io::{Cursor, Read};
4use std::path::Path;
5use std::sync::Arc;
6
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::{
12    client_api::{TryFromFbs, WsApiError},
13    common_generated::common::{ContractContainer as FbsContractContainer, ContractType},
14    contract_interface::{ContractInstanceId, ContractKey},
15    generated::client_request::{DelegateContainer as FbsDelegateContainer, DelegateType},
16    parameters::Parameters,
17    prelude::{
18        CodeHash, ContractCode, ContractWasmAPIVersion::V1, Delegate, DelegateCode, DelegateKey,
19        WrappedContract,
20    },
21};
22
23/// Contains the different versions available for WASM delegates.
24#[non_exhaustive]
25#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
26pub enum DelegateWasmAPIVersion {
27    V1(#[serde(deserialize_with = "Delegate::deserialize_delegate")] Delegate<'static>),
28}
29
30impl DelegateWasmAPIVersion {}
31
32#[non_exhaustive]
33#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
34pub enum DelegateContainer {
35    Wasm(DelegateWasmAPIVersion),
36}
37
38impl From<DelegateContainer> for APIVersion {
39    fn from(delegate: DelegateContainer) -> APIVersion {
40        match delegate {
41            DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
42        }
43    }
44}
45
46impl DelegateContainer {
47    pub fn key(&self) -> &DelegateKey {
48        match self {
49            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.key(),
50        }
51    }
52
53    pub fn code(&self) -> &DelegateCode {
54        match self {
55            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code(),
56        }
57    }
58
59    pub fn code_hash(&self) -> &CodeHash {
60        match self {
61            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code_hash(),
62        }
63    }
64}
65
66impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for DelegateContainer {
67    type Error = std::io::Error;
68
69    fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
70        let (contract_code, version) = DelegateCode::load_versioned_from_path(path)?;
71
72        match version {
73            APIVersion::Version0_0_1 => {
74                let delegate = Delegate::from((&contract_code, &params));
75                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
76                    delegate,
77                )))
78            }
79        }
80    }
81}
82
83impl<'a, P> TryFrom<(Vec<u8>, P)> for DelegateContainer
84where
85    P: std::ops::Deref<Target = Parameters<'a>>,
86{
87    type Error = std::io::Error;
88
89    fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
90        let params = params.deref().clone().into_owned();
91
92        let (contract_code, version) =
93            DelegateCode::load_versioned_from_bytes(versioned_contract_bytes)?;
94
95        match version {
96            APIVersion::Version0_0_1 => {
97                let delegate = Delegate::from((&contract_code, &params));
98                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
99                    delegate,
100                )))
101            }
102        }
103    }
104}
105
106impl<'a> TryFromFbs<&FbsDelegateContainer<'a>> for DelegateContainer {
107    fn try_decode_fbs(container: &FbsDelegateContainer<'a>) -> Result<Self, WsApiError> {
108        match container.delegate_type() {
109            DelegateType::WasmDelegateV1 => {
110                let delegate = container.delegate_as_wasm_delegate_v1().unwrap();
111                let data = DelegateCode::from(delegate.data().data().bytes().to_vec());
112                let params = Parameters::from(delegate.parameters().bytes().to_vec());
113                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
114                    Delegate::from((&data, &params)),
115                )))
116            }
117            _ => unreachable!(),
118        }
119    }
120}
121
122impl DelegateCode<'static> {
123    fn load_versioned(
124        mut contract_data: Cursor<Vec<u8>>,
125    ) -> Result<(Self, APIVersion), std::io::Error> {
126        // Get contract version
127        let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
128            std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
129        })?;
130        let version = APIVersion::from_u64(version).map_err(|e| {
131            std::io::Error::new(
132                std::io::ErrorKind::InvalidData,
133                format!("Version error: {}", e),
134            )
135        })?;
136
137        if version == APIVersion::Version0_0_1 {
138            let mut code_hash = [0u8; 32];
139            contract_data.read_exact(&mut code_hash)?;
140        }
141
142        // Get contract code
143        let mut code_data: Vec<u8> = vec![];
144        contract_data
145            .read_to_end(&mut code_data)
146            .map_err(|_| std::io::ErrorKind::InvalidData)?;
147        Ok((DelegateCode::from(code_data), version))
148    }
149
150    /// Loads contract code which has been versioned from the fs.
151    pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
152        let contract_data = Cursor::new(Self::load_bytes(path)?);
153        Self::load_versioned(contract_data)
154    }
155
156    /// Loads contract code which has been versioned from the fs.
157    pub fn load_versioned_from_bytes(
158        versioned_code: Vec<u8>,
159    ) -> Result<(Self, APIVersion), std::io::Error> {
160        let contract_data = Cursor::new(versioned_code);
161        Self::load_versioned(contract_data)
162    }
163}
164
165impl DelegateCode<'_> {
166    pub fn to_bytes_versioned(
167        &self,
168        version: APIVersion,
169    ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
170        match version {
171            APIVersion::Version0_0_1 => {
172                let output_size =
173                    std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
174                let mut output: Vec<u8> = Vec::with_capacity(output_size);
175                output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
176                output.extend(self.hash().0.iter());
177                output.extend(self.data());
178                Ok(output)
179            }
180        }
181    }
182}
183
184/// Contains the different versions available for WASM contracts.
185#[non_exhaustive]
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
187
188pub enum ContractWasmAPIVersion {
189    V1(WrappedContract),
190}
191
192impl From<ContractWasmAPIVersion> for ContractContainer {
193    fn from(value: ContractWasmAPIVersion) -> Self {
194        ContractContainer::Wasm(value)
195    }
196}
197
198impl Display for ContractWasmAPIVersion {
199    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
200        match self {
201            ContractWasmAPIVersion::V1(contract_v1) => {
202                write!(f, "[api=0.0.1]({contract_v1})")
203            }
204        }
205    }
206}
207
208/// Wrapper that allows contract versioning. This enum maintains the types of contracts that are
209/// allowed and their corresponding version.
210#[non_exhaustive]
211#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
212pub enum ContractContainer {
213    Wasm(ContractWasmAPIVersion),
214}
215
216impl From<ContractContainer> for APIVersion {
217    fn from(contract: ContractContainer) -> APIVersion {
218        match contract {
219            ContractContainer::Wasm(ContractWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
220        }
221    }
222}
223
224impl ContractContainer {
225    /// Return the `ContractKey` from the specific contract version.
226    pub fn key(&self) -> ContractKey {
227        match self {
228            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => *contract_v1.key(),
229        }
230    }
231
232    /// Return the `ContractInstanceId` from the specific contract version.
233    pub fn id(&self) -> &ContractInstanceId {
234        match self {
235            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.key().id(),
236        }
237    }
238
239    /// Return the `Parameters` from the specific contract version.
240    pub fn params(&self) -> Parameters<'static> {
241        match self {
242            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.params().clone(),
243        }
244    }
245
246    /// Return the contract code from the specific contract version as `Vec<u8>`.
247    pub fn data(&self) -> &[u8] {
248        match self {
249            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.data.data(),
250        }
251    }
252
253    pub fn unwrap_v1(self) -> WrappedContract {
254        match self {
255            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1,
256        }
257    }
258}
259
260impl Display for ContractContainer {
261    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
262        match self {
263            ContractContainer::Wasm(wasm_version) => {
264                write!(f, "WasmContainer({wasm_version})")
265            }
266        }
267    }
268}
269
270impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for ContractContainer {
271    type Error = std::io::Error;
272
273    fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
274        let (contract_code, version) = ContractCode::load_versioned_from_path(path)?;
275
276        match version {
277            APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
278                WrappedContract::new(Arc::new(contract_code), params),
279            ))),
280        }
281    }
282}
283
284impl<'a, P> TryFrom<(Vec<u8>, P)> for ContractContainer
285where
286    P: std::ops::Deref<Target = Parameters<'a>>,
287{
288    type Error = std::io::Error;
289
290    fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
291        let params = params.deref().clone().into_owned();
292
293        let (contract_code, version) =
294            ContractCode::load_versioned_from_bytes(versioned_contract_bytes)?;
295
296        match version {
297            APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
298                WrappedContract::new(Arc::new(contract_code), params),
299            ))),
300        }
301    }
302}
303
304impl ContractCode<'static> {
305    fn load_versioned(
306        mut contract_data: Cursor<Vec<u8>>,
307    ) -> Result<(Self, APIVersion), std::io::Error> {
308        // Get contract version
309        let version = contract_data.read_u64::<BigEndian>().map_err(|_| {
310            std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to read version")
311        })?;
312        let version = APIVersion::from_u64(version).map_err(|e| {
313            std::io::Error::new(
314                std::io::ErrorKind::InvalidData,
315                format!("Version error: {}", e),
316            )
317        })?;
318
319        if version == APIVersion::Version0_0_1 {
320            let mut code_hash = [0u8; 32];
321            contract_data.read_exact(&mut code_hash)?;
322        }
323
324        // Get contract code
325        let mut code_data: Vec<u8> = vec![];
326        contract_data
327            .read_to_end(&mut code_data)
328            .map_err(|_| std::io::ErrorKind::InvalidData)?;
329        Ok((ContractCode::from(code_data), version))
330    }
331
332    /// Loads contract code which has been versioned from the fs.
333    pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
334        let contract_data = Cursor::new(Self::load_bytes(path)?);
335        Self::load_versioned(contract_data)
336    }
337
338    /// Loads contract code which has been versioned from the fs.
339    pub fn load_versioned_from_bytes(
340        versioned_code: Vec<u8>,
341    ) -> Result<(Self, APIVersion), std::io::Error> {
342        let contract_data = Cursor::new(versioned_code);
343        Self::load_versioned(contract_data)
344    }
345}
346
347#[derive(Debug, Error)]
348pub enum VersionError {
349    #[error("unsupported incremental API version: {0}")]
350    UnsupportedVersion(u64),
351    #[error("failed to read version: {0}")]
352    IoError(#[from] std::io::Error),
353}
354
355#[derive(PartialEq, Eq, Clone, Copy)]
356pub enum APIVersion {
357    Version0_0_1,
358}
359
360impl APIVersion {
361    fn from_u64(version: u64) -> Result<Self, VersionError> {
362        match version {
363            0 => Ok(Self::Version0_0_1),
364            v => Err(VersionError::UnsupportedVersion(v)),
365        }
366    }
367
368    fn into_u64(self) -> u64 {
369        match self {
370            Self::Version0_0_1 => 0,
371        }
372    }
373}
374
375impl Display for APIVersion {
376    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
377        match self {
378            APIVersion::Version0_0_1 => write!(f, "0.0.1"),
379        }
380    }
381}
382
383impl<'a> TryFrom<&'a semver::Version> for APIVersion {
384    type Error = Box<dyn std::error::Error + Send + Sync>;
385    fn try_from(value: &'a semver::Version) -> Result<Self, Self::Error> {
386        match value {
387            ver if ver == &semver::Version::new(0, 0, 1) => Ok(APIVersion::Version0_0_1),
388            other => Err(format!("{other} version not supported").into()),
389        }
390    }
391}
392
393impl ContractCode<'_> {
394    pub fn to_bytes_versioned(
395        &self,
396        version: APIVersion,
397    ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
398        match version {
399            APIVersion::Version0_0_1 => {
400                let output_size =
401                    std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
402                let mut output: Vec<u8> = Vec::with_capacity(output_size);
403                output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
404                output.extend(self.hash().0.iter());
405                output.extend(self.data());
406                Ok(output)
407            }
408        }
409    }
410}
411
412impl<'a> TryFromFbs<&FbsContractContainer<'a>> for ContractContainer {
413    fn try_decode_fbs(value: &FbsContractContainer<'a>) -> Result<Self, WsApiError> {
414        match value.contract_type() {
415            ContractType::WasmContractV1 => {
416                let contract = value.contract_as_wasm_contract_v1().unwrap();
417                let data = Arc::new(ContractCode::from(contract.data().data().bytes().to_vec()));
418                let params = Parameters::from(contract.parameters().bytes().to_vec());
419                let key = ContractKey::from_params_and_code(&params, &*data);
420                Ok(ContractContainer::from(V1(WrappedContract {
421                    data,
422                    params,
423                    key,
424                })))
425            }
426            _ => unreachable!(),
427        }
428    }
429}