Skip to main content

mavryk_smart_rollup_mock/
lib.rs

1// SPDX-FileCopyrightText: 2022-2023 TriliTech <contact@trili.tech>
2//
3// SPDX-License-Identifier: MIT
4
5#![doc = include_str!("../README.md")]
6#![deny(missing_docs)]
7#![deny(rustdoc::broken_intra_doc_links)]
8
9mod host;
10mod state;
11
12extern crate mavryk_crypto_rs as crypto;
13
14use crypto::hash::ContractKt1Hash;
15use crypto::hash::HashType;
16use crypto::hash::SmartRollupHash;
17use mavryk_data_encoding::enc::BinWriter;
18use mavryk_smart_rollup_core::PREIMAGE_HASH_SIZE;
19use mavryk_smart_rollup_encoding::inbox;
20use mavryk_smart_rollup_encoding::michelson::Michelson;
21use mavryk_smart_rollup_encoding::michelson::MichelsonUnit;
22use mavryk_smart_rollup_encoding::public_key_hash::PublicKeyHash;
23use mavryk_smart_rollup_encoding::smart_rollup::SmartRollupAddress;
24use mavryk_smart_rollup_encoding::timestamp::Timestamp;
25use mavryk_smart_rollup_host::metadata::RollupMetadata;
26
27use state::HostState;
28use std::cell::RefCell;
29
30const MAXIMUM_REBOOTS_PER_INPUT: i32 = 1000;
31
32/// Flag used in the durable storage by the kernel to ask a reboot from the PVM
33/// without increasing level.
34const REBOOT_FLAG_KEY: &str = "/kernel/env/reboot";
35
36const TOO_MANY_REBOOT_FLAG_KEY: &str = "/readonly/kernel/env/too_many_reboot";
37
38/// The path where the WASM PVM exposes the remaining reboots a kernel can do
39/// with a given inbox. Written as i32 (little-endian).
40const REBOOT_COUNTER_KEY: &str = "/readonly/kernel/env/reboot_counter";
41
42const NAIROBI_ACTIVATION_LEVEL: u32 = 3_760_129;
43const NAIROBI_BLOCK_TIME: i64 = 15;
44// Nairobi activated approximately at 0:07AM UTC on June 24th 2023.
45const NAIROBI_ACTIVATION_TIMESTAMP: i64 = 1_687_561_630;
46
47pub use state::InMemoryStore;
48
49/// The runtime host when _not_ running in **wasm**.
50#[derive(Debug)]
51pub struct MockHost {
52    state: RefCell<HostState>,
53    info: inbox::InfoPerLevel,
54}
55
56impl Default for MockHost {
57    fn default() -> Self {
58        let address = SmartRollupAddress::new(SmartRollupHash(vec![
59                0;
60                HashType::SmartRollupHash
61                    .size()
62            ]));
63
64        Self::with_address(&address)
65    }
66}
67
68/// Specifies the sender & source of a message from an L1-contract.
69///
70/// Additionally, the target smart rollup is specified - by default
71/// referring to the rollup address of the running kernel.
72pub struct TransferMetadata {
73    sender: ContractKt1Hash,
74    source: PublicKeyHash,
75    destination: Option<SmartRollupAddress>,
76}
77
78impl TransferMetadata {
79    /// Create transfer metadata from a smart contract hash & public key hash.
80    pub fn new<E1: std::fmt::Debug, E2: std::fmt::Debug>(
81        sender: impl TryInto<ContractKt1Hash, Error = E1>,
82        source: impl TryInto<PublicKeyHash, Error = E2>,
83    ) -> Self {
84        Self {
85            sender: sender.try_into().unwrap(),
86            source: source.try_into().unwrap(),
87            destination: None,
88        }
89    }
90
91    /// Where desired, manually set the target rollup address to be different
92    /// to that of the current rollup.
93    pub fn override_destination(&mut self, dest: SmartRollupAddress) {
94        self.destination = Some(dest);
95    }
96}
97
98impl MockHost {
99    /// Create a new instance of the `MockHost`, specifying the rollup address.
100    pub fn with_address(address: &SmartRollupAddress) -> Self {
101        let raw_rollup_address = address
102            .hash()
103            .0
104            .as_slice()
105            .try_into()
106            .expect("Incorrect length for SmartRollupHash");
107
108        let mut state = HostState::default_with_metadata(RollupMetadata {
109            raw_rollup_address,
110            origination_level: NAIROBI_ACTIVATION_LEVEL,
111        });
112
113        // Ensure reboots correctly initialised for testing without using
114        // `run_level` API.
115        let reboots = MAXIMUM_REBOOTS_PER_INPUT;
116        let bytes = reboots.to_le_bytes().to_vec();
117        state.store.0.set_value(REBOOT_COUNTER_KEY, bytes);
118
119        state.curr_level = NAIROBI_ACTIVATION_LEVEL;
120
121        let info = info_for_level(state.curr_level as i32);
122
123        let mut host = Self {
124            state: state.into(),
125            info,
126        };
127
128        // Ensure inbox setup correctly
129        host.bump_level();
130
131        host
132    }
133
134    /// Append an internal message to the current inbox.
135    pub fn add_transfer(&mut self, payload: impl Michelson, metadata: &TransferMetadata) {
136        let destination = metadata.destination.clone().unwrap_or_else(|| {
137            SmartRollupAddress::new(self.state.borrow().get_metadata().address())
138        });
139
140        let transfer = inbox::Transfer {
141            payload,
142            sender: metadata.sender.clone(),
143            source: metadata.source.clone(),
144            destination,
145        };
146
147        let message = inbox::InboxMessage::Internal(
148            inbox::InternalInboxMessage::Transfer(transfer),
149        );
150
151        let mut inbox_message = Vec::new();
152        message
153            .serialize(&mut inbox_message)
154            .expect("serialization of transfer failed");
155
156        self.as_mut().add_input(inbox_message);
157    }
158
159    /// Append an external message to the current inbox.
160    pub fn add_external(&mut self, message: impl BinWriter) {
161        let mut payload = Vec::new();
162        message
163            .bin_write(&mut payload)
164            .expect("serialization of external payload failed");
165
166        let external_message = inbox::InboxMessage::External::<MichelsonUnit>(&payload);
167
168        self.add_inbox_message(external_message);
169    }
170
171    /// Make a preimage available to the _reveal_data_ channel.
172    pub fn set_preimage(&mut self, preimage: Vec<u8>) -> [u8; PREIMAGE_HASH_SIZE] {
173        self.as_mut().set_preimage(preimage)
174    }
175
176    /// Runs `kernel_run` against the current level's inbox.
177    ///
178    /// - Includes the `StartOfLevel`, `InfoPerLevel` & `EndOfLevel` messages.
179    /// - Returns the level the kernel was run at.
180    pub fn run_level(&mut self, kernel_run: fn(&mut Self)) -> u32 {
181        self.finalise_inputs();
182
183        let mut reboots = MAXIMUM_REBOOTS_PER_INPUT;
184
185        loop {
186            let bytes = reboots.to_le_bytes().to_vec();
187            self.as_mut().store.0.set_value(REBOOT_COUNTER_KEY, bytes);
188
189            kernel_run(self);
190            self.as_mut().store.0.node_delete(TOO_MANY_REBOOT_FLAG_KEY);
191
192            reboots -= 1;
193
194            let reboot_requested = self
195                .as_mut()
196                .store
197                .0
198                .maybe_get_value(REBOOT_FLAG_KEY)
199                .is_some();
200
201            if reboot_requested {
202                self.as_mut().store.0.node_delete(REBOOT_FLAG_KEY);
203
204                if reboots > 0 {
205                    continue;
206                }
207
208                self.as_mut()
209                    .store
210                    .0
211                    .set_value(TOO_MANY_REBOOT_FLAG_KEY, vec![]);
212            }
213            break;
214        }
215
216        let level_ran_at = self.state.borrow().curr_level;
217        self.bump_level();
218
219        level_ran_at
220    }
221
222    /// Returns the level of the next `kernel_run`.
223    pub fn level(&self) -> u32 {
224        self.state.borrow().curr_level
225    }
226
227    /// Returns the `InfoPerLevel` of the next `kernel_run`.
228    pub fn info_per_level(&self) -> &inbox::InfoPerLevel {
229        &self.info
230    }
231
232    /// Show the outbox at the given level
233    pub fn outbox_at(&self, level: u32) -> Vec<Vec<u8>> {
234        self.state.borrow().store.0.outbox_at(level).to_vec()
235    }
236
237    fn bump_level(&mut self) {
238        let state = self.as_mut();
239        state.curr_level += 1;
240        state.curr_input_id = 0;
241        state.input.truncate(0);
242        let curr_info = info_for_level(state.curr_level as i32);
243
244        let sol = inbox::InboxMessage::<MichelsonUnit>::Internal(
245            inbox::InternalInboxMessage::StartOfLevel,
246        );
247
248        self.add_inbox_message(sol);
249
250        let info = inbox::InboxMessage::<MichelsonUnit>::Internal(
251            inbox::InternalInboxMessage::InfoPerLevel(curr_info.clone()),
252        );
253
254        self.add_inbox_message(info);
255        self.info = curr_info;
256    }
257
258    fn finalise_inputs(&mut self) {
259        let eol = inbox::InboxMessage::<MichelsonUnit>::Internal(
260            inbox::InternalInboxMessage::EndOfLevel,
261        );
262
263        self.add_inbox_message(eol);
264    }
265
266    fn add_inbox_message<Expr: Michelson>(&mut self, message: inbox::InboxMessage<Expr>) {
267        let mut inbox_message = Vec::new();
268        message
269            .serialize(&mut inbox_message)
270            .expect("serialization of message failed");
271
272        self.as_mut().add_input(inbox_message);
273    }
274}
275
276fn info_for_level(level: i32) -> inbox::InfoPerLevel {
277    let timestamp = (level as i64 - 1 - (NAIROBI_ACTIVATION_LEVEL as i64))
278        * NAIROBI_BLOCK_TIME
279        + NAIROBI_ACTIVATION_TIMESTAMP;
280
281    let hash = crypto::blake2b::digest_256(&timestamp.to_le_bytes()).unwrap();
282
283    inbox::InfoPerLevel {
284        predecessor: crypto::hash::BlockHash(hash),
285        predecessor_timestamp: Timestamp::from(timestamp),
286    }
287}