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