diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index ae920537..d03b0729 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -3,8 +3,10 @@ members = [ "box", "c-example", "callcenter", + "callstack", "initializer", "counter", + "counter_float", "debugger", "double_counter", "empty_initializer", diff --git a/contracts/callcenter/src/lib.rs b/contracts/callcenter/src/lib.rs index b6801c86..4b08ffd4 100644 --- a/contracts/callcenter/src/lib.rs +++ b/contracts/callcenter/src/lib.rs @@ -80,6 +80,11 @@ impl Callcenter { uplink::caller() } + /// Return the entire call stack of this contract + pub fn return_callstack(&self) -> Vec { + uplink::callstack() + } + /// Make sure that the caller of this contract is the contract itself pub fn call_self(&self) -> Result { let self_id = uplink::self_id(); @@ -90,6 +95,16 @@ impl Callcenter { } } + /// Return a call stack after calling itself n times + pub fn call_self_n_times(&self, n: u32) -> Vec { + let self_id = uplink::self_id(); + match n { + 0 => uplink::callstack(), + _ => uplink::call(self_id, "call_self_n_times", &(n - 1)) + .expect("calling self should succeed") + } + } + /// Calls the `spend` function of the `contract` with no arguments, and the /// given `gas_limit`, assuming the called function returns `()`. It will /// then return the call's result itself. @@ -133,6 +148,12 @@ unsafe fn call_self(arg_len: u32) -> u32 { wrap_call(arg_len, |_: ()| STATE.call_self()) } +/// Expose `Callcenter::call_self_n_times()` to the host +#[no_mangle] +unsafe fn call_self_n_times(arg_len: u32) -> u32 { + wrap_call(arg_len, |n: u32| STATE.call_self_n_times(n)) +} + /// Expose `Callcenter::call_spend_with_limit` to the host #[no_mangle] unsafe fn call_spend_with_limit(arg_len: u32) -> u32 { @@ -153,6 +174,12 @@ unsafe fn return_caller(arg_len: u32) -> u32 { wrap_call(arg_len, |_: ()| STATE.return_caller()) } +/// Expose `Callcenter::return_callstack()` to the host +#[no_mangle] +unsafe fn return_callstack(arg_len: u32) -> u32 { + wrap_call(arg_len, |_: ()| STATE.return_callstack()) +} + /// Expose `Callcenter::delegate_query()` to the host #[no_mangle] unsafe fn delegate_query(arg_len: u32) -> u32 { diff --git a/contracts/callstack/Cargo.toml b/contracts/callstack/Cargo.toml new file mode 100644 index 00000000..9a0b8a1a --- /dev/null +++ b/contracts/callstack/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "callstack" +version = "0.1.0" +authors = [ + "Milosz Muszynski ", +] +edition = "2021" + +license = "MPL-2.0" + +[dependencies] +piecrust-uplink = { path = "../../piecrust-uplink", features = ["abi", "dlmalloc"] } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/contracts/callstack/src/lib.rs b/contracts/callstack/src/lib.rs new file mode 100644 index 00000000..c4239747 --- /dev/null +++ b/contracts/callstack/src/lib.rs @@ -0,0 +1,34 @@ +// 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. + +//! Contract which exposes the call stack + +#![no_std] + +extern crate alloc; + +use piecrust_uplink as uplink; +use alloc::vec::Vec; +use uplink::ContractId; + +/// Struct that describes the state of the contract +pub struct CallStack; + +/// State of the Counter contract +static mut STATE: CallStack = CallStack; + +impl CallStack { + /// Return the call stack + pub fn return_callstack(&self) -> Vec { + uplink::callstack() + } +} + +/// Expose `CallStack::read_callstack()` to the host +#[no_mangle] +unsafe fn return_callstack(arg_len: u32) -> u32 { + uplink::wrap_call_unchecked(arg_len, |_: ()| STATE.return_callstack()) +} diff --git a/contracts/counter_float/Cargo.toml b/contracts/counter_float/Cargo.toml new file mode 100644 index 00000000..265b431a --- /dev/null +++ b/contracts/counter_float/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "counter_float" +version = "0.1.0" +authors = [ + "Kristoffer Ström ", + "Milosz Muszynski ", +] +edition = "2021" + +license = "MPL-2.0" + +[dependencies] +piecrust-uplink = { path = "../../piecrust-uplink", features = ["abi", "dlmalloc"] } + +[lib] +crate-type = ["cdylib", "rlib"] diff --git a/contracts/counter_float/src/lib.rs b/contracts/counter_float/src/lib.rs new file mode 100644 index 00000000..f7598872 --- /dev/null +++ b/contracts/counter_float/src/lib.rs @@ -0,0 +1,45 @@ +// 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. + +//! Contract to implement a simple counter that can be read and incremented by +//! one count. + +#![no_std] + +use piecrust_uplink as uplink; + +/// Struct that describes the state of the Counter contract +pub struct Counter { + value: f64, +} + +/// State of the Counter contract +static mut STATE: Counter = Counter { value: 0xfc as f64 }; + +impl Counter { + /// Read the value of the counter + pub fn read_value(&self) -> f64 { + self.value + } + + /// Increment the value of the counter by 1 + pub fn increment(&mut self) { + let value = self.value + 1.0; + self.value = value; + } +} + +/// Expose `Counter::read_value()` to the host +#[no_mangle] +unsafe fn read_value(arg_len: u32) -> u32 { + uplink::wrap_call_unchecked(arg_len, |_: ()| STATE.read_value()) +} + +/// Expose `Counter::increment()` to the host +#[no_mangle] +unsafe fn increment(arg_len: u32) -> u32 { + uplink::wrap_call_unchecked(arg_len, |_: ()| STATE.increment()) +} diff --git a/contracts/crossover/src/lib.rs b/contracts/crossover/src/lib.rs index a10f74ef..9729e2e7 100644 --- a/contracts/crossover/src/lib.rs +++ b/contracts/crossover/src/lib.rs @@ -67,6 +67,27 @@ impl Crossover { self.set_crossover(value_to_set); } + // Chain of ICC is not being rolled back when a callee panics and its panic + // is not propagated up the call chain. + pub fn check_iccs_dont_rollback( + &mut self, + contract: ContractId, + value_to_set: i32, + ) { + self.set_crossover(value_to_set); + + const ANY_VALUE_1: i32 = 5; + const ANY_VALUE_2: i32 = 6; + + uplink::debug!("calling panicking contract {contract:?}"); + uplink::call::<_, ()>( + contract, + "set_back_and_panic", + &(ANY_VALUE_1, ANY_VALUE_2), + ) + .expect_err("should give an error on a panic"); + } + // Sets the contract's value and then calls its caller's [`set_crossover`] // call to set their value. The caller is assumed to be another crossover // contract. @@ -125,6 +146,14 @@ unsafe fn check_consistent_state_on_errors(arg_len: u32) -> u32 { }) } +/// Expose `Crossover::check_iccs_dont_rollback()` to the host +#[no_mangle] +unsafe fn check_iccs_dont_rollback(arg_len: u32) -> u32 { + uplink::wrap_call(arg_len, |(contract, s)| { + STATE.check_iccs_dont_rollback(contract, s) + }) +} + /// Expose `Crossover::set_back_and_panic()` to the host #[no_mangle] unsafe fn set_back_and_panic(arg_len: u32) -> u32 { diff --git a/piecrust-uplink/CHANGELOG.md b/piecrust-uplink/CHANGELOG.md index ddf33b1e..be51a444 100644 --- a/piecrust-uplink/CHANGELOG.md +++ b/piecrust-uplink/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.17.3] - 2024-12-19 + ## [0.17.2] - 2024-12-17 ### Added @@ -250,7 +252,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#136]: https://github.com/dusk-network/piecrust/issues/136 -[Unreleased]: https://github.com/dusk-network/piecrust/compare/uplink-0.17.2...HEAD +[Unreleased]: https://github.com/dusk-network/piecrust/compare/uplink-0.17.3...HEAD +[0.17.3]: https://github.com/dusk-network/piecrust/compare/uplink-0.17.2...uplink-0.17.3 [0.17.2]: https://github.com/dusk-network/piecrust/compare/uplink-0.17.1...uplink-0.17.2 [0.17.1]: https://github.com/dusk-network/piecrust/compare/uplink-0.17.0...uplink-0.17.1 [0.17.0]: https://github.com/dusk-network/piecrust/compare/uplink-0.16.0...uplink-0.17.0 diff --git a/piecrust-uplink/Cargo.toml b/piecrust-uplink/Cargo.toml index b90d9d50..1434d21d 100644 --- a/piecrust-uplink/Cargo.toml +++ b/piecrust-uplink/Cargo.toml @@ -7,7 +7,7 @@ categories = ["wasm", "no-std", "cryptography::cryptocurrencies"] keywords = ["virtual", "machine", "smart", "contract", "wasm"] repository = "https://github.com/dusk-network/piecrust" -version = "0.17.2" +version = "0.17.3" edition = "2021" license = "MPL-2.0" diff --git a/piecrust-uplink/src/abi/state.rs b/piecrust-uplink/src/abi/state.rs index 5073587b..c5617952 100644 --- a/piecrust-uplink/src/abi/state.rs +++ b/piecrust-uplink/src/abi/state.rs @@ -58,6 +58,7 @@ mod ext { pub fn feed(arg_len: u32); pub fn caller() -> i32; + pub fn callstack() -> i32; pub fn limit() -> u64; pub fn spent() -> u64; pub fn owner(contract_id: *const u8) -> i32; @@ -289,6 +290,22 @@ pub fn caller() -> Option { } } +/// Returns IDs of all calling contracts present in the calling stack +pub fn callstack() -> Vec { + let n = unsafe { ext::callstack() }; + with_arg_buf(|buf| { + let mut v = Vec::new(); + for i in 0..n as usize { + let mut bytes = [0; CONTRACT_ID_BYTES]; + bytes.copy_from_slice( + &buf[i * CONTRACT_ID_BYTES..(i + 1) * CONTRACT_ID_BYTES], + ); + v.push(ContractId::from_bytes(bytes)); + } + v + }) +} + /// Returns the gas limit with which the contact was called. pub fn limit() -> u64 { unsafe { ext::limit() } diff --git a/piecrust/CHANGELOG.md b/piecrust/CHANGELOG.md index cade6d78..72bfe7fc 100644 --- a/piecrust/CHANGELOG.md +++ b/piecrust/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.27.0] - 2024-12-18 + +### Added + +- Access to callstack via uplink API [#405] + +### Changed + +- Store merkle tree positions in a binary format [#410] + ## [0.26.0] - 2024-10-21 ### Changed @@ -475,6 +485,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +[#410]: https://github.com/dusk-network/piecrust/issues/410 +[#405]: https://github.com/dusk-network/piecrust/issues/405 [#396]: https://github.com/dusk-network/piecrust/issues/396 [#393]: https://github.com/dusk-network/piecrust/issues/393 [#388]: https://github.com/dusk-network/piecrust/issues/388 @@ -524,7 +536,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 -[Unreleased]: https://github.com/dusk-network/piecrust/compare/piecrust-0.26.0...HEAD +[Unreleased]: https://github.com/dusk-network/piecrust/compare/piecrust-0.27.0...HEAD +[0.27.0]: https://github.com/dusk-network/piecrust/compare/piecrust-0.26.0...piecrust-0.27.0 [0.26.0]: https://github.com/dusk-network/piecrust/compare/piecrust-0.25.0...piecrust-0.26.0 [0.25.0]: https://github.com/dusk-network/piecrust/compare/piecrust-0.24.0...piecrust-0.25.0 [0.24.0]: https://github.com/dusk-network/piecrust/compare/piecrust-0.23.0...piecrust-0.24.0 diff --git a/piecrust/Cargo.toml b/piecrust/Cargo.toml index e649f956..f4aa9ed8 100644 --- a/piecrust/Cargo.toml +++ b/piecrust/Cargo.toml @@ -7,14 +7,14 @@ categories = ["wasm", "no-std", "cryptography::cryptocurrencies"] keywords = ["virtual", "machine", "smart", "contract", "wasm"] repository = "https://github.com/dusk-network/piecrust" -version = "0.26.0" +version = "0.27.0" edition = "2021" license = "MPL-2.0" [dependencies] crumbles = { version = "0.3", path = "../crumbles" } -piecrust-uplink = { version = "0.17", path = "../piecrust-uplink" } +piecrust-uplink = { version = "0.17.3", path = "../piecrust-uplink" } dusk-wasmtime = { version = "21.0.0-alpha", default-features = false, features = ["cranelift", "runtime", "parallel-compilation"] } bytecheck = "0.6" diff --git a/piecrust/src/call_tree.rs b/piecrust/src/call_tree.rs index 15a5d1be..b578a0b3 100644 --- a/piecrust/src/call_tree.rs +++ b/piecrust/src/call_tree.rs @@ -101,6 +101,20 @@ impl CallTree { current.map(|inner| unsafe { (*inner).elem }) } + /// Returns all call ids. + pub(crate) fn call_ids(&self) -> Vec<&ContractId> { + let mut v = Vec::new(); + let mut current = self.0; + + while current.is_some() { + let p = *current.as_ref().unwrap(); + v.push(unsafe { &(*p).elem.contract_id }); + current = current.and_then(|inner| unsafe { (*inner).parent }); + } + + v + } + /// Clears the call tree of all elements. pub(crate) fn clear(&mut self) { unsafe { diff --git a/piecrust/src/imports.rs b/piecrust/src/imports.rs index 79a441e4..dd5d7cb0 100644 --- a/piecrust/src/imports.rs +++ b/piecrust/src/imports.rs @@ -55,6 +55,7 @@ impl Imports { fn import(store: &mut Store, name: &str, is_64: bool) -> Option { Some(match name { "caller" => Func::wrap(store, caller), + "callstack" => Func::wrap(store, callstack), "c" => match is_64 { false => Func::wrap(store, wasm32::c), true => Func::wrap(store, wasm64::c), @@ -386,6 +387,21 @@ fn caller(env: Caller) -> i32 { } } +fn callstack(env: Caller) -> i32 { + let env = env.data(); + let instance = env.self_instance(); + + let mut i = 0usize; + for contract_id in env.call_ids() { + instance.with_arg_buf_mut(|buf| { + buf[i * CONTRACT_ID_BYTES..(i + 1) * CONTRACT_ID_BYTES] + .copy_from_slice(contract_id.as_bytes()); + }); + i += 1; + } + i as i32 +} + fn feed(mut fenv: Caller, arg_len: u32) -> WasmtimeResult<()> { let env = fenv.data_mut(); let instance = env.self_instance(); diff --git a/piecrust/src/session.rs b/piecrust/src/session.rs index bd40ce82..6e90399e 100644 --- a/piecrust/src/session.rs +++ b/piecrust/src/session.rs @@ -643,6 +643,10 @@ impl Session { self.inner.call_tree.nth_parent(n) } + pub(crate) fn call_ids(&self) -> Vec<&ContractId> { + self.inner.call_tree.call_ids() + } + /// Creates a new instance of the given contract, returning its memory /// length. fn create_instance( diff --git a/piecrust/src/store.rs b/piecrust/src/store.rs index 23455dd6..4d74c77a 100644 --- a/piecrust/src/store.rs +++ b/piecrust/src/store.rs @@ -19,7 +19,8 @@ use std::collections::btree_map::Entry::*; use std::collections::btree_map::Keys; use std::collections::{BTreeMap, BTreeSet}; use std::fmt::{Debug, Formatter}; -use std::fs::create_dir_all; +use std::fs::{create_dir_all, OpenOptions}; +use std::io::{BufReader, BufWriter}; use std::path::{Path, PathBuf}; use std::sync::{mpsc, Arc, Mutex}; use std::{fs, io, thread}; @@ -29,10 +30,10 @@ use piecrust_uplink::ContractId; use session::ContractDataEntry; use tree::{Hash, NewContractIndex}; -use crate::store::commit::CommitHulk; +use crate::store::commit::Hulk; use crate::store::tree::{ position_from_contract, BaseInfo, ContractIndexElement, ContractsMerkle, - PageTree, + TreePos, }; pub use bytecode::Bytecode; pub use memory::{Memory, PAGE_SIZE}; @@ -45,6 +46,8 @@ const BYTECODE_DIR: &str = "bytecode"; const MEMORY_DIR: &str = "memory"; const LEAF_DIR: &str = "leaf"; const BASE_FILE: &str = "base"; +const TREE_POS_FILE: &str = "tree_pos"; +const TREE_POS_OPT_FILE: &str = "tree_pos_opt"; const ELEMENT_FILE: &str = "element"; const OBJECTCODE_EXTENSION: &str = "a"; const METADATA_EXTENSION: &str = "m"; @@ -70,14 +73,17 @@ impl Debug for ContractStore { } } +#[derive(Debug)] pub struct CommitStore { commits: BTreeMap, + main_index: NewContractIndex, } impl CommitStore { pub fn new() -> Self { Self { commits: BTreeMap::new(), + main_index: NewContractIndex::new(), } } @@ -89,6 +95,40 @@ impl CommitStore { self.commits.get(hash) } + pub fn get_element_and_base( + &self, + hash: &Hash, + contract_id: &ContractId, + ) -> (Option<*const ContractIndexElement>, Option) { + match self.commits.get(hash) { + Some(commit) => { + let e = commit.index.get(contract_id); + (e.map(|a| a as *const ContractIndexElement), commit.base) + } + None => { + let e = self.main_index.get(contract_id); + (e.map(|a| a as *const ContractIndexElement), None) + } + } + } + + pub fn get_element_and_base_mut( + &mut self, + hash: &Hash, + contract_id: &ContractId, + ) -> (Option<*mut ContractIndexElement>, Option) { + match self.commits.get_mut(hash) { + Some(commit) => { + let e = commit.index.get_mut(contract_id); + (e.map(|a| a as *mut ContractIndexElement), commit.base) + } + None => { + let e = self.main_index.get_mut(contract_id); + (e.map(|a| a as *mut ContractIndexElement), None) + } + } + } + pub fn contains_key(&self, hash: &Hash) -> bool { self.commits.contains_key(hash) } @@ -98,7 +138,17 @@ impl CommitStore { } pub fn remove_commit(&mut self, hash: &Hash) { - self.commits.remove(hash); + if let Some(commit) = self.commits.remove(hash) { + commit.index.move_into(&mut self.main_index); + } + } + + pub fn insert_main_index( + &mut self, + contract_id: &ContractId, + element: ContractIndexElement, + ) { + self.main_index.insert_contract_index(contract_id, element); } } @@ -237,17 +287,14 @@ impl ContractStore { fn session_with_base(&self, base: Option) -> ContractSession { let base_commit = base.and_then(|hash| { - self.commit_store - .lock() - .unwrap() - .get_commit(&hash) - .map(|commit| commit.to_hulk()) + self.commit_store.lock().unwrap().get_commit(&hash).cloned() }); ContractSession::new( &self.root_dir, self.engine.clone(), base_commit, self.call.as_ref().expect("call should exist").clone(), + self.commit_store.clone(), ) } } @@ -273,7 +320,8 @@ fn read_all_commits>( continue; } tracing::trace!("before read_commit"); - let commit = read_commit(engine, entry.path())?; + let commit = + read_commit(engine, entry.path(), commit_store.clone())?; tracing::trace!("before read_commit"); let root = *commit.root(); commit_store.lock().unwrap().insert_commit(root, commit); @@ -286,9 +334,10 @@ fn read_all_commits>( fn read_commit>( engine: &Engine, commit_dir: P, + commit_store: Arc>, ) -> io::Result { let commit_dir = commit_dir.as_ref(); - let commit = commit_from_dir(engine, commit_dir)?; + let commit = commit_from_dir(engine, commit_dir, commit_store)?; Ok(commit) } @@ -317,6 +366,16 @@ fn base_path_main, S: AsRef>( Ok(dir.join(BASE_FILE)) } +fn tree_pos_path_main, S: AsRef>( + main_dir: P, + commit_id: S, +) -> io::Result { + let commit_id = commit_id.as_ref(); + let dir = main_dir.as_ref().join(commit_id); + fs::create_dir_all(&dir)?; + Ok(dir.join(TREE_POS_OPT_FILE)) +} + fn commit_id_to_hash>(commit_id: S) -> Hash { let hash: [u8; 32] = hex::decode(commit_id.as_ref()) .expect("Hex decoding of commit id string should succeed") @@ -336,6 +395,7 @@ fn contract_id_from_hex>(contract_id: S) -> ContractId { fn commit_from_dir>( engine: &Engine, dir: P, + commit_store: Arc>, ) -> io::Result { let dir = dir.as_ref(); let mut commit_id: Option = None; @@ -361,8 +421,22 @@ fn commit_from_dir>( // let contracts_merkle_path = dir.join(MERKLE_FILE); let leaf_dir = main_dir.join(LEAF_DIR); tracing::trace!("before index_merkle_from_path"); - let (index, contracts_merkle) = - index_merkle_from_path(main_dir, leaf_dir, &maybe_hash)?; + + let tree_pos = if let Some(ref hash_hex) = commit_id { + let tree_pos_path = main_dir.join(hash_hex).join(TREE_POS_FILE); + let tree_pos_opt_path = main_dir.join(hash_hex).join(TREE_POS_OPT_FILE); + Some(tree_pos_from_path(tree_pos_path, tree_pos_opt_path)?) + } else { + None + }; + + let (index, contracts_merkle) = index_merkle_from_path( + main_dir, + leaf_dir, + &maybe_hash, + commit_store.clone(), + tree_pos.as_ref(), + )?; tracing::trace!("after index_merkle_from_path"); let bytecode_dir = main_dir.join(BYTECODE_DIR); @@ -396,7 +470,7 @@ fn commit_from_dir>( let contract_memory_dir = memory_dir.join(&contract_hex); - for page_index in &contract_index.page_indices { + for page_index in contract_index.page_indices() { let main_page_path = page_path(&contract_memory_dir, *page_index); if !main_page_path.is_file() { let path = ContractSession::find_page( @@ -418,10 +492,19 @@ fn commit_from_dir>( } } + let base = if let Some(ref hash_hex) = commit_id { + let base_info_path = main_dir.join(hash_hex).join(BASE_FILE); + base_from_path(base_info_path)?.maybe_base + } else { + None + }; + Ok(Commit { index, contracts_merkle, maybe_hash, + commit_store: Some(commit_store), + base, }) } @@ -429,6 +512,8 @@ fn index_merkle_from_path( main_path: impl AsRef, leaf_dir: impl AsRef, maybe_commit_id: &Option, + commit_store: Arc>, + maybe_tree_pos: Option<&TreePos>, ) -> io::Result<(NewContractIndex, ContractsMerkle)> { let leaf_dir = leaf_dir.as_ref(); @@ -442,12 +527,13 @@ fn index_merkle_from_path( let contract_id = contract_id_from_hex(contract_id_hex.to_string_lossy()); let contract_leaf_path = leaf_dir.join(contract_id_hex); - let element_path = ContractSession::find_element( + let (element_path, element_depth) = ContractSession::find_element( *maybe_commit_id, &contract_leaf_path, &main_path, + 0, ) - .unwrap_or(contract_leaf_path.join(ELEMENT_FILE)); + .unwrap_or((contract_leaf_path.join(ELEMENT_FILE), 0)); if element_path.is_file() { let element_bytes = fs::read(&element_path)?; let element: ContractIndexElement = @@ -463,18 +549,29 @@ fn index_merkle_from_path( ), ) })?; - if let Some(h) = element.hash { - merkle.insert_with_int_pos( - position_from_contract(&contract_id), - element.int_pos.expect("int pos should be present"), - h, - ); + if element_depth != u32::MAX { + index.insert_contract_index(&contract_id, element); + } else { + commit_store + .lock() + .unwrap() + .insert_main_index(&contract_id, element); } - index.insert_contract_index(&contract_id, element); } } } + match maybe_tree_pos { + Some(tree_pos) => { + for (int_pos, (hash, pos)) in tree_pos.iter() { + merkle.insert_with_int_pos(*pos, *int_pos as u64, *hash); + } + } + None => { + unreachable!() + } + } + Ok((index, merkle)) } @@ -492,19 +589,49 @@ fn base_from_path>(path: P) -> io::Result { Ok(base_info) } +fn tree_pos_from_path( + path: impl AsRef, + opt_path: impl AsRef, +) -> io::Result { + let path = path.as_ref(); + + let tree_pos = if opt_path.as_ref().exists() { + let f = OpenOptions::new().read(true).open(opt_path.as_ref())?; + let mut buf_f = BufReader::new(f); + TreePos::unmarshall(&mut buf_f) + } else { + let tree_pos_bytes = fs::read(path)?; + rkyv::from_bytes(&tree_pos_bytes).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("Invalid tree positions file \"{path:?}\": {err}"), + ) + }) + }; + + tree_pos +} + #[derive(Debug, Clone)] pub(crate) struct Commit { index: NewContractIndex, contracts_merkle: ContractsMerkle, maybe_hash: Option, + commit_store: Option>>, + base: Option, } impl Commit { - pub fn new() -> Self { + pub fn new( + commit_store: &Arc>, + maybe_base: Option, + ) -> Self { Self { index: NewContractIndex::new(), contracts_merkle: ContractsMerkle::default(), maybe_hash: None, + commit_store: Some(commit_store.clone()), + base: maybe_base, } } @@ -515,7 +642,7 @@ impl Commit { ) -> Self { let mut index = NewContractIndex::new(); for contract_id in contract_ids { - if let Some(a) = self.index.get(contract_id, None) { + if let Some(a) = self.index.get(contract_id) { index.insert_contract_index(contract_id, a.clone()); } } @@ -523,13 +650,11 @@ impl Commit { index, contracts_merkle: self.contracts_merkle.clone(), maybe_hash: self.maybe_hash, + commit_store: self.commit_store.clone(), + base: self.base, } } - pub fn to_hulk(&self) -> CommitHulk { - CommitHulk::from_commit(self) - } - #[allow(dead_code)] pub fn inclusion_proofs( mut self, @@ -539,14 +664,14 @@ impl Commit { let pos = position_from_contract(contract_id); - Some(contract.page_indices.into_iter().map(move |page_index| { + let (iter, tree) = contract.page_indices_and_tree(); + Some(iter.map(move |page_index| { let tree_opening = self .contracts_merkle .opening(pos) .expect("There must be a leaf for the contract"); - let page_opening = contract - .tree + let page_opening = tree .opening(page_index as u64) .expect("There must be a leaf for the page"); @@ -561,34 +686,32 @@ impl Commit { } pub fn insert(&mut self, contract_id: ContractId, memory: &Memory) { - if self.index.get(&contract_id, self.maybe_hash).is_none() { + if self.index_get(&contract_id).is_none() { self.index.insert_contract_index( &contract_id, - ContractIndexElement { - tree: PageTree::new(memory.is_64()), - len: 0, - page_indices: BTreeSet::new(), - hash: None, - int_pos: None, - }, + ContractIndexElement::new(memory.is_64()), ); } - let element = - self.index.get_mut(&contract_id, self.maybe_hash).unwrap(); + let (element, contracts_merkle) = + self.element_and_merkle_mut(&contract_id); + let element = element.unwrap(); - element.len = memory.current_len; + element.set_len(memory.current_len); for (dirty_page, _, page_index) in memory.dirty_pages() { - element.page_indices.insert(*page_index); let hash = Hash::new(dirty_page); - element.tree.insert(*page_index as u64, hash); + element.insert_page_index_hash( + *page_index, + *page_index as u64, + hash, + ); } - let root = element.tree.root(); + let root = *element.tree().root(); let pos = position_from_contract(&contract_id); - let internal_pos = self.contracts_merkle.insert(pos, *root); - element.hash = Some(*root); - element.int_pos = Some(internal_pos); + let internal_pos = contracts_merkle.insert(pos, root); + element.set_hash(Some(root)); + element.set_int_pos(Some(internal_pos)); } pub fn remove_and_insert(&mut self, contract: ContractId, memory: &Memory) { @@ -602,6 +725,35 @@ impl Commit { tracing::trace!("calculating root finished"); ret } + + pub fn index_get( + &self, + contract_id: &ContractId, + ) -> Option<&ContractIndexElement> { + Hulk::deep_index_get( + &self.index, + *contract_id, + self.commit_store.clone(), + self.base, + ) + .map(|a| unsafe { &*a }) + } + + pub fn element_and_merkle_mut( + &mut self, + contract_id: &ContractId, + ) -> (Option<&mut ContractIndexElement>, &mut ContractsMerkle) { + ( + Hulk::deep_index_get_mut( + &mut self.index, + *contract_id, + self.commit_store.clone(), + self.base, + ) + .map(|a| unsafe { &mut *a }), + &mut self.contracts_merkle, + ) + } } pub(crate) enum Call { @@ -826,7 +978,8 @@ fn write_commit>( // maybe_hash: base.as_ref().and_then(|base| base.maybe_hash), // }; - let mut commit = base.unwrap_or(Commit::new()); + let mut commit = + base.unwrap_or(Commit::new(&commit_store, base_info.maybe_base)); for (contract_id, contract_data) in &commit_contracts { if contract_data.is_new { commit.remove_and_insert(*contract_id, &contract_data.memory); @@ -837,6 +990,8 @@ fn write_commit>( let root = *commit.root(); let root_hex = hex::encode(root); + commit.maybe_hash = Some(root); + commit.base = base_info.maybe_base; // Don't write the commit if it already exists on disk. This may happen if // the same transactions on the same base commit for example. @@ -935,8 +1090,6 @@ fn write_commit_inner, S: AsRef>( tracing::trace!("persisting index started"); for (contract_id, element) in commit.index.iter() { if commit_contracts.contains_key(contract_id) { - // todo: write element to disk at - // main/leaf/{contract_id}/{commit_id} let element_dir_path = directories .leaf_main_dir .join(hex::encode(contract_id.as_bytes())) @@ -956,7 +1109,7 @@ fn write_commit_inner, S: AsRef>( tracing::trace!("persisting index finished"); let base_main_path = - base_path_main(directories.main_dir, commit_id.as_ref())?; + base_path_main(&directories.main_dir, commit_id.as_ref())?; let base_info_bytes = rkyv::to_bytes::<_, 128>(&base_info).map_err(|err| { io::Error::new( @@ -966,6 +1119,16 @@ fn write_commit_inner, S: AsRef>( })?; fs::write(base_main_path, base_info_bytes)?; + let tree_pos_opt_path = + tree_pos_path_main(&directories.main_dir, commit_id.as_ref())?; + + let f = OpenOptions::new() + .append(true) + .create(true) + .open(tree_pos_opt_path)?; + let mut buf_f = BufWriter::new(f); + commit.contracts_merkle.tree_pos().marshall(&mut buf_f)?; + Ok(()) } @@ -1006,6 +1169,8 @@ fn finalize_commit>( let root = hex::encode(root); let commit_path = main_dir.join(&root); let base_info_path = commit_path.join(BASE_FILE); + let tree_pos_path = commit_path.join(TREE_POS_FILE); + let tree_pos_opt_path = commit_path.join(TREE_POS_OPT_FILE); let base_info = base_from_path(&base_info_path)?; for contract_hint in base_info.contract_hints { let contract_hex = hex::encode(contract_hint); @@ -1035,6 +1200,8 @@ fn finalize_commit>( } fs::remove_file(base_info_path)?; + let _ = fs::remove_file(tree_pos_path); + let _ = fs::remove_file(tree_pos_opt_path); fs::remove_dir(commit_path)?; Ok(()) diff --git a/piecrust/src/store/commit.rs b/piecrust/src/store/commit.rs index 6d6420cd..85e579af 100644 --- a/piecrust/src/store/commit.rs +++ b/piecrust/src/store/commit.rs @@ -4,192 +4,56 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use crate::store::tree::{ - position_from_contract, ContractIndexElement, ContractsMerkle, Hash, - NewContractIndex, PageTree, -}; -use crate::store::{Commit, Memory}; -use crate::PageOpening; +use crate::store::tree::{ContractIndexElement, Hash, NewContractIndex}; +use crate::store::CommitStore; use piecrust_uplink::ContractId; -use std::cell::Ref; -use std::collections::BTreeSet; +use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] -pub(crate) struct CommitHulk { - index: Option<*const NewContractIndex>, - index2: NewContractIndex, - contracts_merkle: ContractsMerkle, - maybe_hash: Option, -} - -impl CommitHulk { - pub fn from_commit(commit: &Commit) -> Self { - Self { - index: Some(&commit.index), - index2: NewContractIndex::new(), - contracts_merkle: commit.contracts_merkle.clone(), - maybe_hash: commit.maybe_hash, - } - } - - pub fn new() -> Self { - Self { - index: None, - index2: NewContractIndex::new(), - contracts_merkle: ContractsMerkle::default(), - maybe_hash: None, - } - } - - pub fn to_commit(&self) -> Commit { - let index = self.index.map(|p| unsafe { p.as_ref().unwrap() }); - match index { - Some(p) => Commit { - index: p.clone(), - contracts_merkle: self.contracts_merkle.clone(), - maybe_hash: self.maybe_hash, - }, - None => Commit { - index: NewContractIndex::new(), - contracts_merkle: self.contracts_merkle.clone(), - maybe_hash: self.maybe_hash, - }, - } - } - - pub fn fast_clone<'a>( - &self, - contract_ids: impl Iterator, - ) -> Self { - let mut index2 = NewContractIndex::new(); - for contract_id in contract_ids { - if let Some(a) = self.index_get(contract_id) { - index2.insert_contract_index(contract_id, a.clone()); +pub(crate) struct Hulk; + +impl Hulk { + pub fn deep_index_get( + index: &NewContractIndex, + contract_id: ContractId, + commit_store: Option>>, + base: Option, + ) -> Option<*const ContractIndexElement> { + if let Some(e) = index.get(&contract_id) { + return Some(e); + } + let mut base = base?; + let commit_store = commit_store.clone()?; + let commit_store = commit_store.lock().unwrap(); + loop { + let (maybe_element, commit_base) = + commit_store.get_element_and_base(&base, &contract_id); + if let Some(e) = maybe_element { + return Some(e); } - } - Self { - index: None, - index2, - contracts_merkle: self.contracts_merkle.clone(), - maybe_hash: self.maybe_hash, - } - } - - pub fn inclusion_proofs( - mut self, - contract_id: &ContractId, - ) -> Option> { - let contract = self.remove_contract_index(contract_id)?; - - let pos = position_from_contract(contract_id); - - Some(contract.page_indices.into_iter().map(move |page_index| { - let tree_opening = self - .contracts_merkle - .opening(pos) - .expect("There must be a leaf for the contract"); - - let page_opening = contract - .tree - .opening(page_index as u64) - .expect("There must be a leaf for the page"); - - ( - page_index, - PageOpening { - tree: tree_opening, - inner: page_opening, - }, - ) - })) - } - - pub fn insert(&mut self, contract_id: ContractId, memory: &Memory) { - if self.index_get(&contract_id).is_none() { - self.insert_contract_index( - &contract_id, - ContractIndexElement { - tree: PageTree::new(memory.is_64()), - len: 0, - page_indices: BTreeSet::new(), - hash: None, - int_pos: None, - }, - ); - } - let (index, contracts_merkle) = self.get_mutables(); - let element = index.get_mut(&contract_id, None).unwrap(); - - element.len = memory.current_len; - - for (dirty_page, _, page_index) in memory.dirty_pages() { - element.page_indices.insert(*page_index); - let hash = Hash::new(dirty_page); - element.tree.insert(*page_index as u64, hash); - } - - let root = element.tree.root(); - let pos = position_from_contract(&contract_id); - let int_pos = contracts_merkle.insert(pos, *root); - element.hash = Some(*root); - element.int_pos = Some(int_pos); - } - - // to satisfy borrow checker - fn get_mutables( - &mut self, - ) -> (&mut NewContractIndex, &mut ContractsMerkle) { - (&mut self.index2, &mut self.contracts_merkle) - } - - pub fn root(&self) -> Ref { - tracing::trace!("calculating root started"); - let ret = self.contracts_merkle.root(); - tracing::trace!("calculating root finished"); - ret - } - - /* - index accessors - */ - - pub fn remove_contract_index( - &mut self, - contract_id: &ContractId, - ) -> Option { - self.index2.contracts_mut().remove(contract_id) - } - - pub fn insert_contract_index( - &mut self, - contract_id: &ContractId, - element: ContractIndexElement, - ) { - self.index2.contracts_mut().insert(*contract_id, element); - } - - pub fn index_get( - &self, - contract_id: &ContractId, - ) -> Option<&ContractIndexElement> { - let index = self.index.map(|p| unsafe { p.as_ref().unwrap() }); - match index { - Some(p) => self - .index2 - .get(contract_id, self.maybe_hash) - .or_else(move || p.get(contract_id, self.maybe_hash)), - None => self.index2.get(contract_id, self.maybe_hash), - } - } - - pub fn index_contains_key(&self, contract_id: &ContractId) -> bool { - let index = self.index.map(|p| unsafe { p.as_ref().unwrap() }); - match index { - Some(p) => { - self.index2.contains_key(contract_id) - || p.contains_key(contract_id) + base = commit_base?; + } + } + + pub fn deep_index_get_mut( + index: &mut NewContractIndex, + contract_id: ContractId, + commit_store: Option>>, + base: Option, + ) -> Option<*mut ContractIndexElement> { + if let Some(e) = index.get_mut(&contract_id) { + return Some(e); + } + let mut base = base?; + let commit_store = commit_store.clone()?; + let mut commit_store = commit_store.lock().unwrap(); + loop { + let (maybe_element, commit_base) = + commit_store.get_element_and_base_mut(&base, &contract_id); + if let Some(e) = maybe_element { + return Some(e); } - None => self.index2.contains_key(contract_id), + base = commit_base?; } } } diff --git a/piecrust/src/store/session.rs b/piecrust/src/store/session.rs index 083726f0..a50a8892 100644 --- a/piecrust/src/store/session.rs +++ b/piecrust/src/store/session.rs @@ -8,19 +8,18 @@ use std::collections::btree_map::Entry::{Occupied, Vacant}; use std::collections::BTreeMap; use std::fmt::Debug; use std::path::{Path, PathBuf}; -use std::sync::{mpsc, Arc}; +use std::sync::{mpsc, Arc, Mutex}; use std::{io, mem}; use dusk_wasmtime::Engine; use piecrust_uplink::ContractId; use crate::contract::ContractMetadata; -use crate::store::commit::CommitHulk; use crate::store::tree::{Hash, PageOpening}; use crate::store::{ - base_from_path, Bytecode, Call, Memory, Metadata, Module, BASE_FILE, - BYTECODE_DIR, ELEMENT_FILE, MAIN_DIR, MEMORY_DIR, METADATA_EXTENSION, - OBJECTCODE_EXTENSION, PAGE_SIZE, + base_from_path, Bytecode, Call, Commit, CommitStore, Memory, Metadata, + Module, BASE_FILE, BYTECODE_DIR, ELEMENT_FILE, MAIN_DIR, MEMORY_DIR, + METADATA_EXTENSION, OBJECTCODE_EXTENSION, PAGE_SIZE, }; use crate::Error; @@ -46,10 +45,12 @@ pub struct ContractSession { contracts: BTreeMap, engine: Engine, - base: Option, + base: Option, root_dir: PathBuf, call: mpsc::Sender, + + commit_store: Arc>, } impl Debug for ContractSession { @@ -66,8 +67,9 @@ impl ContractSession { pub(crate) fn new>( root_dir: P, engine: Engine, - base: Option, + base: Option, call: mpsc::Sender, + commit_store: Arc>, ) -> Self { Self { contracts: BTreeMap::new(), @@ -75,6 +77,7 @@ impl ContractSession { base, root_dir: root_dir.as_ref().into(), call, + commit_store, } } @@ -93,7 +96,7 @@ impl ContractSession { .base .as_ref() .map(|c| c.fast_clone(&mut self.contracts.keys())) - .unwrap_or(CommitHulk::new()); + .unwrap_or(Commit::new(&self.commit_store, None)); for (contract, entry) in &self.contracts { commit.insert(*contract, &entry.memory); } @@ -110,7 +113,10 @@ impl ContractSession { contract: ContractId, ) -> Option> { tracing::trace!("memory_pages called commit cloning"); - let mut commit = self.base.clone().unwrap_or(CommitHulk::new()); + let mut commit = self + .base + .clone() + .unwrap_or(Commit::new(&self.commit_store, None)); for (contract, entry) in &self.contracts { commit.insert(*contract, &entry.memory); } @@ -146,7 +152,7 @@ impl ContractSession { let (replier, receiver) = mpsc::sync_channel(1); let mut contracts = BTreeMap::new(); - let base = self.base.as_ref().map(|c| c.to_commit()); + let base = self.base.clone(); mem::swap(&mut self.contracts, &mut contracts); @@ -207,20 +213,33 @@ impl ContractSession { commit: Option, leaf_path: impl AsRef, main_path: impl AsRef, - ) -> Option { + depth: u32, + ) -> Option<(PathBuf, u32)> { match commit { - None => None, + None => { + let path = leaf_path.as_ref().join(ELEMENT_FILE); + if path.is_file() { + Some((path, u32::MAX)) + } else { + None + } + } Some(hash) => { let hash_hex = hex::encode(hash.as_bytes()); let path = leaf_path.as_ref().join(&hash_hex).join(ELEMENT_FILE); if path.is_file() { - Some(path) + Some((path, depth + 1)) } else { let base_info_path = main_path.as_ref().join(hash_hex).join(BASE_FILE); let index = base_from_path(base_info_path).ok()?; - Self::find_element(index.maybe_base, leaf_path, main_path) + Self::find_element( + index.maybe_base, + leaf_path, + main_path, + depth + 1, + ) } } } @@ -245,7 +264,7 @@ impl ContractSession { Vacant(entry) => match &self.base { None => Ok(None), Some(base_commit) => { - match base_commit.index_contains_key(&contract) { + match base_commit.index_get(&contract).is_some() { true => { let base_dir = self.root_dir.join(MAIN_DIR); @@ -269,7 +288,7 @@ impl ContractSession { { Some(elem) => { let page_indices = - elem.page_indices.clone(); + elem.page_indices().clone(); Memory::from_files( module.is_64(), move |page_index: usize| { @@ -294,7 +313,7 @@ impl ContractSession { false => None, } }, - elem.len, + elem.len(), )? } None => Memory::new(module.is_64())?, @@ -330,7 +349,7 @@ impl ContractSession { if self.contracts.contains_key(&contract_id) { true } else if let Some(base_commit) = &self.base { - base_commit.index_contains_key(&contract_id) + base_commit.index_get(&contract_id).is_some() } else { false } @@ -357,7 +376,7 @@ impl ContractSession { // If the position is already filled in the tree, the contract cannot be // inserted. if let Some(base) = self.base.as_ref() { - if base.index_contains_key(&contract_id) { + if base.index_get(&contract_id).is_some() { return Err(io::Error::new( io::ErrorKind::Other, format!("Existing contract '{contract_id}'"), diff --git a/piecrust/src/store/tree.rs b/piecrust/src/store/tree.rs index 69a4dd6a..418e4d84 100644 --- a/piecrust/src/store/tree.rs +++ b/piecrust/src/store/tree.rs @@ -12,6 +12,7 @@ use std::{ use bytecheck::CheckBytes; use piecrust_uplink::ContractId; use rkyv::{Archive, Deserialize, Serialize}; +use std::io::{self, ErrorKind, Read, Write}; // There are max `2^16` pages in a 32-bit memory const P32_HEIGHT: usize = 8; @@ -97,6 +98,7 @@ impl NewContractIndex { pub struct ContractsMerkle { inner_tree: Tree, dict: BTreeMap, + tree_pos: TreePos, } impl Default for ContractsMerkle { @@ -104,6 +106,7 @@ impl Default for ContractsMerkle { Self { inner_tree: Tree::new(), dict: BTreeMap::new(), + tree_pos: TreePos::default(), } } } @@ -119,12 +122,14 @@ impl ContractsMerkle { Some(p) => *p, }; self.inner_tree.insert(new_pos, hash); + self.tree_pos.insert(new_pos as u32, (hash, pos)); new_pos } pub fn insert_with_int_pos(&mut self, pos: u64, int_pos: u64, hash: Hash) { self.dict.insert(pos, int_pos); self.inner_tree.insert(int_pos, hash); + self.tree_pos.insert(int_pos as u32, (hash, pos)); } pub fn opening(&self, pos: u64) -> Option { @@ -135,6 +140,14 @@ impl ContractsMerkle { pub fn root(&self) -> Ref { self.inner_tree.root() } + + pub fn tree_pos(&self) -> &TreePos { + &self.tree_pos + } + + pub fn len(&self) -> u64 { + self.inner_tree.len() + } } #[derive(Debug, Clone, Archive, Deserialize, Serialize)] @@ -153,14 +166,152 @@ pub struct BaseInfo { pub maybe_base: Option, } +#[derive(Debug, Clone, Default, Archive, Deserialize, Serialize)] +#[archive_attr(derive(CheckBytes))] +pub struct TreePos { + tree_pos: BTreeMap, +} + +impl TreePos { + pub fn insert(&mut self, k: u32, v: (Hash, u64)) { + self.tree_pos.insert(k, v); + } + + pub fn marshall(&self, w: &mut W) -> io::Result<()> { + const CHUNK_SIZE: usize = 8192; + const ELEM_SIZE: usize = 4 + 32 + 4; + let mut b = [0u8; ELEM_SIZE * CHUNK_SIZE]; + let mut chk = 0; + for (k, (h, p)) in self.tree_pos.iter() { + let offset = chk * ELEM_SIZE; + b[offset..(offset + 4)].copy_from_slice(&(*k).to_le_bytes()); + b[(offset + 4)..(offset + 36)].copy_from_slice(h.as_bytes()); + b[(offset + 36)..(offset + 40)] + .copy_from_slice(&(*p as u32).to_le_bytes()); + chk = (chk + 1) % CHUNK_SIZE; + if chk == 0 { + w.write_all(b.as_slice())?; + } + } + if chk != 0 { + w.write_all(&b[..(chk * ELEM_SIZE)])?; + } + Ok(()) + } + + fn read_bytes(r: &mut R) -> io::Result<[u8; N]> { + let mut buffer = [0u8; N]; + r.read_exact(&mut buffer)?; + Ok(buffer) + } + + fn is_eof(r: &io::Result) -> bool { + if let Err(ref e) = r { + if e.kind() == ErrorKind::UnexpectedEof { + return true; + } + } + false + } + + pub fn unmarshall(r: &mut R) -> io::Result { + let mut slf = Self::default(); + loop { + let res = Self::read_bytes(r); + if Self::is_eof(&res) { + break; + } + let k = u32::from_le_bytes(res?); + + let res = Self::read_bytes(r); + if Self::is_eof(&res) { + break; + } + let hash = Hash::from(res?); + + let res = Self::read_bytes(r); + if Self::is_eof(&res) { + break; + } + let p = u32::from_le_bytes(res?); + slf.tree_pos.insert(k, (hash, p as u64)); + } + Ok(slf) + } + + pub fn iter(&self) -> impl Iterator { + self.tree_pos.iter() + } +} + #[derive(Debug, Clone, Archive, Deserialize, Serialize)] #[archive_attr(derive(CheckBytes))] pub struct ContractIndexElement { - pub tree: PageTree, - pub len: usize, - pub page_indices: BTreeSet, - pub hash: Option, - pub int_pos: Option, + tree: PageTree, + len: usize, + page_indices: BTreeSet, + hash: Option, + int_pos: Option, +} + +impl ContractIndexElement { + pub fn new(is_64: bool) -> Self { + Self { + tree: PageTree::new(is_64), + len: 0, + page_indices: BTreeSet::new(), + hash: None, + int_pos: None, + } + } + + pub fn page_indices_and_tree( + self, + ) -> (impl Iterator, PageTree) { + (self.page_indices.into_iter(), self.tree) + } + + pub fn page_indices(&self) -> &BTreeSet { + &self.page_indices + } + + pub fn set_len(&mut self, len: usize) { + self.len = len; + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn set_hash(&mut self, hash: Option) { + self.hash = hash; + } + + pub fn hash(&self) -> Option { + self.hash + } + + pub fn set_int_pos(&mut self, int_pos: Option) { + self.int_pos = int_pos; + } + + pub fn int_pos(&self) -> Option { + self.int_pos + } + + pub fn tree(&self) -> &PageTree { + &self.tree + } + + pub fn insert_page_index_hash( + &mut self, + page_index: usize, + page_index_u64: u64, + page_hash: impl Into, + ) { + self.page_indices.insert(page_index); + self.tree.insert(page_index_u64, page_hash); + } } impl Default for NewContractIndex { @@ -191,18 +342,13 @@ impl NewContractIndex { self.inner_contracts.insert(*contract_id, element); } - pub fn get( - &self, - contract: &ContractId, - _maybe_commit_id: Option, - ) -> Option<&ContractIndexElement> { + pub fn get(&self, contract: &ContractId) -> Option<&ContractIndexElement> { self.inner_contracts.get(contract) } pub fn get_mut( &mut self, contract: &ContractId, - _maybe_commit_id: Option, ) -> Option<&mut ContractIndexElement> { self.inner_contracts.get_mut(contract) } @@ -216,6 +362,12 @@ impl NewContractIndex { ) -> impl Iterator { self.inner_contracts.iter() } + + pub fn move_into(self, target: &mut Self) { + for (contract_id, element) in self.inner_contracts.into_iter() { + target.insert_contract_index(&contract_id, element); + } + } } type Wasm32PageOpening = dusk_merkle::Opening; @@ -399,3 +551,29 @@ pub fn position_from_contract(contract: &ContractId) -> u64 { pos as u64 } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{BufReader, BufWriter}; + + #[test] + fn merkle_position_serialization() -> Result<(), io::Error> { + const TEST_SIZE: u32 = 262144; + const ELEM_SIZE: usize = 4 + 32 + 4; + let mut marshalled = TreePos::default(); + let h = Hash::from([1u8; 32]); + for i in 0..TEST_SIZE { + marshalled.insert(i, (h, i as u64)); + } + let v: Vec = Vec::new(); + let mut w = BufWriter::with_capacity(TEST_SIZE as usize * ELEM_SIZE, v); + marshalled.marshall(&mut w)?; + let mut r = BufReader::new(w.buffer()); + let unmarshalled = TreePos::unmarshall(&mut r)?; + for i in 0..TEST_SIZE { + assert_eq!(unmarshalled.tree_pos.get(&i), Some(&(h, i as u64))); + } + Ok(()) + } +} diff --git a/piecrust/tests/callcenter.rs b/piecrust/tests/callcenter.rs index 5d8545b8..e9fcb21f 100644 --- a/piecrust/tests/callcenter.rs +++ b/piecrust/tests/callcenter.rs @@ -242,6 +242,66 @@ pub fn cc_caller_uninit() -> Result<(), Error> { Ok(()) } +#[test] +pub fn cc_callstack() -> Result<(), Error> { + let vm = VM::ephemeral()?; + + let mut session = vm.session(SessionData::builder())?; + + let center_id = session.deploy( + contract_bytecode!("callcenter"), + ContractData::builder().owner(OWNER), + LIMIT, + )?; + + let callstack_id = session.deploy( + contract_bytecode!("callstack"), + ContractData::builder().owner(OWNER), + LIMIT, + )?; + + let callstack: Vec = session + .call(center_id, "return_callstack", &(), LIMIT)? + .data; + assert_eq!(callstack.len(), 1); + + let self_id: ContractId = + session.call(center_id, "return_self_id", &(), LIMIT)?.data; + assert_eq!(callstack[0], self_id); + + const N: u32 = 5; + let callstack: Vec = session + .call(center_id, "call_self_n_times", &N, LIMIT)? + .data; + assert_eq!(callstack.len(), N as usize + 1); + for i in 1..=N as usize { + assert_eq!(callstack[0], callstack[i]); + } + + let res = session + .call::<_, Result, ContractError>>( + center_id, + "delegate_query", + &( + callstack_id, + String::from("return_callstack"), + Vec::::new(), + ), + LIMIT, + )? + .data + .expect("ICC should succeed"); + + let callstack: Vec = + rkyv::from_bytes(&res).expect("Deserialization to succeed"); + + assert_eq!(callstack.len(), 2); + assert_eq!(callstack[0], callstack_id); + assert_eq!(callstack[1], center_id); + + Ok(()) +} + #[test] pub fn cc_self_id() -> Result<(), Error> { let vm = VM::ephemeral()?; diff --git a/piecrust/tests/counter_float.rs b/piecrust/tests/counter_float.rs new file mode 100644 index 00000000..cc6dd85d --- /dev/null +++ b/piecrust/tests/counter_float.rs @@ -0,0 +1,30 @@ +// 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 piecrust::{contract_bytecode, ContractData, Error, SessionData, VM}; + +const OWNER: [u8; 32] = [0u8; 32]; +const LIMIT: u64 = 1_000_000; + +#[test] +fn counter_float_deployment() -> Result<(), Error> { + let vm = VM::ephemeral()?; + + let mut session = vm.session(SessionData::builder())?; + + let id = session.deploy( + contract_bytecode!("counter_float"), + ContractData::builder().owner(OWNER), + LIMIT, + )?; + + assert_eq!( + session.call::<_, f64>(id, "read_value", &(), LIMIT)?.data, + 0xfc as f64 + ); + + Ok(()) +} diff --git a/piecrust/tests/crossover.rs b/piecrust/tests/crossover.rs index 97a947c1..ab475886 100644 --- a/piecrust/tests/crossover.rs +++ b/piecrust/tests/crossover.rs @@ -70,3 +70,42 @@ fn crossover() -> Result<(), Error> { Ok(()) } + +#[test] +fn iccs_dont_rollback() -> Result<(), Error> { + let vm = VM::ephemeral()?; + + let mut session = vm.session(SessionData::builder())?; + + session.deploy( + contract_bytecode!("crossover"), + ContractData::builder() + .owner(OWNER) + .contract_id(CROSSOVER_ONE), + LIMIT, + )?; + session.deploy( + contract_bytecode!("crossover"), + ContractData::builder() + .owner(OWNER) + .contract_id(CROSSOVER_TWO), + LIMIT, + )?; + // These value should not be set to `INITIAL_VALUE` in the contract. + const CROSSOVER_TO_SET: i32 = 42; + + session.call::<_, ()>( + CROSSOVER_ONE, + "check_iccs_dont_rollback", + &(CROSSOVER_TWO, CROSSOVER_TO_SET), + LIMIT, + )?; + + assert_eq!( + session.call::<_, i32>(CROSSOVER_ONE, "crossover", &(), LIMIT)?.data, + CROSSOVER_TO_SET, + "The crossover should still be set even though the other contract panicked" + ); + + Ok(()) +}