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 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}