freenet_stdlib/contract_interface/
update.rs

1//! Contract update mechanisms and related contract management.
2//!
3//! This module provides types for updating contract state, managing related contracts,
4//! and validation results.
5
6use std::collections::HashMap;
7
8use serde::{Deserialize, Serialize};
9
10use crate::client_api::{TryFromFbs, WsApiError};
11use crate::common_generated::common::{UpdateData as FbsUpdateData, UpdateDataType};
12use crate::generated::client_request::RelatedContracts as FbsRelatedContracts;
13
14use super::{ContractError, ContractInstanceId, State, StateDelta, CONTRACT_KEY_SIZE};
15
16/// An update to a contract state or any required related contracts to update that state.
17// todo: this should be an enum probably
18#[non_exhaustive]
19#[derive(Debug, Serialize, Deserialize)]
20pub struct UpdateModification<'a> {
21    #[serde(borrow)]
22    pub new_state: Option<State<'a>>,
23    /// Request an other contract so updates can be resolved.
24    pub related: Vec<RelatedContract>,
25}
26
27impl<'a> UpdateModification<'a> {
28    /// Constructor for self when the state is valid.
29    pub fn valid(new_state: State<'a>) -> Self {
30        Self {
31            new_state: Some(new_state),
32            related: vec![],
33        }
34    }
35
36    /// Unwraps self returning a [`State`].
37    ///
38    /// Panics if self does not contain a state.
39    pub fn unwrap_valid(self) -> State<'a> {
40        match self.new_state {
41            Some(s) => s,
42            _ => panic!("failed unwrapping state in modification"),
43        }
44    }
45}
46
47impl UpdateModification<'_> {
48    /// Constructor for self when this contract still is missing some [`RelatedContract`]
49    /// to proceed with any verification or updates.
50    pub fn requires(related: Vec<RelatedContract>) -> Result<Self, ContractError> {
51        if related.is_empty() {
52            return Err(ContractError::InvalidUpdateWithInfo {
53                reason: "At least one related contract is required".into(),
54            });
55        }
56        Ok(Self {
57            new_state: None,
58            related,
59        })
60    }
61
62    /// Gets the pending related contracts.
63    pub fn get_related(&self) -> &[RelatedContract] {
64        &self.related
65    }
66
67    /// Copies the data if not owned and returns an owned version of self.
68    pub fn into_owned(self) -> UpdateModification<'static> {
69        let Self { new_state, related } = self;
70        UpdateModification {
71            new_state: new_state.map(State::into_owned),
72            related,
73        }
74    }
75
76    pub fn requires_dependencies(&self) -> bool {
77        !self.related.is_empty()
78    }
79}
80
81/// The contracts related to a parent or root contract. Tipically this means
82/// contracts which state requires to be verified or integrated in some way with
83/// the parent contract.
84#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
85pub struct RelatedContracts<'a> {
86    #[serde(borrow)]
87    map: HashMap<ContractInstanceId, Option<State<'a>>>,
88}
89
90impl RelatedContracts<'_> {
91    pub fn new() -> Self {
92        Self {
93            map: HashMap::new(),
94        }
95    }
96
97    /// Copies the data if not owned and returns an owned version of self.
98    pub fn into_owned(self) -> RelatedContracts<'static> {
99        let mut map = HashMap::with_capacity(self.map.len());
100        for (k, v) in self.map {
101            map.insert(k, v.map(|s| s.into_owned()));
102        }
103        RelatedContracts { map }
104    }
105
106    pub fn deser_related_contracts<'de, D>(deser: D) -> Result<RelatedContracts<'static>, D::Error>
107    where
108        D: serde::Deserializer<'de>,
109    {
110        let value = <RelatedContracts as Deserialize>::deserialize(deser)?;
111        Ok(value.into_owned())
112    }
113}
114
115impl RelatedContracts<'static> {
116    pub fn states(&self) -> impl Iterator<Item = (&ContractInstanceId, &Option<State<'static>>)> {
117        self.map.iter()
118    }
119}
120
121impl<'a> RelatedContracts<'a> {
122    pub fn update(
123        &mut self,
124    ) -> impl Iterator<Item = (&ContractInstanceId, &mut Option<State<'a>>)> + '_ {
125        self.map.iter_mut()
126    }
127
128    pub fn missing(&mut self, contracts: Vec<ContractInstanceId>) {
129        for key in contracts {
130            self.map.entry(key).or_default();
131        }
132    }
133}
134
135impl<'a> TryFromFbs<&FbsRelatedContracts<'a>> for RelatedContracts<'a> {
136    fn try_decode_fbs(related_contracts: &FbsRelatedContracts<'a>) -> Result<Self, WsApiError> {
137        let mut map = HashMap::with_capacity(related_contracts.contracts().len());
138        for related in related_contracts.contracts().iter() {
139            let id = ContractInstanceId::from_bytes(related.instance_id().data().bytes()).unwrap();
140            let state = State::from(related.state().bytes());
141            map.insert(id, Some(state));
142        }
143        Ok(RelatedContracts::from(map))
144    }
145}
146
147impl<'a> From<HashMap<ContractInstanceId, Option<State<'a>>>> for RelatedContracts<'a> {
148    fn from(related_contracts: HashMap<ContractInstanceId, Option<State<'a>>>) -> Self {
149        Self {
150            map: related_contracts,
151        }
152    }
153}
154
155/// A contract related to an other contract and the specification
156/// of the kind of update notifications that should be received by this contract.
157#[derive(Debug, Serialize, Deserialize)]
158pub struct RelatedContract {
159    pub contract_instance_id: ContractInstanceId,
160    pub mode: RelatedMode,
161    // todo: add a timeout so we stop listening/subscribing eventually
162}
163
164/// Specification of the notifications of interest from a related contract.
165#[derive(Debug, Serialize, Deserialize)]
166pub enum RelatedMode {
167    /// Retrieve the state once, don't be concerned with subsequent changes.
168    StateOnce,
169    /// Retrieve the state once, and then subscribe to updates.
170    StateThenSubscribe,
171}
172
173/// The result of calling the [`ContractInterface::validate_state`] function.
174#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
175pub enum ValidateResult {
176    Valid,
177    Invalid,
178    /// The peer will attempt to retrieve the requested contract states
179    /// and will call validate_state() again when it retrieves them.
180    RequestRelated(Vec<ContractInstanceId>),
181}
182
183/// Update notifications for a contract or a related contract.
184#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
185pub enum UpdateData<'a> {
186    State(#[serde(borrow)] State<'a>),
187    Delta(#[serde(borrow)] StateDelta<'a>),
188    StateAndDelta {
189        #[serde(borrow)]
190        state: State<'a>,
191        #[serde(borrow)]
192        delta: StateDelta<'a>,
193    },
194    RelatedState {
195        related_to: ContractInstanceId,
196        #[serde(borrow)]
197        state: State<'a>,
198    },
199    RelatedDelta {
200        related_to: ContractInstanceId,
201        #[serde(borrow)]
202        delta: StateDelta<'a>,
203    },
204    RelatedStateAndDelta {
205        related_to: ContractInstanceId,
206        #[serde(borrow)]
207        state: State<'a>,
208        #[serde(borrow)]
209        delta: StateDelta<'a>,
210    },
211}
212
213impl UpdateData<'_> {
214    pub fn size(&self) -> usize {
215        match self {
216            UpdateData::State(state) => state.size(),
217            UpdateData::Delta(delta) => delta.size(),
218            UpdateData::StateAndDelta { state, delta } => state.size() + delta.size(),
219            UpdateData::RelatedState { state, .. } => state.size() + CONTRACT_KEY_SIZE,
220            UpdateData::RelatedDelta { delta, .. } => delta.size() + CONTRACT_KEY_SIZE,
221            UpdateData::RelatedStateAndDelta { state, delta, .. } => {
222                state.size() + delta.size() + CONTRACT_KEY_SIZE
223            }
224        }
225    }
226
227    pub fn unwrap_delta(&self) -> &StateDelta<'_> {
228        match self {
229            UpdateData::Delta(delta) => delta,
230            _ => panic!(),
231        }
232    }
233
234    /// Copies the data if not owned and returns an owned version of self.
235    pub fn into_owned(self) -> UpdateData<'static> {
236        match self {
237            UpdateData::State(s) => UpdateData::State(State::from(s.into_bytes())),
238            UpdateData::Delta(d) => UpdateData::Delta(StateDelta::from(d.into_bytes())),
239            UpdateData::StateAndDelta { state, delta } => UpdateData::StateAndDelta {
240                delta: StateDelta::from(delta.into_bytes()),
241                state: State::from(state.into_bytes()),
242            },
243            UpdateData::RelatedState { related_to, state } => UpdateData::RelatedState {
244                related_to,
245                state: State::from(state.into_bytes()),
246            },
247            UpdateData::RelatedDelta { related_to, delta } => UpdateData::RelatedDelta {
248                related_to,
249                delta: StateDelta::from(delta.into_bytes()),
250            },
251            UpdateData::RelatedStateAndDelta {
252                related_to,
253                state,
254                delta,
255            } => UpdateData::RelatedStateAndDelta {
256                related_to,
257                state: State::from(state.into_bytes()),
258                delta: StateDelta::from(delta.into_bytes()),
259            },
260        }
261    }
262
263    pub(crate) fn get_self_states<'a>(
264        updates: &[UpdateData<'a>],
265    ) -> Vec<(Option<State<'a>>, Option<StateDelta<'a>>)> {
266        let mut own_states = Vec::with_capacity(updates.len());
267        for update in updates {
268            match update {
269                UpdateData::State(state) => own_states.push((Some(state.clone()), None)),
270                UpdateData::Delta(delta) => own_states.push((None, Some(delta.clone()))),
271                UpdateData::StateAndDelta { state, delta } => {
272                    own_states.push((Some(state.clone()), Some(delta.clone())))
273                }
274                _ => {}
275            }
276        }
277        own_states
278    }
279
280    pub(crate) fn deser_update_data<'de, D>(deser: D) -> Result<UpdateData<'static>, D::Error>
281    where
282        D: serde::Deserializer<'de>,
283    {
284        let value = <UpdateData as Deserialize>::deserialize(deser)?;
285        Ok(value.into_owned())
286    }
287}
288
289impl<'a> From<StateDelta<'a>> for UpdateData<'a> {
290    fn from(delta: StateDelta<'a>) -> Self {
291        UpdateData::Delta(delta)
292    }
293}
294
295impl<'a> TryFromFbs<&FbsUpdateData<'a>> for UpdateData<'a> {
296    fn try_decode_fbs(update_data: &FbsUpdateData<'a>) -> Result<Self, WsApiError> {
297        match update_data.update_data_type() {
298            UpdateDataType::StateUpdate => {
299                let update = update_data.update_data_as_state_update().unwrap();
300                let state = State::from(update.state().bytes());
301                Ok(UpdateData::State(state))
302            }
303            UpdateDataType::DeltaUpdate => {
304                let update = update_data.update_data_as_delta_update().unwrap();
305                let delta = StateDelta::from(update.delta().bytes());
306                Ok(UpdateData::Delta(delta))
307            }
308            UpdateDataType::StateAndDeltaUpdate => {
309                let update = update_data.update_data_as_state_and_delta_update().unwrap();
310                let state = State::from(update.state().bytes());
311                let delta = StateDelta::from(update.delta().bytes());
312                Ok(UpdateData::StateAndDelta { state, delta })
313            }
314            UpdateDataType::RelatedStateUpdate => {
315                let update = update_data.update_data_as_related_state_update().unwrap();
316                let state = State::from(update.state().bytes());
317                let related_to =
318                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
319                Ok(UpdateData::RelatedState { related_to, state })
320            }
321            UpdateDataType::RelatedDeltaUpdate => {
322                let update = update_data.update_data_as_related_delta_update().unwrap();
323                let delta = StateDelta::from(update.delta().bytes());
324                let related_to =
325                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
326                Ok(UpdateData::RelatedDelta { related_to, delta })
327            }
328            UpdateDataType::RelatedStateAndDeltaUpdate => {
329                let update = update_data
330                    .update_data_as_related_state_and_delta_update()
331                    .unwrap();
332                let state = State::from(update.state().bytes());
333                let delta = StateDelta::from(update.delta().bytes());
334                let related_to =
335                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
336                Ok(UpdateData::RelatedStateAndDelta {
337                    related_to,
338                    state,
339                    delta,
340                })
341            }
342            _ => unreachable!(),
343        }
344    }
345}