Skip to main content

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}