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::{unknown_union_discriminant, TryFromFbs, WsApiError};
11use crate::common_generated::common::{UpdateData as FbsUpdateData, UpdateDataType};
12use crate::generated::client_request::RelatedContracts as FbsRelatedContracts;
13
14use super::key::instance_id_from_fbs;
15use super::{ContractError, ContractInstanceId, State, StateDelta, CONTRACT_KEY_SIZE};
16
17/// An update to a contract state or any required related contracts to update that state.
18// todo: this should be an enum probably
19#[non_exhaustive]
20#[derive(Debug, Serialize, Deserialize)]
21pub struct UpdateModification<'a> {
22    #[serde(borrow)]
23    pub new_state: Option<State<'a>>,
24    /// Request an other contract so updates can be resolved.
25    pub related: Vec<RelatedContract>,
26}
27
28impl<'a> UpdateModification<'a> {
29    /// Constructor for self when the state is valid.
30    pub fn valid(new_state: State<'a>) -> Self {
31        Self {
32            new_state: Some(new_state),
33            related: vec![],
34        }
35    }
36
37    /// Unwraps self returning a [`State`].
38    ///
39    /// Panics if self does not contain a state.
40    pub fn unwrap_valid(self) -> State<'a> {
41        match self.new_state {
42            Some(s) => s,
43            _ => panic!("failed unwrapping state in modification"),
44        }
45    }
46}
47
48impl UpdateModification<'_> {
49    /// Constructor for self when this contract still is missing some [`RelatedContract`]
50    /// to proceed with any verification or updates.
51    pub fn requires(related: Vec<RelatedContract>) -> Result<Self, ContractError> {
52        if related.is_empty() {
53            return Err(ContractError::InvalidUpdateWithInfo {
54                reason: "At least one related contract is required".into(),
55            });
56        }
57        Ok(Self {
58            new_state: None,
59            related,
60        })
61    }
62
63    /// Gets the pending related contracts.
64    pub fn get_related(&self) -> &[RelatedContract] {
65        &self.related
66    }
67
68    /// Copies the data if not owned and returns an owned version of self.
69    pub fn into_owned(self) -> UpdateModification<'static> {
70        let Self { new_state, related } = self;
71        UpdateModification {
72            new_state: new_state.map(State::into_owned),
73            related,
74        }
75    }
76
77    pub fn requires_dependencies(&self) -> bool {
78        !self.related.is_empty()
79    }
80}
81
82/// The contracts related to a parent or root contract. Tipically this means
83/// contracts which state requires to be verified or integrated in some way with
84/// the parent contract.
85#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
86pub struct RelatedContracts<'a> {
87    #[serde(borrow)]
88    map: HashMap<ContractInstanceId, Option<State<'a>>>,
89}
90
91impl RelatedContracts<'_> {
92    pub fn new() -> Self {
93        Self {
94            map: HashMap::new(),
95        }
96    }
97
98    /// Copies the data if not owned and returns an owned version of self.
99    pub fn into_owned(self) -> RelatedContracts<'static> {
100        let mut map = HashMap::with_capacity(self.map.len());
101        for (k, v) in self.map {
102            map.insert(k, v.map(|s| s.into_owned()));
103        }
104        RelatedContracts { map }
105    }
106
107    pub fn deser_related_contracts<'de, D>(deser: D) -> Result<RelatedContracts<'static>, D::Error>
108    where
109        D: serde::Deserializer<'de>,
110    {
111        let value = <RelatedContracts as Deserialize>::deserialize(deser)?;
112        Ok(value.into_owned())
113    }
114}
115
116impl RelatedContracts<'static> {
117    pub fn states(&self) -> impl Iterator<Item = (&ContractInstanceId, &Option<State<'static>>)> {
118        self.map.iter()
119    }
120}
121
122impl<'a> RelatedContracts<'a> {
123    pub fn update(
124        &mut self,
125    ) -> impl Iterator<Item = (&ContractInstanceId, &mut Option<State<'a>>)> + '_ {
126        self.map.iter_mut()
127    }
128
129    pub fn missing(&mut self, contracts: Vec<ContractInstanceId>) {
130        for key in contracts {
131            self.map.entry(key).or_default();
132        }
133    }
134}
135
136impl<'a> TryFromFbs<&FbsRelatedContracts<'a>> for RelatedContracts<'a> {
137    fn try_decode_fbs(related_contracts: &FbsRelatedContracts<'a>) -> Result<Self, WsApiError> {
138        let mut map = HashMap::with_capacity(related_contracts.contracts().len());
139        for related in related_contracts.contracts().iter() {
140            // Length-checked pass-through of the raw 32 bytes. This used to be
141            // `ContractInstanceId::from_bytes(..).unwrap()`, which base58-DECODED
142            // the raw id and then unwrapped: it panicked on every well-formed
143            // PUT carrying a related contract. See `instance_id_from_fbs`.
144            let id = instance_id_from_fbs(
145                "RelatedContract.instance_id.data",
146                related.instance_id().data().bytes(),
147            )?;
148            let state = State::from(related.state().bytes());
149            map.insert(id, Some(state));
150        }
151        Ok(RelatedContracts::from(map))
152    }
153}
154
155impl<'a> From<HashMap<ContractInstanceId, Option<State<'a>>>> for RelatedContracts<'a> {
156    fn from(related_contracts: HashMap<ContractInstanceId, Option<State<'a>>>) -> Self {
157        Self {
158            map: related_contracts,
159        }
160    }
161}
162
163/// A contract related to an other contract and the specification
164/// of the kind of update notifications that should be received by this contract.
165#[derive(Debug, Serialize, Deserialize)]
166pub struct RelatedContract {
167    pub contract_instance_id: ContractInstanceId,
168    pub mode: RelatedMode,
169    // todo: add a timeout so we stop listening/subscribing eventually
170}
171
172/// Specification of the notifications of interest from a related contract.
173#[derive(Debug, Serialize, Deserialize)]
174pub enum RelatedMode {
175    /// Retrieve the state once, don't be concerned with subsequent changes.
176    StateOnce,
177    /// Retrieve the state once, and then subscribe to updates.
178    StateThenSubscribe,
179}
180
181/// The result of calling the [`ContractInterface::validate_state`] function.
182#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
183pub enum ValidateResult {
184    Valid,
185    Invalid,
186    /// The peer will attempt to retrieve the requested contract states
187    /// and will call validate_state() again when it retrieves them.
188    RequestRelated(Vec<ContractInstanceId>),
189}
190
191/// Update notifications for a contract or a related contract.
192///
193/// This sits on the contract-update wire boundary. Marked `#[non_exhaustive]`
194/// so future variants (for example, new `Related*` shapes) can be added
195/// without a source-level break; downstream `match` sites must include a
196/// wildcard arm. Wire format is pinned by `update_data_wire_format_is_stable`.
197#[non_exhaustive]
198#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
199pub enum UpdateData<'a> {
200    State(#[serde(borrow)] State<'a>),
201    Delta(#[serde(borrow)] StateDelta<'a>),
202    StateAndDelta {
203        #[serde(borrow)]
204        state: State<'a>,
205        #[serde(borrow)]
206        delta: StateDelta<'a>,
207    },
208    RelatedState {
209        related_to: ContractInstanceId,
210        #[serde(borrow)]
211        state: State<'a>,
212    },
213    RelatedDelta {
214        related_to: ContractInstanceId,
215        #[serde(borrow)]
216        delta: StateDelta<'a>,
217    },
218    RelatedStateAndDelta {
219        related_to: ContractInstanceId,
220        #[serde(borrow)]
221        state: State<'a>,
222        #[serde(borrow)]
223        delta: StateDelta<'a>,
224    },
225}
226
227impl UpdateData<'_> {
228    pub fn size(&self) -> usize {
229        match self {
230            UpdateData::State(state) => state.size(),
231            UpdateData::Delta(delta) => delta.size(),
232            UpdateData::StateAndDelta { state, delta } => state.size() + delta.size(),
233            UpdateData::RelatedState { state, .. } => state.size() + CONTRACT_KEY_SIZE,
234            UpdateData::RelatedDelta { delta, .. } => delta.size() + CONTRACT_KEY_SIZE,
235            UpdateData::RelatedStateAndDelta { state, delta, .. } => {
236                state.size() + delta.size() + CONTRACT_KEY_SIZE
237            }
238        }
239    }
240
241    pub fn unwrap_delta(&self) -> &StateDelta<'_> {
242        match self {
243            UpdateData::Delta(delta) => delta,
244            _ => panic!(),
245        }
246    }
247
248    /// Copies the data if not owned and returns an owned version of self.
249    pub fn into_owned(self) -> UpdateData<'static> {
250        match self {
251            UpdateData::State(s) => UpdateData::State(State::from(s.into_bytes())),
252            UpdateData::Delta(d) => UpdateData::Delta(StateDelta::from(d.into_bytes())),
253            UpdateData::StateAndDelta { state, delta } => UpdateData::StateAndDelta {
254                delta: StateDelta::from(delta.into_bytes()),
255                state: State::from(state.into_bytes()),
256            },
257            UpdateData::RelatedState { related_to, state } => UpdateData::RelatedState {
258                related_to,
259                state: State::from(state.into_bytes()),
260            },
261            UpdateData::RelatedDelta { related_to, delta } => UpdateData::RelatedDelta {
262                related_to,
263                delta: StateDelta::from(delta.into_bytes()),
264            },
265            UpdateData::RelatedStateAndDelta {
266                related_to,
267                state,
268                delta,
269            } => UpdateData::RelatedStateAndDelta {
270                related_to,
271                state: State::from(state.into_bytes()),
272                delta: StateDelta::from(delta.into_bytes()),
273            },
274        }
275    }
276
277    pub(crate) fn get_self_states<'a>(
278        updates: &[UpdateData<'a>],
279    ) -> Vec<(Option<State<'a>>, Option<StateDelta<'a>>)> {
280        let mut own_states = Vec::with_capacity(updates.len());
281        for update in updates {
282            match update {
283                UpdateData::State(state) => own_states.push((Some(state.clone()), None)),
284                UpdateData::Delta(delta) => own_states.push((None, Some(delta.clone()))),
285                UpdateData::StateAndDelta { state, delta } => {
286                    own_states.push((Some(state.clone()), Some(delta.clone())))
287                }
288                _ => {}
289            }
290        }
291        own_states
292    }
293
294    pub(crate) fn deser_update_data<'de, D>(deser: D) -> Result<UpdateData<'static>, D::Error>
295    where
296        D: serde::Deserializer<'de>,
297    {
298        let value = <UpdateData as Deserialize>::deserialize(deser)?;
299        Ok(value.into_owned())
300    }
301}
302
303impl<'a> From<StateDelta<'a>> for UpdateData<'a> {
304    fn from(delta: StateDelta<'a>) -> Self {
305        UpdateData::Delta(delta)
306    }
307}
308
309impl<'a> TryFromFbs<&FbsUpdateData<'a>> for UpdateData<'a> {
310    fn try_decode_fbs(update_data: &FbsUpdateData<'a>) -> Result<Self, WsApiError> {
311        match update_data.update_data_type() {
312            UpdateDataType::StateUpdate => {
313                let update = update_data.update_data_as_state_update().unwrap();
314                let state = State::from(update.state().bytes());
315                Ok(UpdateData::State(state))
316            }
317            UpdateDataType::DeltaUpdate => {
318                let update = update_data.update_data_as_delta_update().unwrap();
319                let delta = StateDelta::from(update.delta().bytes());
320                Ok(UpdateData::Delta(delta))
321            }
322            UpdateDataType::StateAndDeltaUpdate => {
323                let update = update_data.update_data_as_state_and_delta_update().unwrap();
324                let state = State::from(update.state().bytes());
325                let delta = StateDelta::from(update.delta().bytes());
326                Ok(UpdateData::StateAndDelta { state, delta })
327            }
328            // The three `related_to` decodes below were
329            // `ContractInstanceId::from_bytes(..).unwrap()`, which base58-DECODED
330            // bytes that are already the final raw id and then unwrapped the
331            // failure. Every well-formed related update panicked; see
332            // `instance_id_from_fbs`.
333            UpdateDataType::RelatedStateUpdate => {
334                let update = update_data.update_data_as_related_state_update().unwrap();
335                let state = State::from(update.state().bytes());
336                let related_to = instance_id_from_fbs(
337                    "RelatedStateUpdate.related_to.data",
338                    update.related_to().data().bytes(),
339                )?;
340                Ok(UpdateData::RelatedState { related_to, state })
341            }
342            UpdateDataType::RelatedDeltaUpdate => {
343                let update = update_data.update_data_as_related_delta_update().unwrap();
344                let delta = StateDelta::from(update.delta().bytes());
345                let related_to = instance_id_from_fbs(
346                    "RelatedDeltaUpdate.related_to.data",
347                    update.related_to().data().bytes(),
348                )?;
349                Ok(UpdateData::RelatedDelta { related_to, delta })
350            }
351            UpdateDataType::RelatedStateAndDeltaUpdate => {
352                let update = update_data
353                    .update_data_as_related_state_and_delta_update()
354                    .unwrap();
355                let state = State::from(update.state().bytes());
356                let delta = StateDelta::from(update.delta().bytes());
357                let related_to = instance_id_from_fbs(
358                    "RelatedStateAndDeltaUpdate.related_to.data",
359                    update.related_to().data().bytes(),
360                )?;
361                Ok(UpdateData::RelatedStateAndDelta {
362                    related_to,
363                    state,
364                    delta,
365                })
366            }
367            // Reachable, not `unreachable!()`: the generated verifier for this
368            // union ends in `_ => Ok(())`, so any discriminant a client sets —
369            // including `NONE` — arrives here. See `unknown_union_discriminant`.
370            other => Err(unknown_union_discriminant("UpdateDataType", other.0)),
371        }
372    }
373}
374
375#[cfg(test)]
376mod update_data_wire_format_tests {
377    use super::*;
378
379    /// Wire-format pin for [`UpdateData`]. Variant ordering is a wire
380    /// contract: deployed contracts compiled against an older stdlib
381    /// deserialize `UpdateData` by positional tag, so reordering the
382    /// existing variants would silently route incoming state updates
383    /// into the wrong arm. This test locks the tag for `State(..)` at 0
384    /// and `Delta(..)` at 1. Adding a new variant at the end is
385    /// compatible; inserting or reordering existing variants is a
386    /// wire-format break and must trip this test.
387    #[test]
388    fn update_data_wire_format_is_stable() {
389        let state = UpdateData::State(State::from(vec![0xAA, 0xBB]));
390        let state_bytes = bincode::serialize(&state).unwrap();
391        assert_eq!(
392            state_bytes[..4],
393            [0, 0, 0, 0],
394            "UpdateData::State must stay at variant tag 0"
395        );
396
397        let delta = UpdateData::Delta(StateDelta::from(vec![0xCC, 0xDD]));
398        let delta_bytes = bincode::serialize(&delta).unwrap();
399        assert_eq!(
400            delta_bytes[..4],
401            [1, 0, 0, 0],
402            "UpdateData::Delta must stay at variant tag 1"
403        );
404
405        // Round-trip both.
406        let decoded_state: UpdateData<'_> = bincode::deserialize(&state_bytes).unwrap();
407        assert!(matches!(decoded_state, UpdateData::State(_)));
408        let decoded_delta: UpdateData<'_> = bincode::deserialize(&delta_bytes).unwrap();
409        assert!(matches!(decoded_delta, UpdateData::Delta(_)));
410    }
411}