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