essential_vm/
access.rs

1//! Access operation implementations.
2
3use crate::{
4    cached::LazyCache,
5    error::{AccessError, MissingAccessArgError, OpSyncResult},
6    repeat::Repeat,
7    sets::encode_set,
8    types::{
9        convert::{bytes_from_word, u8_32_from_word_4, word_4_from_u8_32},
10        solution::{Solution, SolutionIndex, SolutionSet},
11        Key, Value, Word,
12    },
13    Stack,
14};
15use std::collections::HashSet;
16
17#[cfg(test)]
18mod dec_vars;
19#[cfg(test)]
20mod predicate_exists;
21#[cfg(test)]
22mod test_utils;
23#[cfg(test)]
24mod tests;
25
26/// All necessary solution access required to check an individual predicate.
27#[derive(Clone, Copy, Debug)]
28pub struct Access<'a> {
29    /// The set of input data for each predicate being solved within the solution set.
30    ///
31    /// We require *all* solutions in order to handle checking predicate exists.
32    pub solutions: &'a [Solution],
33    /// Checking is performed for one solution at a time. This index refers to
34    /// the checked predicate's associated solution within the `SolutionSet` slice.
35    pub index: usize,
36    /// The keys being proposed for mutation for the predicate.
37    pub mutable_keys: &'a HashSet<&'a [Word]>,
38}
39
40impl<'a> Access<'a> {
41    /// A shorthand for constructing a `SolutionAccess` instance for checking
42    /// the predicate at the given index within the given solution.
43    ///
44    /// This constructor assumes that the given mutable keys contract is correct
45    /// for this solution. It is not checked by this function for performance.
46    pub fn new(
47        set: &'a SolutionSet,
48        solution_index: SolutionIndex,
49        mutable_keys: &'a HashSet<&[Word]>,
50    ) -> Self {
51        Self {
52            solutions: &set.solutions,
53            index: solution_index.into(),
54            mutable_keys,
55        }
56    }
57
58    /// The solution associated with the predicate currently being checked.
59    ///
60    /// **Panics** in the case that `self.index` is out of range of the `self.solutions` slice.
61    pub fn this_solution(&self) -> &Solution {
62        self.solutions
63            .get(self.index)
64            .expect("solution index out of range of solutions slice")
65    }
66}
67
68/// A helper for collecting all mutable keys that are proposed for mutation for
69/// the predicate at the given index.
70///
71/// Specifically, assists in calculating the `mut_keys_len` for
72/// `SolutionAccess`, as this is equal to the `.count()` of the returned iterator.
73///
74/// **Note:** In the case that the given solution is invalid and contains multiple
75/// mutations to the same key, the same key will be yielded multiple times.
76pub fn mut_keys(set: &SolutionSet, solution_index: SolutionIndex) -> impl Iterator<Item = &Key> {
77    set.solutions[solution_index as usize]
78        .state_mutations
79        .iter()
80        .map(|m| &m.key)
81}
82
83/// Get the mutable keys as slices
84pub fn mut_keys_slices(
85    set: &SolutionSet,
86    solution_index: SolutionIndex,
87) -> impl Iterator<Item = &[Word]> {
88    set.solutions[solution_index as usize]
89        .state_mutations
90        .iter()
91        .map(|m| m.key.as_ref())
92}
93
94/// Get the contract of mutable keys for this predicate.
95pub fn mut_keys_set(solution_set: &SolutionSet, solution_index: SolutionIndex) -> HashSet<&[Word]> {
96    mut_keys_slices(solution_set, solution_index).collect()
97}
98
99/// `Access::PredicateData` implementation.
100pub(crate) fn predicate_data(this_predicate_data: &[Value], stack: &mut Stack) -> OpSyncResult<()> {
101    let len = stack
102        .pop()
103        .map_err(|_| AccessError::MissingArg(MissingAccessArgError::PredDataLen))?;
104    let value_ix = stack
105        .pop()
106        .map_err(|_| AccessError::MissingArg(MissingAccessArgError::PredDataValueIx))?;
107    let slot_ix = stack
108        .pop()
109        .map_err(|_| AccessError::MissingArg(MissingAccessArgError::PredDataSlotIx))?;
110    let slot_ix = usize::try_from(slot_ix)
111        .map_err(|_| AccessError::PredicateDataSlotIxOutOfBounds(slot_ix))?;
112    let range = range_from_start_len(value_ix, len).ok_or(AccessError::InvalidAccessRange)?;
113    let words = resolve_predicate_data_range(this_predicate_data, slot_ix, range)?;
114    stack.extend(words.iter().copied())?;
115    Ok(())
116}
117
118/// `Access::PredicateDataLen` implementation.
119pub(crate) fn predicate_data_len(
120    this_predicate_data: &[Value],
121    stack: &mut Stack,
122) -> Result<(), AccessError> {
123    let slot_ix = stack
124        .pop()
125        .map_err(|_| MissingAccessArgError::PredDataSlotIx)?;
126    let slot_ix = usize::try_from(slot_ix)
127        .map_err(|_| AccessError::PredicateDataSlotIxOutOfBounds(slot_ix))?;
128    let len = resolve_predicate_data_len(this_predicate_data, slot_ix)?;
129    let w = Word::try_from(len).map_err(|_| AccessError::PredicateDataValueTooLarge(len))?;
130    stack
131        .push(w)
132        .expect("Can't fail because 1 is popped and 1 is pushed");
133    Ok(())
134}
135
136/// `Access::MutKeys` implementation.
137pub(crate) fn push_mut_keys(access: Access, stack: &mut Stack) -> OpSyncResult<()> {
138    encode_set(access.mutable_keys.iter().map(|k| k.iter().copied()), stack)
139}
140
141/// `Access::ThisAddress` implementation.
142pub(crate) fn this_address(solution: &Solution, stack: &mut Stack) -> OpSyncResult<()> {
143    let words = word_4_from_u8_32(solution.predicate_to_solve.predicate.0);
144    stack.extend(words)?;
145    Ok(())
146}
147
148/// `Access::ThisContractAddress` implementation.
149pub(crate) fn this_contract_address(solution: &Solution, stack: &mut Stack) -> OpSyncResult<()> {
150    let words = word_4_from_u8_32(solution.predicate_to_solve.contract.0);
151    stack.extend(words)?;
152    Ok(())
153}
154
155pub(crate) fn repeat_counter(stack: &mut Stack, repeat: &Repeat) -> OpSyncResult<()> {
156    let counter = repeat.counter()?;
157    Ok(stack.push(counter)?)
158}
159
160/// Implementation of the `Access::NumSlots` operation.
161pub(crate) fn predicate_data_slots(
162    stack: &mut Stack,
163    predicate_data: &[Value],
164) -> OpSyncResult<()> {
165    let num_slots = Word::try_from(predicate_data.len())
166        .map_err(|_| AccessError::SlotsLengthTooLarge(predicate_data.len()))?;
167    stack.push(num_slots)?;
168    Ok(())
169}
170
171/// Resolve a range of words at a predicate data slot.
172///
173/// Errors if the solution or predicate data indices are out of bounds.
174pub(crate) fn resolve_predicate_data_range(
175    predicate_data: &[Value],
176    slot_ix: usize,
177    value_range_ix: core::ops::Range<usize>,
178) -> Result<&[Word], AccessError> {
179    predicate_data
180        .get(slot_ix)
181        .ok_or(AccessError::PredicateDataSlotIxOutOfBounds(slot_ix as Word))?
182        .get(value_range_ix.clone())
183        .ok_or(AccessError::PredicateDataValueRangeOutOfBounds(
184            value_range_ix.start as Word,
185            value_range_ix.end as Word,
186        ))
187}
188
189/// Resolve the length of predicate data slot.
190///
191/// Errors if the solution or predicate data indices are out of bounds.
192pub(crate) fn resolve_predicate_data_len(
193    predicate_data: &[Value],
194    slot_ix: usize,
195) -> Result<usize, AccessError> {
196    predicate_data
197        .get(slot_ix)
198        .map(|slot| slot.len())
199        .ok_or(AccessError::PredicateDataSlotIxOutOfBounds(slot_ix as Word))
200}
201
202pub(crate) fn predicate_exists(
203    stack: &mut Stack,
204    solutions: &[Solution],
205    cache: &LazyCache,
206) -> OpSyncResult<()> {
207    let hash = u8_32_from_word_4(stack.pop4()?);
208    let found = cache.get_pred_data_hashes(solutions).contains(&hash);
209    stack.push(found as Word)?;
210    Ok(())
211}
212
213pub(crate) fn init_predicate_exists(
214    solutions: &[Solution],
215) -> impl Iterator<Item = essential_types::Hash> + '_ {
216    solutions.iter().map(|d| {
217        let data = d
218            .predicate_data
219            .iter()
220            .flat_map(|slot| {
221                Some(slot.len() as Word)
222                    .into_iter()
223                    .chain(slot.iter().cloned())
224            })
225            .chain(word_4_from_u8_32(d.predicate_to_solve.contract.0))
226            .chain(word_4_from_u8_32(d.predicate_to_solve.predicate.0))
227            .flat_map(bytes_from_word)
228            .collect::<Vec<_>>();
229        sha256(&data)
230    })
231}
232
233fn sha256(bytes: &[u8]) -> [u8; 32] {
234    use sha2::{Digest, Sha256};
235    let mut hasher = Sha256::new();
236    hasher.update(bytes);
237    let result: [u8; 32] = hasher.finalize().into();
238    result
239}
240
241fn range_from_start_len(start: Word, len: Word) -> Option<std::ops::Range<usize>> {
242    let start = usize::try_from(start).ok()?;
243    let len = usize::try_from(len).ok()?;
244    let end = start.checked_add(len)?;
245    Some(start..end)
246}