1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Formatter};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use tempfile::tempdir;
use crate::session::{Session, SessionData};
use crate::store::ContractStore;
use crate::Error::{self, PersistenceError};
/// A handle to the piecrust virtual machine.
///
/// It is instantiated using [`new`] or [`ephemeral`], and can be used to spawn
/// multiple [`Session`]s using [`session`].
///
/// These sessions are synchronized with the help of a sync loop. [`Deletions`]
/// and [`squashes`] are assured to not delete any commits used as a base for
/// sessions until these are dropped. A handle to this loop is available at
/// [`sync_thread`].
///
/// Users are encouraged to instantiate a `VM` once during the lifetime of their
/// program and spawn sessions as needed.
///
/// [`new`]: VM::new
/// [`ephemeral`]: VM::ephemeral
/// [`Session`]: Session
/// [`session`]: VM::session
/// [`Deletions`]: VM::delete_commit
/// [`squashes`]: VM::squash_commit
/// [`sync_thread`]: VM::sync_thread
#[derive(Debug)]
pub struct VM {
host_queries: HostQueries,
store: ContractStore,
}
impl VM {
/// Creates a new `VM`, reading the given `dir`ectory for existing commits
/// and bytecode.
///
/// The directory will be used to save any future session commits made by
/// this `VM` instance.
///
/// # Errors
/// If the directory contains unparseable or inconsistent data.
pub fn new<P: AsRef<Path>>(root_dir: P) -> Result<Self, Error>
where
P: Into<PathBuf>,
{
let store = ContractStore::new(root_dir)
.map_err(|err| PersistenceError(Arc::new(err)))?;
Ok(Self {
host_queries: HostQueries::default(),
store,
})
}
/// Creates a new `VM` using a new temporary directory.
///
/// Any session commits made by this machine should be considered discarded
/// once this `VM` instance drops.
///
/// # Errors
/// If creating a temporary directory fails.
pub fn ephemeral() -> Result<Self, Error> {
let tmp = tempdir().map_err(|err| PersistenceError(Arc::new(err)))?;
let tmp = tmp.path().to_path_buf();
let store = ContractStore::new(tmp)
.map_err(|err| PersistenceError(Arc::new(err)))?;
Ok(Self {
host_queries: HostQueries::default(),
store,
})
}
/// Registers a [host `query`] with the given `name`.
///
/// The query will be available to any session spawned *after* this was
/// called.
///
/// [host `query`]: HostQuery
pub fn register_host_query<Q, S>(&mut self, name: S, query: Q)
where
Q: 'static + HostQuery,
S: Into<Cow<'static, str>>,
{
self.host_queries.insert(name, query);
}
/// Spawn a [`Session`].
///
/// # Errors
/// If base commit is provided but does not exist.
///
/// [`Session`]: Session
pub fn session(
&self,
data: impl Into<SessionData>,
) -> Result<Session, Error> {
let data = data.into();
let contract_session = match data.base {
Some(base) => self
.store
.session(base.into())
.map_err(|err| PersistenceError(Arc::new(err)))?,
_ => self.store.genesis_session(),
};
Ok(Session::new(
contract_session,
self.host_queries.clone(),
data,
))
}
/// Return all existing commits.
pub fn commits(&self) -> Vec<[u8; 32]> {
self.store.commits().into_iter().map(Into::into).collect()
}
/// Remove the diff files from a commit by applying them to the base
/// memories, and writing them back to disk.
///
/// # Errors
/// If this function fails, it may be due to any number of reasons:
///
/// - [`remove_file`] may fail
/// - [`write`] may fail
///
/// Failing may result in a corrupted commit, and the user is encouraged to
/// call [`delete_commit`].
///
/// [`remove_file`]: std::fs::remove_file
/// [`write`]: std::fs::write
/// [`delete_commit`]: VM::delete_commit
pub fn squash_commit(&self, root: [u8; 32]) -> Result<(), Error> {
self.store
.squash_commit(root.into())
.map_err(|err| PersistenceError(Arc::new(err)))
}
/// Deletes the given commit from disk.
pub fn delete_commit(&self, root: [u8; 32]) -> Result<(), Error> {
self.store
.delete_commit(root.into())
.map_err(|err| PersistenceError(Arc::new(err)))
}
/// Return the root directory of the virtual machine.
///
/// This is either the directory passed in by using [`new`], or the
/// temporary directory created using [`ephemeral`].
///
/// [`new`]: VM::new
/// [`ephemeral`]: VM::ephemeral
pub fn root_dir(&self) -> &Path {
self.store.root_dir()
}
/// Returns a reference to the synchronization thread.
pub fn sync_thread(&self) -> &thread::Thread {
self.store.sync_loop()
}
}
#[derive(Default, Clone)]
pub struct HostQueries {
map: BTreeMap<Cow<'static, str>, Arc<dyn HostQuery>>,
}
impl Debug for HostQueries {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.map.keys()).finish()
}
}
impl HostQueries {
pub fn insert<Q, S>(&mut self, name: S, query: Q)
where
Q: 'static + HostQuery,
S: Into<Cow<'static, str>>,
{
self.map.insert(name.into(), Arc::new(query));
}
pub fn call(&self, name: &str, buf: &mut [u8], len: u32) -> Option<u32> {
self.map.get(name).map(|host_query| host_query(buf, len))
}
}
/// A query executable on the host.
///
/// The buffer containing the argument the contract used to call the query,
/// together with the argument's length, are passed as arguments to the
/// function, and should be processed first. Once this is done, the implementor
/// should emplace the return of the query in the same buffer, and return the
/// length written.
pub trait HostQuery: Send + Sync + Fn(&mut [u8], u32) -> u32 {}
impl<F> HostQuery for F where F: Send + Sync + Fn(&mut [u8], u32) -> u32 {}