Skip to main content

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///
185/// This sits on the contract-update wire boundary. Marked `#[non_exhaustive]`
186/// so future variants (for example, new `Related*` shapes) can be added
187/// without a source-level break; downstream `match` sites must include a
188/// wildcard arm. Wire format is pinned by `update_data_wire_format_is_stable`.
189#[non_exhaustive]
190#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
191pub enum UpdateData<'a> {
192    State(#[serde(borrow)] State<'a>),
193    Delta(#[serde(borrow)] StateDelta<'a>),
194    StateAndDelta {
195        #[serde(borrow)]
196        state: State<'a>,
197        #[serde(borrow)]
198        delta: StateDelta<'a>,
199    },
200    RelatedState {
201        related_to: ContractInstanceId,
202        #[serde(borrow)]
203        state: State<'a>,
204    },
205    RelatedDelta {
206        related_to: ContractInstanceId,
207        #[serde(borrow)]
208        delta: StateDelta<'a>,
209    },
210    RelatedStateAndDelta {
211        related_to: ContractInstanceId,
212        #[serde(borrow)]
213        state: State<'a>,
214        #[serde(borrow)]
215        delta: StateDelta<'a>,
216    },
217}
218
219impl UpdateData<'_> {
220    pub fn size(&self) -> usize {
221        match self {
222            UpdateData::State(state) => state.size(),
223            UpdateData::Delta(delta) => delta.size(),
224            UpdateData::StateAndDelta { state, delta } => state.size() + delta.size(),
225            UpdateData::RelatedState { state, .. } => state.size() + CONTRACT_KEY_SIZE,
226            UpdateData::RelatedDelta { delta, .. } => delta.size() + CONTRACT_KEY_SIZE,
227            UpdateData::RelatedStateAndDelta { state, delta, .. } => {
228                state.size() + delta.size() + CONTRACT_KEY_SIZE
229            }
230        }
231    }
232
233    pub fn unwrap_delta(&self) -> &StateDelta<'_> {
234        match self {
235            UpdateData::Delta(delta) => delta,
236            _ => panic!(),
237        }
238    }
239
240    /// Copies the data if not owned and returns an owned version of self.
241    pub fn into_owned(self) -> UpdateData<'static> {
242        match self {
243            UpdateData::State(s) => UpdateData::State(State::from(s.into_bytes())),
244            UpdateData::Delta(d) => UpdateData::Delta(StateDelta::from(d.into_bytes())),
245            UpdateData::StateAndDelta { state, delta } => UpdateData::StateAndDelta {
246                delta: StateDelta::from(delta.into_bytes()),
247                state: State::from(state.into_bytes()),
248            },
249            UpdateData::RelatedState { related_to, state } => UpdateData::RelatedState {
250                related_to,
251                state: State::from(state.into_bytes()),
252            },
253            UpdateData::RelatedDelta { related_to, delta } => UpdateData::RelatedDelta {
254                related_to,
255                delta: StateDelta::from(delta.into_bytes()),
256            },
257            UpdateData::RelatedStateAndDelta {
258                related_to,
259                state,
260                delta,
261            } => UpdateData::RelatedStateAndDelta {
262                related_to,
263                state: State::from(state.into_bytes()),
264                delta: StateDelta::from(delta.into_bytes()),
265            },
266        }
267    }
268
269    pub(crate) fn get_self_states<'a>(
270        updates: &[UpdateData<'a>],
271    ) -> Vec<(Option<State<'a>>, Option<StateDelta<'a>>)> {
272        let mut own_states = Vec::with_capacity(updates.len());
273        for update in updates {
274            match update {
275                UpdateData::State(state) => own_states.push((Some(state.clone()), None)),
276                UpdateData::Delta(delta) => own_states.push((None, Some(delta.clone()))),
277                UpdateData::StateAndDelta { state, delta } => {
278                    own_states.push((Some(state.clone()), Some(delta.clone())))
279                }
280                _ => {}
281            }
282        }
283        own_states
284    }
285
286    pub(crate) fn deser_update_data<'de, D>(deser: D) -> Result<UpdateData<'static>, D::Error>
287    where
288        D: serde::Deserializer<'de>,
289    {
290        let value = <UpdateData as Deserialize>::deserialize(deser)?;
291        Ok(value.into_owned())
292    }
293}
294
295impl<'a> From<StateDelta<'a>> for UpdateData<'a> {
296    fn from(delta: StateDelta<'a>) -> Self {
297        UpdateData::Delta(delta)
298    }
299}
300
301impl<'a> TryFromFbs<&FbsUpdateData<'a>> for UpdateData<'a> {
302    fn try_decode_fbs(update_data: &FbsUpdateData<'a>) -> Result<Self, WsApiError> {
303        match update_data.update_data_type() {
304            UpdateDataType::StateUpdate => {
305                let update = update_data.update_data_as_state_update().unwrap();
306                let state = State::from(update.state().bytes());
307                Ok(UpdateData::State(state))
308            }
309            UpdateDataType::DeltaUpdate => {
310                let update = update_data.update_data_as_delta_update().unwrap();
311                let delta = StateDelta::from(update.delta().bytes());
312                Ok(UpdateData::Delta(delta))
313            }
314            UpdateDataType::StateAndDeltaUpdate => {
315                let update = update_data.update_data_as_state_and_delta_update().unwrap();
316                let state = State::from(update.state().bytes());
317                let delta = StateDelta::from(update.delta().bytes());
318                Ok(UpdateData::StateAndDelta { state, delta })
319            }
320            UpdateDataType::RelatedStateUpdate => {
321                let update = update_data.update_data_as_related_state_update().unwrap();
322                let state = State::from(update.state().bytes());
323                let related_to =
324                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
325                Ok(UpdateData::RelatedState { related_to, state })
326            }
327            UpdateDataType::RelatedDeltaUpdate => {
328                let update = update_data.update_data_as_related_delta_update().unwrap();
329                let delta = StateDelta::from(update.delta().bytes());
330                let related_to =
331                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
332                Ok(UpdateData::RelatedDelta { related_to, delta })
333            }
334            UpdateDataType::RelatedStateAndDeltaUpdate => {
335                let update = update_data
336                    .update_data_as_related_state_and_delta_update()
337                    .unwrap();
338                let state = State::from(update.state().bytes());
339                let delta = StateDelta::from(update.delta().bytes());
340                let related_to =
341                    ContractInstanceId::from_bytes(update.related_to().data().bytes()).unwrap();
342                Ok(UpdateData::RelatedStateAndDelta {
343                    related_to,
344                    state,
345                    delta,
346                })
347            }
348            _ => unreachable!(),
349        }
350    }
351}
352
353#[cfg(test)]
354mod update_data_wire_format_tests {
355    use super::*;
356
357    /// Wire-format pin for [`UpdateData`]. Variant ordering is a wire
358    /// contract: deployed contracts compiled against an older stdlib
359    /// deserialize `UpdateData` by positional tag, so reordering the
360    /// existing variants would silently route incoming state updates
361    /// into the wrong arm. This test locks the tag for `State(..)` at 0
362    /// and `Delta(..)` at 1. Adding a new variant at the end is
363    /// compatible; inserting or reordering existing variants is a
364    /// wire-format break and must trip this test.
365    #[test]
366    fn update_data_wire_format_is_stable() {
367        let state = UpdateData::State(State::from(vec![0xAA, 0xBB]));
368        let state_bytes = bincode::serialize(&state).unwrap();
369        assert_eq!(
370            state_bytes[..4],
371            [0, 0, 0, 0],
372            "UpdateData::State must stay at variant tag 0"
373        );
374
375        let delta = UpdateData::Delta(StateDelta::from(vec![0xCC, 0xDD]));
376        let delta_bytes = bincode::serialize(&delta).unwrap();
377        assert_eq!(
378            delta_bytes[..4],
379            [1, 0, 0, 0],
380            "UpdateData::Delta must stay at variant tag 1"
381        );
382
383        // Round-trip both.
384        let decoded_state: UpdateData<'_> = bincode::deserialize(&state_bytes).unwrap();
385        assert!(matches!(decoded_state, UpdateData::State(_)));
386        let decoded_delta: UpdateData<'_> = bincode::deserialize(&delta_bytes).unwrap();
387        assert!(matches!(decoded_delta, UpdateData::Delta(_)));
388    }
389}