diff --git a/bin/darkfid/genesis_block_localnet b/bin/darkfid/genesis_block_localnet index bd946ab99..bd41a01bc 100644 --- a/bin/darkfid/genesis_block_localnet +++ b/bin/darkfid/genesis_block_localnet @@ -1 +1 @@ -AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAOjwIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAGALfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/bin/darkfid/genesis_block_mainnet b/bin/darkfid/genesis_block_mainnet index 7f248cf58..7455ebddc 100644 --- a/bin/darkfid/genesis_block_mainnet +++ b/bin/darkfid/genesis_block_mainnet @@ -1 +1 @@ -AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAE9GG2gAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCMPjhlzo1AEcT03pj4ZfNZRxh9AwSl4slZFTeM+H5fyGAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAHQLfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/bin/darkfid/genesis_block_testnet b/bin/darkfid/genesis_block_testnet index 25fd84537..7ca618ec9 100644 --- a/bin/darkfid/genesis_block_testnet +++ b/bin/darkfid/genesis_block_testnet @@ -1 +1 @@ -AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAO/wIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAG0LfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/doc/src/arch/consensus.md b/doc/src/arch/consensus.md index 80c07e32c..cd2a39684 100644 --- a/doc/src/arch/consensus.md +++ b/doc/src/arch/consensus.md @@ -301,13 +301,13 @@ comparison the tx in Bitcoin with the most outputs has 2501. ## Contracts states Monotree(SMT) -DarkFi is using an optimized Sparse Merkle Tree implementation, -called Monotree. -This tree is constructed using all contracts states trees checksums, -along with the wasm bincodes tree checksum, excluding native contracts -zkas tree and wasm bincodes. -The checksum of each tree is computed by hashing all its key and values, -using a `blake3` hasher. +DarkFi is using an optimized Sparse Merkle Tree implementation, called +Monotree. Each contract has its own Monotree, containing all its +database trees hashed records. Additionally, a global tree exists +reflecting the total database state. The global tree is constructed +using all contracts states monotrees roots, along with the wasm +bincodes monotree root, excluding native contracts zkas tree and wasm +bincodes. For each block, we compute the current tree root and keep it in its header, enabling us to both verify the contacts state after the block insertion, and create proofs commiting to that specific state. @@ -317,7 +317,7 @@ and create proofs commiting to that specific state. In this pseudocode we can see how we can use the Monotree to produce a proof for a specific state. First, we will create a random set of key-value `blake3` hash pairs, representing our contract states -checksums: +roots: ``` keys = random_hashes(100); diff --git a/src/blockchain/contract_store.rs b/src/blockchain/contract_store.rs index 582fcf8a3..78fdf85ce 100644 --- a/src/blockchain/contract_store.rs +++ b/src/blockchain/contract_store.rs @@ -21,13 +21,13 @@ use std::{collections::BTreeMap, io::Cursor}; use darkfi_sdk::{ crypto::contract_id::{ ContractId, NATIVE_CONTRACT_IDS_BYTES, NATIVE_CONTRACT_ZKAS_DB_NAMES, - SMART_CONTRACT_ZKAS_DB_NAME, + SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME, }, - monotree::{self, Monotree}, + monotree::{MemoryDb, Monotree, SledOverlayDb, SledTreeDb, EMPTY_HASH}, }; use darkfi_serial::{deserialize, serialize}; -use sled_overlay::{serial::parse_record, sled, SledDbOverlay}; use tracing::{debug, error}; +use sled_overlay::{serial::parse_record, sled}; use crate::{ zk::{empty_witnesses, VerifyingKey, ZkCircuit}, @@ -38,6 +38,7 @@ use crate::{ use super::SledDbOverlayPtr; pub const SLED_CONTRACTS_TREE: &[u8] = b"_contracts"; +pub const SLED_CONTRACTS_TREES_TREE: &[u8] = b"_contracts_trees"; pub const SLED_BINCODE_TREE: &[u8] = b"_wasm_bincode"; /// The `ContractStore` is a structure representing all `sled` trees related @@ -61,6 +62,17 @@ pub struct ContractStore { /// ``` /// These values get mutated with `init()` and `remove()`. pub state: sled::Tree, + /// The `sled` tree storing the inverse pointers to contracts' + /// databases. See the rustdoc for the impl functions for more + /// info. + /// The layout looks like this: + /// ```plaintext + /// tree: "_contracts_trees" + /// key: blake3(ContractId || tree_name) + /// value: ContractId + /// ``` + /// These values get mutated with `init()` and `remove()`. + pub state_trees: sled::Tree, } impl ContractStore { @@ -68,7 +80,8 @@ impl ContractStore { pub fn new(db: &sled::Db) -> Result { let wasm = db.open_tree(SLED_BINCODE_TREE)?; let state = db.open_tree(SLED_CONTRACTS_TREE)?; - Ok(Self { wasm, state }) + let state_trees = db.open_tree(SLED_CONTRACTS_TREES_TREE)?; + Ok(Self { wasm, state, state_trees }) } /// Fetches the bincode for a given ContractId from the store's wasm tree. @@ -139,10 +152,14 @@ impl ContractStore { if !state_pointers.contains(&ptr) { return Err(Error::ContractStateNotFound) } + if !self.state_trees.contains_key(ptr)? { + return Err(Error::ContractStateNotFound) + } // Remove the deleted tree from the state pointer set. state_pointers.retain(|x| *x != ptr); self.state.insert(contract_id_bytes, serialize(&state_pointers))?; + self.state_trees.remove(ptr)?; // Drop the deleted tree from the database db.drop_tree(ptr)?; @@ -257,40 +274,47 @@ impl ContractStore { } /// Generate a Monotree(SMT) containing all contracts states - /// checksums, along with the wasm bincodes checksum. + /// roots, along with the wasm bincodes monotree root. /// /// Note: native contracts zkas tree and wasm bincodes are excluded. - pub fn get_state_monotree(&self, db: &sled::Db) -> Result> { + pub fn get_state_monotree(&self, db: &sled::Db) -> Result> { // Initialize the monotree let mut root = None; - let monotree_db = monotree::MemoryDb::new(); + let monotree_db = MemoryDb::new(); let mut tree = Monotree::new(monotree_db); // Iterate over current contracts states records - // TODO: parallelize this with a threadpool - for state_record in self.state.iter().values() { - // Iterate over contract states pointers - let state_pointers: Vec<[u8; 32]> = deserialize(&state_record?)?; - for state_ptr in state_pointers { - // Skip native zkas tree - if NATIVE_CONTRACT_ZKAS_DB_NAMES.contains(&state_ptr) { - continue - } + for state_record in self.state.iter() { + // Grab its monotree pointer + let (contract_id, state_pointers): (ContractId, Vec<[u8; 32]>) = + parse_record(state_record?)?; + let state_monotree_ptr = contract_id.hash_state_id(SMART_CONTRACT_MONOTREE_DB_NAME); - // Grab the state tree - let state_tree = db.open_tree(state_ptr)?; - - // Compute its checksum - let checksum = sled_tree_checksum(&state_tree)?; - - // Insert record to monotree - root = tree.insert(root.as_ref(), &state_ptr, &checksum)?; - tree.set_headroot(root.as_ref()); + // Check it exists + if !state_pointers.contains(&state_monotree_ptr) { + return Err(Error::ContractStateNotFound) } + if !self.state_trees.contains_key(&state_monotree_ptr)? { + return Err(Error::ContractStateNotFound) + } + + // Grab its monotree + let state_tree = db.open_tree(state_monotree_ptr)?; + let state_monotree_db = SledTreeDb::new(&state_tree); + let state_monotree = Monotree::new(state_monotree_db); + + // Insert its root to the global monotree + let state_monotree_root = match state_monotree.get_headroot()? { + Some(hash) => hash, + None => *EMPTY_HASH, + }; + root = tree.insert(root.as_ref(), &contract_id.to_bytes(), &state_monotree_root)?; } - // Iterate over current contracts wasm bincodes to compute its checksum - let mut hasher = blake3::Hasher::new(); + // Iterate over current contracts wasm bincodes to compute its monotree root + let mut wasm_monotree_root = None; + let wasm_monotree_db = MemoryDb::new(); + let mut wasm_monotree = Monotree::new(wasm_monotree_db); for record in self.wasm.iter() { let (key, value) = record?; @@ -299,16 +323,23 @@ impl ContractStore { continue } - // Hash record - hasher.update(&key); - hasher.update(&value); + // Insert record + wasm_monotree_root = wasm_monotree.insert( + wasm_monotree_root.as_ref(), + blake3::hash(&key).as_bytes(), + blake3::hash(&value).as_bytes(), + )?; } - // Insert wasm bincodes record to monotree + // Insert wasm bincodes root to the global monotree + let wasm_monotree_root = match wasm_monotree_root { + Some(hash) => hash, + None => *EMPTY_HASH, + }; root = tree.insert( root.as_ref(), blake3::hash(SLED_BINCODE_TREE).as_bytes(), - hasher.finalize().as_bytes(), + &wasm_monotree_root, )?; tree.set_headroot(root.as_ref()); @@ -323,6 +354,7 @@ impl ContractStoreOverlay { pub fn new(overlay: &SledDbOverlayPtr) -> Result { overlay.lock().unwrap().open_tree(SLED_BINCODE_TREE, true)?; overlay.lock().unwrap().open_tree(SLED_CONTRACTS_TREE, true)?; + overlay.lock().unwrap().open_tree(SLED_CONTRACTS_TREES_TREE, true)?; Ok(Self(overlay.clone())) } @@ -385,6 +417,7 @@ impl ContractStoreOverlay { // Now we add it so it's marked as initialized and create its tree. state_pointers.push(ptr); lock.insert(SLED_CONTRACTS_TREE, &contract_id_bytes, &serialize(&state_pointers))?; + lock.insert(SLED_CONTRACTS_TREES_TREE, &ptr, &contract_id_bytes)?; lock.open_tree(&ptr, false)?; Ok(ptr) @@ -413,6 +446,9 @@ impl ContractStoreOverlay { if !state_pointers.contains(&ptr) { return Err(Error::ContractStateNotFound) } + if !lock.contains_key(SLED_CONTRACTS_TREES_TREE, &ptr)? { + return Err(Error::ContractStateNotFound) + } // We open the tree and return its handle lock.open_tree(&ptr, false)?; @@ -452,47 +488,55 @@ impl ContractStoreOverlay { } /// Generate a Monotree(SMT) containing all contracts states - /// checksums, along with the wasm bincodes checksum. - /// Be carefull as this will open all states trees in the overlay. + /// roots, along with the wasm bincodes monotree roots. + /// Be carefull as this will open all states monotrees in the overlay. /// /// Note: native contracts zkas tree and wasm bincodes are excluded. - pub fn get_state_monotree(&self) -> Result> { - let mut lock = self.0.lock().unwrap(); + pub fn get_state_monotree(&self) -> Result> { + let lock = self.0.lock().unwrap(); - // Grab all states pointers - let mut states_pointers = vec![]; + // Grab all states monotrees pointers + let mut states_monotrees_pointers = vec![]; for state_record in lock.iter(SLED_CONTRACTS_TREE)? { - let state_pointers: Vec<[u8; 32]> = deserialize(&state_record?.1)?; - for state_ptr in state_pointers { - // Skip native zkas tree - if NATIVE_CONTRACT_ZKAS_DB_NAMES.contains(&state_ptr) { - continue - } - states_pointers.push(state_ptr); + // Grab its monotree pointer + let (contract_id, state_pointers): (ContractId, Vec<[u8; 32]>) = + parse_record(state_record?)?; + let state_monotree_ptr = contract_id.hash_state_id(SMART_CONTRACT_MONOTREE_DB_NAME); + + // Check it exists + if !state_pointers.contains(&state_monotree_ptr) { + return Err(Error::ContractStateNotFound) } + if !lock.contains_key(SLED_CONTRACTS_TREES_TREE, &state_monotree_ptr)? { + return Err(Error::ContractStateNotFound) + } + + states_monotrees_pointers.push((contract_id, state_monotree_ptr)); } // Initialize the monotree let mut root = None; - let monotree_db = monotree::MemoryDb::new(); + let monotree_db = MemoryDb::new(); let mut tree = Monotree::new(monotree_db); - // Iterate over contract states pointers - // TODO: parallelize this with a threadpool - for state_ptr in states_pointers { - // Open the state tree in the overlay - lock.open_tree(&state_ptr, false)?; + // Iterate over contract states monotrees pointers + for (contract_id, state_monotree_ptr) in states_monotrees_pointers { + // Grab its monotree + let state_monotree_db = SledOverlayDb::new(&lock, &state_monotree_ptr)?; + let state_monotree = Monotree::new(state_monotree_db); - // Compute its checksum - let checksum = sled_overlay_tree_checksum(&lock, &state_ptr)?; - - // Insert record to monotree - root = tree.insert(root.as_ref(), &state_ptr, &checksum)?; - tree.set_headroot(root.as_ref()); + // Insert its root to the global monotree + let state_monotree_root = match state_monotree.get_headroot()? { + Some(hash) => hash, + None => *EMPTY_HASH, + }; + root = tree.insert(root.as_ref(), &contract_id.to_bytes(), &state_monotree_root)?; } - // Iterate over current contracts wasm bincodes to compute its checksum - let mut hasher = blake3::Hasher::new(); + // Iterate over current contracts wasm bincodes to compute its monotree root + let mut wasm_monotree_root = None; + let wasm_monotree_db = MemoryDb::new(); + let mut wasm_monotree = Monotree::new(wasm_monotree_db); for record in lock.iter(SLED_BINCODE_TREE)? { let (key, value) = record?; @@ -501,32 +545,38 @@ impl ContractStoreOverlay { continue } - // Hash record - hasher.update(&key); - hasher.update(&value); + // Insert record + wasm_monotree_root = wasm_monotree.insert( + wasm_monotree_root.as_ref(), + blake3::hash(&key).as_bytes(), + blake3::hash(&value).as_bytes(), + )?; } - // Insert wasm bincodes record to monotree + // Insert wasm bincodes root to the global monotree + let wasm_monotree_root = match wasm_monotree_root { + Some(hash) => hash, + None => *EMPTY_HASH, + }; root = tree.insert( root.as_ref(), blake3::hash(SLED_BINCODE_TREE).as_bytes(), - hasher.finalize().as_bytes(), + &wasm_monotree_root, )?; tree.set_headroot(root.as_ref()); Ok(tree) } - /// Compute all updated contracts states and wasm bincodes - /// checksums and update their records in the provided + /// Retrieve all updated contracts states and wasm bincodes + /// monotrees roots and update their records in the provided /// Monotree(SMT). /// /// Note: native contracts zkas tree and wasm bincodes are excluded. - pub fn update_state_monotree(&self, tree: &mut Monotree) -> Result<()> { + pub fn update_state_monotree(&self, tree: &mut Monotree) -> Result<()> { let lock = self.0.lock().unwrap(); // Iterate over overlay's caches - // TODO: parallelize this with a threadpool let mut root = tree.get_headroot()?; for (state_key, state_cache) in &lock.state.caches { // Check if that cache is a contract state one. @@ -539,13 +589,48 @@ impl ContractStoreOverlay { continue } - // Compute its checksum - let checksum = sled_overlay_tree_checksum(&lock, &state_key)?; + // Grab its contract id + let Some(record) = lock.get(SLED_CONTRACTS_TREES_TREE, &state_key)? else { + return Err(Error::ContractStateNotFound) + }; + let contract_id: ContractId = deserialize(&record)?; + debug!(target: "blockchain::contractstore::update_state_monotree", "Updating monotree for contract: {contract_id}"); - // Insert record to monotree - root = tree.insert(root.as_ref(), &state_key, &checksum)?; + // Grab its monotree + let state_monotree_ptr = contract_id.hash_state_id(SMART_CONTRACT_MONOTREE_DB_NAME); + let state_monotree_db = SledOverlayDb::new(&lock, &state_monotree_ptr)?; + let mut state_monotree = Monotree::new(state_monotree_db); + let mut state_monotree_root = state_monotree.get_headroot()?; + + // Remove dropped records + for key in &state_cache.state.removed { + let key = blake3::hash(&key); + debug!(target: "blockchain::contractstore::update_state_monotree", "Removed key: {key}"); + state_monotree_root = + state_monotree.remove(state_monotree_root.as_ref(), key.as_bytes())?; + } + + // Update or insert new records + for (key, value) in &state_cache.state.cache { + let key = blake3::hash(&key); + let value = blake3::hash(&value); + debug!(target: "blockchain::contractstore::update_state_monotree", "Updating key {key} with value: {value}"); + state_monotree_root = state_monotree.insert( + state_monotree_root.as_ref(), + key.as_bytes(), + value.as_bytes(), + )?; + } + + // Insert its root to the global monotree + let state_monotree_root = match state_monotree_root { + Some(hash) => hash, + None => *EMPTY_HASH, + }; + debug!(target: "blockchain::contractstore::update_state_monotree", "New root: {}", blake3::hash(&state_monotree_root)); + root = tree.insert(root.as_ref(), &contract_id.to_bytes(), &state_monotree_root)?; tree.set_headroot(root.as_ref()); - + debug!(target: "blockchain::contractstore::update_state_monotree", "New global root: {}", blake3::hash(&root.unwrap())); continue } @@ -559,10 +644,12 @@ impl ContractStoreOverlay { continue } - // Iterate over current contracts wasm bincodes to compute - // its checksum. - let mut hasher = blake3::Hasher::new(); - for record in lock.iter(SLED_BINCODE_TREE)? { + // Iterate over current contracts wasm bincodes to compute its monotree root + debug!(target: "blockchain::contractstore::update_state_monotree", "Updating wasm bincodes monotree..."); + let mut wasm_monotree_root = None; + let wasm_monotree_db = MemoryDb::new(); + let mut wasm_monotree = Monotree::new(wasm_monotree_db); + for record in state_cache.iter() { let (key, value) = record?; // Skip native ones @@ -570,54 +657,32 @@ impl ContractStoreOverlay { continue } - // Hash record - hasher.update(&key); - hasher.update(&value); + // Insert record + let key = blake3::hash(&key); + let value = blake3::hash(&value); + debug!(target: "blockchain::contractstore::update_state_monotree", "Inserting key {key} with value: {value}"); + wasm_monotree_root = wasm_monotree.insert( + wasm_monotree_root.as_ref(), + key.as_bytes(), + value.as_bytes(), + )?; } - // Insert wasm bincodes record to monotree + // Insert wasm bincodes root to the global monotree + let wasm_monotree_root = match wasm_monotree_root { + Some(hash) => hash, + None => *EMPTY_HASH, + }; + debug!(target: "blockchain::contractstore::update_state_monotree", "New root: {}", blake3::hash(&wasm_monotree_root)); root = tree.insert( root.as_ref(), blake3::hash(SLED_BINCODE_TREE).as_bytes(), - hasher.finalize().as_bytes(), + &wasm_monotree_root, )?; tree.set_headroot(root.as_ref()); + debug!(target: "blockchain::contractstore::update_state_monotree", "New global root: {}", blake3::hash(&root.unwrap())); } Ok(()) } } - -/// Auxiliary function to compute a blake3 checksum for provided sled -/// tree. -fn sled_tree_checksum(tree: &sled::Tree) -> Result<[u8; 32]> { - // Generate a new blake3 hashed - let mut hasher = blake3::Hasher::new(); - - // Iterate over tree records to compute its checksum - for record in tree.iter() { - let (key, value) = record?; - hasher.update(&key); - hasher.update(&value); - } - - // Return the finalized hasher bytes - Ok(*hasher.finalize().as_bytes()) -} - -/// Auxiliary function to compute a blake3 checksum for provided sled -/// overlay tree. -fn sled_overlay_tree_checksum(overlay: &SledDbOverlay, tree_key: &[u8]) -> Result<[u8; 32]> { - // Generate a new blake3 hashed - let mut hasher = blake3::Hasher::new(); - - // Iterate over tree records to compute its checksum - for record in overlay.iter(tree_key)? { - let (key, value) = record?; - hasher.update(&key); - hasher.update(&value); - } - - // Return the finalized hasher bytes - Ok(*hasher.finalize().as_bytes()) -} diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 1d77f0a69..80da0b03d 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -51,6 +51,7 @@ pub use tx_store::{ pub mod contract_store; pub use contract_store::{ ContractStore, ContractStoreOverlay, SLED_BINCODE_TREE, SLED_CONTRACTS_TREE, + SLED_CONTRACTS_TREES_TREE, }; /// Monero definitions needed for merge mining @@ -447,6 +448,7 @@ impl BlockchainOverlay { SLED_PENDING_TX_TREE, SLED_PENDING_TX_ORDER_TREE, SLED_CONTRACTS_TREE, + SLED_CONTRACTS_TREES_TREE, SLED_BINCODE_TREE, ]; let overlay = Arc::new(Mutex::new(sled_overlay::SledDbOverlay::new( diff --git a/src/runtime/import/db.rs b/src/runtime/import/db.rs index 9120b3934..cb07582c3 100644 --- a/src/runtime/import/db.rs +++ b/src/runtime/import/db.rs @@ -19,7 +19,9 @@ use std::io::Cursor; use darkfi_sdk::{ - crypto::contract_id::{ContractId, SMART_CONTRACT_ZKAS_DB_NAME}, + crypto::contract_id::{ + ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME, + }, wasm, }; use darkfi_serial::{deserialize, serialize, Decodable}; @@ -138,6 +140,15 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut, ptr: WasmPtr, ptr_len: u return darkfi_sdk::error::CALLER_ACCESS_DENIED } + // Nor can we allow initializing the special monotree db: + if read_db_name == SMART_CONTRACT_MONOTREE_DB_NAME { + error!( + target: "runtime::db::db_init", + "[WASM] [{cid}] db_init(): Attempted to init monotree db" + ); + return darkfi_sdk::error::CALLER_ACCESS_DENIED + } + // Nor can we allow another contract to initialize a db for someone else: if cid != read_cid { error!( @@ -293,6 +304,14 @@ pub(crate) fn db_lookup(mut ctx: FunctionEnvMut, ptr: WasmPtr, ptr_len: return darkfi_sdk::error::CALLER_ACCESS_DENIED } + if db_name == SMART_CONTRACT_MONOTREE_DB_NAME { + error!( + target: "runtime::db::db_lookup", + "[WASM] [{cid}] db_lookup(): Attempted to lookup monotree db" + ); + return darkfi_sdk::error::CALLER_ACCESS_DENIED + } + // Lookup contract state let tree_handle = match contracts.lookup(&cid, &db_name) { Ok(v) => v, diff --git a/src/runtime/vm_runtime.rs b/src/runtime/vm_runtime.rs index 831306ef7..baa4cee80 100644 --- a/src/runtime/vm_runtime.rs +++ b/src/runtime/vm_runtime.rs @@ -22,7 +22,9 @@ use std::{ }; use darkfi_sdk::{ - crypto::contract_id::{ContractId, SMART_CONTRACT_ZKAS_DB_NAME}, + crypto::contract_id::{ + ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME, + }, tx::TransactionHash, wasm, AsHex, }; @@ -466,6 +468,12 @@ impl Runtime { Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?, }; + // Create the monotree db tree for this contract, + // if it doesn't exists. + if contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME).is_err() { + contracts.init(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME)?; + } + let mut db_handles = env_mut.db_handles.borrow_mut(); db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle)); } diff --git a/src/sdk/src/crypto/contract_id.rs b/src/sdk/src/crypto/contract_id.rs index fadcd2d63..d0021acac 100644 --- a/src/sdk/src/crypto/contract_id.rs +++ b/src/sdk/src/crypto/contract_id.rs @@ -28,6 +28,9 @@ use crate::error::ContractError; /// The hardcoded db name for the zkas circuits database tree pub const SMART_CONTRACT_ZKAS_DB_NAME: &str = "_zkas"; +/// The hardcoded db name for the monotree database tree +pub const SMART_CONTRACT_MONOTREE_DB_NAME: &str = "_monotree"; + lazy_static! { // The idea here is that 0 is not a valid x coordinate for any pallas point, // therefore a signature cannot be produced for such IDs. This allows us to diff --git a/src/sdk/src/monotree/mod.rs b/src/sdk/src/monotree/mod.rs index 329f8e6ca..d1177b832 100644 --- a/src/sdk/src/monotree/mod.rs +++ b/src/sdk/src/monotree/mod.rs @@ -41,7 +41,7 @@ pub mod bits; pub mod node; pub mod tree; -pub use tree::{MemoryDb, Monotree, SledOverlayDb}; +pub use tree::{MemoryDb, Monotree, SledOverlayDb, SledTreeDb}; pub mod utils; diff --git a/src/sdk/src/monotree/tree.rs b/src/sdk/src/monotree/tree.rs index 0ae2a06f3..bd6856659 100644 --- a/src/sdk/src/monotree/tree.rs +++ b/src/sdk/src/monotree/tree.rs @@ -17,10 +17,8 @@ * along with this program. If not, see . */ -use std::sync::{Arc, Mutex}; - use hashbrown::{HashMap, HashSet}; -use sled_overlay::SledDbOverlay; +use sled_overlay::{sled::Tree, SledDbOverlay}; use super::{ bits::Bits, @@ -150,28 +148,25 @@ impl MonotreeStorageAdapter for MemoryDb { } } -/// sled-overlay-based storage for Monotree +/// sled-tree based storage for Monotree #[derive(Clone)] -pub struct SledOverlayDb { - overlay: Arc>, - tree: [u8; 32], +pub struct SledTreeDb { + tree: Tree, batch: MemCache, batch_on: bool, } -impl SledOverlayDb { - pub fn new(overlay: &Arc>, tree: &[u8; 32]) -> Self { - Self { overlay: overlay.clone(), tree: *tree, batch: MemCache::new(), batch_on: false } +impl SledTreeDb { + pub fn new(tree: &Tree) -> Self { + Self { tree: tree.clone(), batch: MemCache::new(), batch_on: false } } } -impl MonotreeStorageAdapter for SledOverlayDb { +impl MonotreeStorageAdapter for SledTreeDb { fn put(&mut self, key: &Hash, value: Vec) -> GenericResult<()> { if self.batch_on { self.batch.put(key, value); - } else if let Err(e) = - self.overlay.lock().unwrap().insert(&self.tree, &slice_to_hash(key), &value) - { + } else if let Err(e) = self.tree.insert(slice_to_hash(key), value) { return Err(ContractError::IoError(e.to_string())) } @@ -183,7 +178,7 @@ impl MonotreeStorageAdapter for SledOverlayDb { return Ok(self.batch.get(key)); } - match self.overlay.lock().unwrap().get(&self.tree, key) { + match self.tree.get(key) { Ok(Some(v)) => Ok(Some(v.to_vec())), Ok(None) => Ok(None), Err(e) => Err(ContractError::IoError(e.to_string())), @@ -193,7 +188,7 @@ impl MonotreeStorageAdapter for SledOverlayDb { fn del(&mut self, key: &Hash) -> GenericResult<()> { if self.batch_on { self.batch.del(key); - } else if let Err(e) = self.overlay.lock().unwrap().remove(&self.tree, key) { + } else if let Err(e) = self.tree.remove(key) { return Err(ContractError::IoError(e.to_string())); } @@ -212,12 +207,92 @@ impl MonotreeStorageAdapter for SledOverlayDb { fn finish_batch(&mut self) -> GenericResult<()> { if self.batch_on { for (key, value) in self.batch.map.drain() { - if let Err(e) = self.overlay.lock().unwrap().insert(&self.tree, &key, &value) { + if let Err(e) = self.tree.insert(key, value) { return Err(ContractError::IoError(e.to_string())) } } for key in self.batch.set.drain() { - if let Err(e) = self.overlay.lock().unwrap().remove(&self.tree, &key) { + if let Err(e) = self.tree.remove(key) { + return Err(ContractError::IoError(e.to_string())) + } + } + self.batch_on = false; + } + + Ok(()) + } +} + +/// sled-overlay based storage for Monotree +#[derive(Clone)] +pub struct SledOverlayDb { + overlay: SledDbOverlay, + tree: [u8; 32], + batch: MemCache, + batch_on: bool, +} + +impl SledOverlayDb { + pub fn new(overlay: &SledDbOverlay, tree: &[u8; 32]) -> GenericResult { + let mut overlay = overlay.clone(); + if let Err(e) = overlay.open_tree(tree, false) { + return Err(ContractError::IoError(e.to_string())) + }; + Ok(Self { overlay, tree: *tree, batch: MemCache::new(), batch_on: false }) + } +} + +impl MonotreeStorageAdapter for SledOverlayDb { + fn put(&mut self, key: &Hash, value: Vec) -> GenericResult<()> { + if self.batch_on { + self.batch.put(key, value); + } else if let Err(e) = self.overlay.insert(&self.tree, &slice_to_hash(key), &value) { + return Err(ContractError::IoError(e.to_string())) + } + + Ok(()) + } + + fn get(&self, key: &[u8]) -> GenericResult>> { + if self.batch_on && self.batch.contains(key) { + return Ok(self.batch.get(key)); + } + + match self.overlay.get(&self.tree, key) { + Ok(Some(v)) => Ok(Some(v.to_vec())), + Ok(None) => Ok(None), + Err(e) => Err(ContractError::IoError(e.to_string())), + } + } + + fn del(&mut self, key: &Hash) -> GenericResult<()> { + if self.batch_on { + self.batch.del(key); + } else if let Err(e) = self.overlay.remove(&self.tree, key) { + return Err(ContractError::IoError(e.to_string())); + } + + Ok(()) + } + + fn init_batch(&mut self) -> GenericResult<()> { + if !self.batch_on { + self.batch.clear(); + self.batch_on = true; + } + + Ok(()) + } + + fn finish_batch(&mut self) -> GenericResult<()> { + if self.batch_on { + for (key, value) in self.batch.map.drain() { + if let Err(e) = self.overlay.insert(&self.tree, &key, &value) { + return Err(ContractError::IoError(e.to_string())) + } + } + for key in self.batch.set.drain() { + if let Err(e) = self.overlay.remove(&self.tree, &key) { return Err(ContractError::IoError(e.to_string())) } }