tezos_smart_rollup_mock/
host.rs

1// SPDX-FileCopyrightText: 2022-2023 TriliTech <contact@trili.tech>
2// SPDX-FileCopyrightText: 2023 Marigold <contact@marigold.dev>
3// SPDX-FileCopyrightText: 2022-2023 Nomadic Labs <contact@nomadic-labs.com>
4//
5// SPDX-License-Identifier: MIT
6
7//! Contains an implementation of [SmartRollupCore] suitable for running the
8//! kernel standalone for experiements and testing purposes. Used when
9//! _not_ compiling to **wasm**.
10
11use crate::state::{HostState, NextInput};
12use crate::MockHost;
13use core::{
14    cell::RefCell,
15    ptr,
16    slice::{from_raw_parts, from_raw_parts_mut},
17};
18use tezos_smart_rollup_core::smart_rollup_core::{ReadInputMessageInfo, SmartRollupCore};
19use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE;
20use tezos_smart_rollup_host::metadata::METADATA_SIZE;
21use tezos_smart_rollup_host::Error;
22
23impl From<HostState> for MockHost {
24    fn from(state: HostState) -> Self {
25        Self {
26            info: super::info_for_level(state.curr_level as i32),
27            state: RefCell::new(state),
28        }
29    }
30}
31
32impl AsMut<HostState> for MockHost {
33    fn as_mut(&mut self) -> &mut HostState {
34        self.state.get_mut()
35    }
36}
37
38unsafe impl SmartRollupCore for MockHost {
39    unsafe fn read_input(
40        &self,
41        message_info: *mut ReadInputMessageInfo,
42        dst: *mut u8,
43        max_bytes: usize,
44    ) -> i32 {
45        if let Some(NextInput { level, id, payload }) =
46            self.state.borrow_mut().handle_read_input(max_bytes)
47        {
48            let input_message_info = ReadInputMessageInfo {
49                level: level as i32,
50                id: id as i32,
51            };
52            ptr::write(message_info, input_message_info);
53
54            // safe as payload.len() <= max_bytes
55            let slice = from_raw_parts_mut(dst, payload.len());
56            slice.copy_from_slice(payload.as_slice());
57
58            payload.len().try_into().unwrap()
59        } else {
60            0_i32
61        }
62    }
63
64    unsafe fn write_debug(&self, src: *const u8, num_bytes: usize) {
65        let debug_out = from_raw_parts(src, num_bytes).to_vec();
66
67        let debug = String::from_utf8(debug_out).expect("unexpected non-utf8 debug log");
68
69        eprint!("{}", &debug);
70    }
71
72    unsafe fn write_output(&self, src: *const u8, num_bytes: usize) -> i32 {
73        let output = from_raw_parts(src, num_bytes).to_vec();
74
75        self.state
76            .borrow_mut()
77            .handle_write_output(output)
78            .map(|_| 0)
79            .unwrap_or_else(Error::code)
80    }
81
82    unsafe fn store_has(&self, path: *const u8, len: usize) -> i32 {
83        let path = from_raw_parts(path, len);
84        self.state
85            .borrow()
86            .handle_store_has(path)
87            .unwrap_or_else(Error::code)
88    }
89
90    unsafe fn store_read(
91        &self,
92        path: *const u8,
93        len: usize,
94        offset: usize,
95        dst: *mut u8,
96        max_bytes: usize,
97    ) -> i32 {
98        let path = from_raw_parts(path, len);
99
100        let bytes = self
101            .state
102            .borrow()
103            .handle_store_read(path, offset, max_bytes);
104
105        match bytes {
106            Ok(bytes) => {
107                assert!(bytes.len() <= max_bytes);
108
109                let slice = from_raw_parts_mut(dst, bytes.len());
110                slice.copy_from_slice(bytes.as_slice());
111
112                bytes.len().try_into().unwrap()
113            }
114            Err(e) => e.code(),
115        }
116    }
117
118    unsafe fn store_write(
119        &self,
120        path: *const u8,
121        len: usize,
122        offset: usize,
123        src: *const u8,
124        num_bytes: usize,
125    ) -> i32 {
126        let path = from_raw_parts(path, len);
127        let bytes = from_raw_parts(src, num_bytes);
128
129        self.state
130            .borrow_mut()
131            .handle_store_write(path, offset, bytes)
132            .map(|_| 0)
133            .unwrap_or_else(Error::code)
134    }
135
136    unsafe fn store_delete(&self, path: *const u8, len: usize) -> i32 {
137        let path = from_raw_parts(path, len);
138
139        self.state
140            .borrow_mut()
141            .handle_store_delete(path)
142            .map(|_| 0)
143            .unwrap_or_else(Error::code)
144    }
145
146    unsafe fn store_delete_value(&self, path: *const u8, len: usize) -> i32 {
147        let path = from_raw_parts(path, len);
148
149        self.state
150            .borrow_mut()
151            .handle_store_delete_value(path)
152            .map(|_| 0)
153            .unwrap_or_else(Error::code)
154    }
155
156    unsafe fn store_list_size(&self, path: *const u8, len: usize) -> i64 {
157        let path = from_raw_parts(path, len);
158
159        self.state
160            .borrow()
161            .handle_store_list_size(path)
162            .unwrap_or_else(|e| e.code() as i64)
163    }
164
165    unsafe fn store_move(
166        &self,
167        from_path: *const u8,
168        from_path_len: usize,
169        to_path: *const u8,
170        to_path_len: usize,
171    ) -> i32 {
172        let from_path = from_raw_parts(from_path, from_path_len);
173        let to_path = from_raw_parts(to_path, to_path_len);
174
175        self.state
176            .borrow_mut()
177            .handle_store_move(from_path, to_path)
178            .map(|_| 0)
179            .unwrap_or_else(Error::code)
180    }
181
182    unsafe fn store_copy(
183        &self,
184        from_path: *const u8,
185        from_path_len: usize,
186        to_path: *const u8,
187        to_path_len: usize,
188    ) -> i32 {
189        let from_path = from_raw_parts(from_path, from_path_len);
190        let to_path = from_raw_parts(to_path, to_path_len);
191
192        self.state
193            .borrow_mut()
194            .handle_store_copy(from_path, to_path)
195            .map(|_| 0)
196            .unwrap_or_else(Error::code)
197    }
198
199    unsafe fn reveal_preimage(
200        &self,
201        hash_addr: *const u8,
202        hash_len: usize,
203        destination_addr: *mut u8,
204        max_bytes: usize,
205    ) -> i32 {
206        // only `Reveal_hash` supported for now.
207        let hash = from_raw_parts(hash_addr, hash_len)
208            .try_into()
209            .unwrap_or_else(|_| panic!("Hash is not {} bytes", PREIMAGE_HASH_SIZE));
210
211        let bytes = self
212            .state
213            .borrow()
214            .handle_reveal_preimage(&hash, max_bytes)
215            .to_vec();
216
217        assert!(bytes.len() <= max_bytes);
218
219        let slice = from_raw_parts_mut(destination_addr, bytes.len());
220        slice.copy_from_slice(bytes.as_slice());
221
222        bytes.len().try_into().unwrap()
223    }
224
225    unsafe fn store_value_size(&self, path: *const u8, path_len: usize) -> i32 {
226        let path = from_raw_parts(path, path_len);
227        self.state
228            .borrow()
229            .handle_store_value_size(path)
230            .unwrap_or_else(Error::code)
231    }
232
233    unsafe fn reveal_metadata(&self, destination_addr: *mut u8, max_bytes: usize) -> i32 {
234        assert!(METADATA_SIZE <= max_bytes);
235        let metadata: [u8; METADATA_SIZE] =
236            self.state.borrow().get_metadata().clone().into();
237        let slice = from_raw_parts_mut(destination_addr, metadata.len());
238        slice.copy_from_slice(metadata.as_slice());
239        metadata.len().try_into().unwrap()
240    }
241
242    #[cfg(feature = "proto-alpha")]
243    unsafe fn reveal(
244        &self,
245        _payload_addr: *const u8,
246        _payload_len: usize,
247        _destination_addr: *mut u8,
248        _max_bytes: usize,
249    ) -> i32 {
250        // TODO: https://gitlab.com/tezos/tezos/-/issues/6171
251        unimplemented!("The `reveal` host function is not yet mocked.")
252    }
253}
254
255#[cfg(test)]
256mod tests {
257
258    use super::MockHost;
259
260    use crate::state::HostState;
261    use tezos_smart_rollup_core::{MAX_FILE_CHUNK_SIZE, MAX_INPUT_MESSAGE_SIZE};
262    use tezos_smart_rollup_host::input::Message;
263    use tezos_smart_rollup_host::{
264        metadata::RollupMetadata,
265        path::RefPath,
266        runtime::{Runtime, RuntimeError},
267    };
268
269    #[test]
270    fn test_read_input_message() {
271        // Arrange
272        let mut mock_host = MockHost::default();
273
274        mock_host
275            .as_mut()
276            .add_input(vec![5; MAX_INPUT_MESSAGE_SIZE / 2]);
277
278        // Act
279        let result = mock_host.read_input();
280
281        // Assert
282        let expected = Ok(Some(Message::new(
283            mock_host.level(),
284            0,
285            vec![5; MAX_INPUT_MESSAGE_SIZE / 2],
286        )));
287
288        assert_eq!(expected, result);
289    }
290
291    #[test]
292    fn test_reveal_preimage() {
293        // Arrange
294        let mut state = HostState::default();
295
296        let data = vec![b'a'; 3 * 1024];
297
298        let hash = state.set_preimage(data);
299
300        let mock_host = MockHost::from(state);
301
302        let mut buffer = [0; 300];
303        // Act
304        let _result = mock_host.reveal_preimage(&hash, &mut buffer);
305
306        // Assert
307
308        assert_eq!(buffer, [b'a'; 300]);
309    }
310
311    #[test]
312    fn test_reveal_metadata() {
313        // Arrange
314
315        let metadata_bytes = [
316            // sr1 as 20 bytes
317            b'M', 165, 28, b']', 231, 161, 205, 212, 148, 193, b'[', b'S', 129, b'^', 31,
318            170, b'L', 26, 150, 202, // origination level as 4 bytes
319            0, 0, 0, 42,
320        ];
321
322        let expected_metadata = RollupMetadata::from(metadata_bytes);
323        let state = HostState::default_with_metadata(expected_metadata.clone());
324        let mock_host = MockHost::from(state); // Act
325
326        // Act
327        let result = mock_host.reveal_metadata();
328
329        // Assert
330        assert_eq!(expected_metadata, result);
331    }
332
333    #[test]
334    fn read_value_slice_not_found() {
335        let mock = MockHost::default();
336        const PATH: RefPath<'static> = RefPath::assert_from(b"/some/path");
337        let mut buffer = [0_u8; 16];
338
339        assert_eq!(
340            mock.store_read_slice(&PATH, 0, &mut buffer),
341            Err(RuntimeError::HostErr(
342                tezos_smart_rollup_host::Error::StoreNotAValue
343            ))
344        );
345    }
346
347    #[test]
348    fn read_value_slice_partial_buffer_fill() {
349        let mut mock = MockHost::default();
350        const PATH: RefPath<'static> = RefPath::assert_from(b"/some/path");
351        let value = [1_u8; 8];
352        let mut buffer = [0_u8; 16];
353
354        mock.store_write(&PATH, &value, 0)
355            .expect("Could not write value to store");
356
357        assert_eq!(mock.store_read_slice(&PATH, 0, &mut buffer), Ok(8_usize));
358
359        assert_eq!(buffer, [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
360    }
361
362    #[test]
363    fn read_value_slice_complete_buffer_fill() {
364        let mut mock = MockHost::default();
365        const PATH: RefPath<'static> = RefPath::assert_from(b"/some/path");
366        let value = [1_u8; 16];
367        let mut buffer = [0_u8; 16];
368
369        mock.store_write(&PATH, &value, 0)
370            .expect("Could not write value to store");
371
372        assert_eq!(mock.store_read_slice(&PATH, 0, &mut buffer), Ok(16_usize));
373
374        assert_eq!(buffer, [1_u8; 16]);
375    }
376
377    #[test]
378    fn test_store_write() {
379        let mut state = HostState::default();
380        let size = 256_i32;
381        let data = vec![b'a'; size as usize];
382        let path = b"/a/b";
383
384        state.handle_store_write(path, 0, &data).unwrap();
385        let value_size = state.handle_store_value_size(path).unwrap();
386
387        assert_eq!(size, value_size)
388    }
389
390    #[test]
391    fn store_read_and_write_all() {
392        let mut mock = MockHost::default();
393        const PATH: RefPath = RefPath::assert_from(b"/path/value");
394
395        let value: Vec<u8> = (0..MAX_FILE_CHUNK_SIZE * 2 + 100)
396            .map(|v| (v % 100).try_into().unwrap())
397            .collect();
398
399        Runtime::store_write_all(&mut mock, &PATH, &value)
400            .expect("Could not write value to store");
401
402        let value_in_durable = Runtime::store_read_all(&mock, &PATH)
403            .expect("Could not read the value from the store");
404
405        let size = mock.store_value_size(&PATH);
406
407        assert_eq!(Ok(value.len()), size);
408        assert_eq!(value_in_durable, value);
409    }
410
411    #[test]
412    fn store_write_all_delete_previous_value() {
413        let mut mock = MockHost::default();
414        const PATH: RefPath = RefPath::assert_from(b"/path/value");
415
416        // First write a value of size ~4.2KB
417        let initial_value: Vec<u8> = (0..MAX_FILE_CHUNK_SIZE * 2 + 100)
418            .map(|v| (v % 100).try_into().unwrap())
419            .collect();
420
421        Runtime::store_write_all(&mut mock, &PATH, &initial_value)
422            .expect("Could not write value to store");
423        let initial_size = mock.store_value_size(&PATH).expect("Could not read size");
424        let initial_value_in_store = Runtime::store_read_all(&mock, &PATH)
425            .expect("Could not read the value from the store");
426
427        // Then write a new value of size ~2.1 KB
428        let smaller_value: Vec<u8> = (0..MAX_FILE_CHUNK_SIZE + 100)
429            .map(|v| (v % 50).try_into().unwrap())
430            .collect();
431
432        Runtime::store_write_all(&mut mock, &PATH, &smaller_value)
433            .expect("Could not write value to store");
434        let new_size = mock.store_value_size(&PATH).expect("Could not read size");
435        let new_value_in_store = Runtime::store_read_all(&mock, &PATH)
436            .expect("Could not read the value from the store");
437
438        // The size of the value in the storage should have been shrinked, and
439        // the new value read from the storage shouldn't be equal to the initial
440        // one.
441        assert!(new_size < initial_size);
442        assert_ne!(new_value_in_store, initial_value_in_store);
443        assert_eq!(new_value_in_store, smaller_value);
444    }
445}