dusk_vm/lib.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//![doc = include_str!("../README.md")]
8
9#![deny(missing_docs)]
10#![deny(clippy::all)]
11#![deny(unused_crate_dependencies)]
12#![deny(unused_extern_crates)]
13
14extern crate alloc;
15
16pub use self::execute::feature::Activation as FeatureActivation;
17pub use self::execute::{Config as ExecutionConfig, execute, gen_contract_id};
18pub use piecrust::{
19 CallReceipt, CallTree, CallTreeElem, ContractData, Error, PageOpening,
20 Session,
21};
22
23/// Contract Metadata
24pub struct ContractMetadata {
25 /// Contract ID
26 pub contract_id: ContractId,
27 /// Owner
28 pub owner: Vec<u8>,
29}
30
31unsafe impl Send for ContractMetadata {}
32unsafe impl Sync for ContractMetadata {}
33
34use alloc::vec::Vec;
35use std::collections::HashMap;
36use std::fmt::{self, Debug, Formatter};
37use std::path::{Path, PathBuf};
38use std::thread;
39
40use dusk_core::abi::{ContractId, Metadata, Query};
41use piecrust::{SessionData, VM as PiecrustVM};
42
43use self::host_queries::{
44 host_hash, host_keccak256, host_poseidon_hash, host_secp256k1_recover,
45 host_sha256, host_verify_bls, host_verify_bls_multisig,
46 host_verify_groth16_bn254, host_verify_kzg_proof, host_verify_plonk,
47 host_verify_schnorr,
48};
49
50pub(crate) mod cache;
51mod execute;
52pub mod host_queries;
53
54/// The Virtual Machine (VM) for executing smart contracts in the Dusk Network.
55///
56/// The `VM` struct serves as the core for managing the network's state,
57/// executing smart contracts, and interfacing with host functions. It supports
58/// both persistent and ephemeral sessions for handling transactions, contract
59/// queries and contract deployments.
60pub struct VM {
61 inner: PiecrustVM,
62 hq_activation: HashMap<String, FeatureActivation>,
63}
64
65impl From<PiecrustVM> for VM {
66 fn from(piecrust_vm: PiecrustVM) -> Self {
67 VM {
68 inner: piecrust_vm,
69 hq_activation: HashMap::new(),
70 }
71 }
72}
73
74impl Debug for VM {
75 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
76 self.inner.fmt(f)
77 }
78}
79
80impl VM {
81 /// Creates a new instance of the virtual machine.
82 ///
83 /// This method initializes the VM with a given root directory and
84 /// registers the necessary host-queries for contract execution.
85 ///
86 /// # Arguments
87 /// * `root_dir` - The path to the root directory for the VM's state
88 /// storage. This directory will be used to save any future session
89 /// commits made by this `VM` instance.
90 ///
91 /// # Returns
92 /// A new `VM` instance.
93 ///
94 /// # Errors
95 /// If the directory contains unparseable or inconsistent data.
96 ///
97 /// # Examples
98 /// ```rust
99 /// use dusk_vm::VM;
100 ///
101 /// let vm = VM::new("/path/to/root_dir");
102 /// ```
103 pub fn new(
104 root_dir: impl AsRef<Path> + Into<PathBuf>,
105 ) -> Result<Self, Error> {
106 let mut vm: Self = PiecrustVM::new(root_dir)?.into();
107 vm.register_host_queries();
108 Ok(vm)
109 }
110
111 /// Creates an ephemeral VM instance.
112 ///
113 /// This method initializes a VM that operates in memory without persisting
114 /// state. It is useful for testing or temporary computations.
115 ///
116 /// # Returns
117 /// A new ephemeral `VM` instance.
118 ///
119 /// # Errors
120 /// If creating a temporary directory fails.
121 ///
122 /// # Examples
123 /// ```rust
124 /// use dusk_vm::VM;
125 ///
126 /// let vm = VM::ephemeral();
127 /// ```
128 pub fn ephemeral() -> Result<VM, Error> {
129 let mut vm: Self = PiecrustVM::ephemeral()?.into();
130 vm.register_host_queries();
131 Ok(vm)
132 }
133
134 /// Sets the activation height for a specific host query.
135 ///
136 /// This method associates a previously registered host query with a block
137 /// height at which it becomes active. Before this activation height,
138 /// the host query will be excluded from session execution.
139 ///
140 /// **Note:** The specified host query must already be registered in the
141 /// global host queries registry before calling this method.
142 ///
143 /// # Arguments
144 /// * `host_query` - The name of the host query to activate.
145 /// * `activation` - The block height at which the host query becomes
146 /// active.
147 ///
148 /// # Panics
149 /// This method will panic if the provided `host_query` is not already
150 /// registered in the global host queries registry.
151 ///
152 /// # Examples
153 /// ```rust
154 /// use dusk_vm::VM;
155 /// use dusk_vm::FeatureActivation;
156 /// use dusk_core::abi::Query;
157 ///
158 /// let mut vm = VM::ephemeral().unwrap();
159 /// vm.with_hq_activation(Query::KECCAK256, FeatureActivation::Height(100));
160 /// ```
161 pub fn with_hq_activation<S: Into<String>>(
162 &mut self,
163 host_query: S,
164 activation: FeatureActivation,
165 ) {
166 let host_query = host_query.into();
167 if self.inner.host_queries().get(&host_query).is_none() {
168 panic!(
169 "Host query '{host_query}' must be registered before setting activation"
170 );
171 }
172 self.hq_activation.insert(host_query, activation);
173 }
174
175 /// Creates a new session for transaction execution.
176 ///
177 /// This method initializes a session with a specific base state commit,
178 /// chain identifier, and block height. Sessions allow for isolated
179 /// transaction execution without directly affecting the persistent VM
180 /// state until finalized.
181 ///
182 /// # Arguments
183 /// * `base` - A 32-byte array representing the base state from which the
184 /// session begins.
185 /// * `chain_id` - The identifier of the network.
186 /// * `block_height` - The current block height at which the session is
187 /// created.
188 ///
189 /// # Returns
190 /// A `Result` containing a `Session` instance for executing transactions,
191 /// or an error if the session cannot be initialized.
192 ///
193 /// # Errors
194 /// If base commit is provided but does not exist.
195 ///
196 /// # Examples
197 /// ```rust
198 /// use dusk_vm::VM;
199 ///
200 /// const CHAIN_ID: u8 = 42;
201 ///
202 /// // create a genesis session
203 /// let vm = VM::ephemeral().unwrap();
204 /// let session = vm.genesis_session(CHAIN_ID);
205 ///
206 /// // [...] apply changes to the network through the running session
207 ///
208 /// // commit the changes
209 /// let base = session.commit().unwrap();
210 ///
211 /// // spawn a new session on top of the base-commit
212 /// let block_height = 21;
213 /// let session = vm.session(base, CHAIN_ID, block_height).unwrap();
214 /// ```
215 pub fn session(
216 &self,
217 base: [u8; 32],
218 chain_id: u8,
219 block_height: u64,
220 ) -> Result<Session, Error> {
221 let mut builder = SessionData::builder()
222 .base(base)
223 .insert(Metadata::CHAIN_ID, chain_id)?
224 .insert(Metadata::BLOCK_HEIGHT, block_height)?;
225 // If the block height is greater than 0, exclude host queries
226 // that are not yet activated.
227 // We don't want to exclude host queries for block height 0 because it's
228 // used for query sessions
229 if block_height > 0 {
230 for (host_query, activation) in &self.hq_activation {
231 if !activation.is_active_at(block_height) {
232 builder = builder.exclude_hq(host_query.clone());
233 }
234 }
235 }
236 self.inner.session(builder)
237 }
238
239 /// Initializes a session for setting up the genesis block.
240 ///
241 /// This method creates a session specifically for defining the genesis
242 /// block, which serves as the starting state of the network. The
243 /// genesis session uses the specified chain ID.
244 ///
245 /// # Arguments
246 /// * `chain_id` - The identifier of the blockchain chain for which the
247 /// genesis state is initialized.
248 ///
249 /// # Returns
250 /// A `Session` instance for defining the genesis block.
251 ///
252 /// # Examples
253 /// ```rust
254 /// use dusk_vm::VM;
255 ///
256 /// const CHAIN_ID: u8 = 42;
257 ///
258 /// let vm = VM::ephemeral().unwrap();
259 /// let genesis_session = vm.genesis_session(CHAIN_ID);
260 /// ```
261 pub fn genesis_session(&self, chain_id: u8) -> Session {
262 self.inner
263 .session(
264 SessionData::builder()
265 .insert(Metadata::CHAIN_ID, chain_id)
266 .expect("Inserting chain ID in metadata should succeed")
267 .insert(Metadata::BLOCK_HEIGHT, 0)
268 .expect(
269 "Inserting block height in metadata should succeed",
270 ),
271 )
272 .expect("Creating a genesis session should always succeed")
273 }
274
275 /// Retrieves all pending commits in the VM.
276 ///
277 /// This method fetches unfinalized state changes for inspection or
278 /// processing.
279 ///
280 /// # Returns
281 /// A vector of commits.
282 pub fn commits(&self) -> Vec<[u8; 32]> {
283 self.inner.commits()
284 }
285
286 /// Deletes a specified commit from the VM.
287 ///
288 /// # Arguments
289 /// * `commit` - The commit to be deleted.
290 pub fn delete_commit(&self, root: [u8; 32]) -> Result<(), Error> {
291 self.inner.delete_commit(root)
292 }
293
294 /// Finalizes a specified commit, applying its state changes permanently.
295 ///
296 /// # Arguments
297 /// * `commit` - The commit to be finalized.
298 pub fn finalize_commit(&self, root: [u8; 32]) -> Result<(), Error> {
299 self.inner.finalize_commit(root)
300 }
301
302 /// Returns the root directory of the VM.
303 ///
304 /// This is either the directory passed in by using [`Self::new`], or the
305 /// temporary directory created using [`Self::ephemeral`].
306 pub fn root_dir(&self) -> &Path {
307 self.inner.root_dir()
308 }
309
310 /// Returns a reference to the synchronization thread.
311 pub fn sync_thread(&self) -> &thread::Thread {
312 self.inner.sync_thread()
313 }
314
315 fn register_host_queries(&mut self) {
316 self.inner.register_host_query(Query::HASH, host_hash);
317 self.inner
318 .register_host_query(Query::POSEIDON_HASH, host_poseidon_hash);
319 self.inner
320 .register_host_query(Query::VERIFY_PLONK, host_verify_plonk);
321 self.inner.register_host_query(
322 Query::VERIFY_GROTH16_BN254,
323 host_verify_groth16_bn254,
324 );
325 self.inner
326 .register_host_query(Query::VERIFY_SCHNORR, host_verify_schnorr);
327 self.inner
328 .register_host_query(Query::VERIFY_BLS, host_verify_bls);
329 self.inner.register_host_query(
330 Query::VERIFY_BLS_MULTISIG,
331 host_verify_bls_multisig,
332 );
333 self.inner
334 .register_host_query(Query::KECCAK256, host_keccak256);
335 self.inner.register_host_query(Query::SHA256, host_sha256);
336 self.inner.register_host_query(
337 Query::VERIFY_KZG_PROOF,
338 host_verify_kzg_proof,
339 );
340 self.inner.register_host_query(
341 Query::SECP256K1_RECOVER,
342 host_secp256k1_recover,
343 );
344 }
345
346 /// Remove contract
347 pub fn remove_3rd_party(
348 &self,
349 contract_id: ContractId,
350 ) -> Result<(), Error> {
351 self.inner.remove_module(contract_id)
352 }
353
354 /// Recompile contract
355 pub fn recompile_3rd_party(
356 &self,
357 contract_id: ContractId,
358 ) -> Result<(), Error> {
359 self.inner.recompile_module(contract_id)
360 }
361}