blockchain/contract_store: use monotrees for contracts states instead of checksums

This commit is contained in:
skoupidi
2025-07-21 14:52:29 +03:00
parent 37efe70543
commit 01950761e7
11 changed files with 320 additions and 148 deletions

View File

@@ -1 +1 @@
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAOjwIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAGALfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View File

@@ -1 +1 @@
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAE9GG2gAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCMPjhlzo1AEcT03pj4ZfNZRxh9AwSl4slZFTeM+H5fyGAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAHQLfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View File

@@ -1 +1 @@
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAO/wIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAG0LfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

View File

@@ -301,13 +301,13 @@ comparison the tx in Bitcoin with the most outputs has 2501.
## Contracts states Monotree(SMT) ## Contracts states Monotree(SMT)
DarkFi is using an optimized Sparse Merkle Tree implementation, DarkFi is using an optimized Sparse Merkle Tree implementation, called
called Monotree. Monotree. Each contract has its own Monotree, containing all its
This tree is constructed using all contracts states trees checksums, database trees hashed records. Additionally, a global tree exists
along with the wasm bincodes tree checksum, excluding native contracts reflecting the total database state. The global tree is constructed
zkas tree and wasm bincodes. using all contracts states monotrees roots, along with the wasm
The checksum of each tree is computed by hashing all its key and values, bincodes monotree root, excluding native contracts zkas tree and wasm
using a `blake3` hasher. bincodes.
For each block, we compute the current tree root and keep it in its header, 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, enabling us to both verify the contacts state after the block insertion,
and create proofs commiting to that specific state. 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 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 a proof for a specific state. First, we will create a random set of
key-value `blake3` hash pairs, representing our contract states key-value `blake3` hash pairs, representing our contract states
checksums: roots:
``` ```
keys = random_hashes(100); keys = random_hashes(100);

View File

@@ -21,13 +21,13 @@ use std::{collections::BTreeMap, io::Cursor};
use darkfi_sdk::{ use darkfi_sdk::{
crypto::contract_id::{ crypto::contract_id::{
ContractId, NATIVE_CONTRACT_IDS_BYTES, NATIVE_CONTRACT_ZKAS_DB_NAMES, 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 darkfi_serial::{deserialize, serialize};
use sled_overlay::{serial::parse_record, sled, SledDbOverlay};
use tracing::{debug, error}; use tracing::{debug, error};
use sled_overlay::{serial::parse_record, sled};
use crate::{ use crate::{
zk::{empty_witnesses, VerifyingKey, ZkCircuit}, zk::{empty_witnesses, VerifyingKey, ZkCircuit},
@@ -38,6 +38,7 @@ use crate::{
use super::SledDbOverlayPtr; use super::SledDbOverlayPtr;
pub const SLED_CONTRACTS_TREE: &[u8] = b"_contracts"; 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"; pub const SLED_BINCODE_TREE: &[u8] = b"_wasm_bincode";
/// The `ContractStore` is a structure representing all `sled` trees related /// 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()`. /// These values get mutated with `init()` and `remove()`.
pub state: sled::Tree, 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 { impl ContractStore {
@@ -68,7 +80,8 @@ impl ContractStore {
pub fn new(db: &sled::Db) -> Result<Self> { pub fn new(db: &sled::Db) -> Result<Self> {
let wasm = db.open_tree(SLED_BINCODE_TREE)?; let wasm = db.open_tree(SLED_BINCODE_TREE)?;
let state = db.open_tree(SLED_CONTRACTS_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. /// Fetches the bincode for a given ContractId from the store's wasm tree.
@@ -139,10 +152,14 @@ impl ContractStore {
if !state_pointers.contains(&ptr) { if !state_pointers.contains(&ptr) {
return Err(Error::ContractStateNotFound) return Err(Error::ContractStateNotFound)
} }
if !self.state_trees.contains_key(ptr)? {
return Err(Error::ContractStateNotFound)
}
// Remove the deleted tree from the state pointer set. // Remove the deleted tree from the state pointer set.
state_pointers.retain(|x| *x != ptr); state_pointers.retain(|x| *x != ptr);
self.state.insert(contract_id_bytes, serialize(&state_pointers))?; self.state.insert(contract_id_bytes, serialize(&state_pointers))?;
self.state_trees.remove(ptr)?;
// Drop the deleted tree from the database // Drop the deleted tree from the database
db.drop_tree(ptr)?; db.drop_tree(ptr)?;
@@ -257,40 +274,47 @@ impl ContractStore {
} }
/// Generate a Monotree(SMT) containing all contracts states /// 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. /// Note: native contracts zkas tree and wasm bincodes are excluded.
pub fn get_state_monotree(&self, db: &sled::Db) -> Result<Monotree<monotree::MemoryDb>> { pub fn get_state_monotree(&self, db: &sled::Db) -> Result<Monotree<MemoryDb>> {
// Initialize the monotree // Initialize the monotree
let mut root = None; let mut root = None;
let monotree_db = monotree::MemoryDb::new(); let monotree_db = MemoryDb::new();
let mut tree = Monotree::new(monotree_db); let mut tree = Monotree::new(monotree_db);
// Iterate over current contracts states records // Iterate over current contracts states records
// TODO: parallelize this with a threadpool for state_record in self.state.iter() {
for state_record in self.state.iter().values() { // Grab its monotree pointer
// Iterate over contract states pointers let (contract_id, state_pointers): (ContractId, Vec<[u8; 32]>) =
let state_pointers: Vec<[u8; 32]> = deserialize(&state_record?)?; parse_record(state_record?)?;
for state_ptr in state_pointers { let state_monotree_ptr = contract_id.hash_state_id(SMART_CONTRACT_MONOTREE_DB_NAME);
// Skip native zkas tree
if NATIVE_CONTRACT_ZKAS_DB_NAMES.contains(&state_ptr) {
continue
}
// Grab the state tree // Check it exists
let state_tree = db.open_tree(state_ptr)?; if !state_pointers.contains(&state_monotree_ptr) {
return Err(Error::ContractStateNotFound)
// 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());
} }
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 // Iterate over current contracts wasm bincodes to compute its monotree root
let mut hasher = blake3::Hasher::new(); 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() { for record in self.wasm.iter() {
let (key, value) = record?; let (key, value) = record?;
@@ -299,16 +323,23 @@ impl ContractStore {
continue continue
} }
// Hash record // Insert record
hasher.update(&key); wasm_monotree_root = wasm_monotree.insert(
hasher.update(&value); 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 = tree.insert(
root.as_ref(), root.as_ref(),
blake3::hash(SLED_BINCODE_TREE).as_bytes(), blake3::hash(SLED_BINCODE_TREE).as_bytes(),
hasher.finalize().as_bytes(), &wasm_monotree_root,
)?; )?;
tree.set_headroot(root.as_ref()); tree.set_headroot(root.as_ref());
@@ -323,6 +354,7 @@ impl ContractStoreOverlay {
pub fn new(overlay: &SledDbOverlayPtr) -> Result<Self> { pub fn new(overlay: &SledDbOverlayPtr) -> Result<Self> {
overlay.lock().unwrap().open_tree(SLED_BINCODE_TREE, true)?; 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_TREE, true)?;
overlay.lock().unwrap().open_tree(SLED_CONTRACTS_TREES_TREE, true)?;
Ok(Self(overlay.clone())) Ok(Self(overlay.clone()))
} }
@@ -385,6 +417,7 @@ impl ContractStoreOverlay {
// Now we add it so it's marked as initialized and create its tree. // Now we add it so it's marked as initialized and create its tree.
state_pointers.push(ptr); state_pointers.push(ptr);
lock.insert(SLED_CONTRACTS_TREE, &contract_id_bytes, &serialize(&state_pointers))?; 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)?; lock.open_tree(&ptr, false)?;
Ok(ptr) Ok(ptr)
@@ -413,6 +446,9 @@ impl ContractStoreOverlay {
if !state_pointers.contains(&ptr) { if !state_pointers.contains(&ptr) {
return Err(Error::ContractStateNotFound) 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 // We open the tree and return its handle
lock.open_tree(&ptr, false)?; lock.open_tree(&ptr, false)?;
@@ -452,47 +488,55 @@ impl ContractStoreOverlay {
} }
/// Generate a Monotree(SMT) containing all contracts states /// Generate a Monotree(SMT) containing all contracts states
/// checksums, along with the wasm bincodes checksum. /// roots, along with the wasm bincodes monotree roots.
/// Be carefull as this will open all states trees in the overlay. /// Be carefull as this will open all states monotrees in the overlay.
/// ///
/// Note: native contracts zkas tree and wasm bincodes are excluded. /// Note: native contracts zkas tree and wasm bincodes are excluded.
pub fn get_state_monotree(&self) -> Result<Monotree<monotree::MemoryDb>> { pub fn get_state_monotree(&self) -> Result<Monotree<MemoryDb>> {
let mut lock = self.0.lock().unwrap(); let lock = self.0.lock().unwrap();
// Grab all states pointers // Grab all states monotrees pointers
let mut states_pointers = vec![]; let mut states_monotrees_pointers = vec![];
for state_record in lock.iter(SLED_CONTRACTS_TREE)? { for state_record in lock.iter(SLED_CONTRACTS_TREE)? {
let state_pointers: Vec<[u8; 32]> = deserialize(&state_record?.1)?; // Grab its monotree pointer
for state_ptr in state_pointers { let (contract_id, state_pointers): (ContractId, Vec<[u8; 32]>) =
// Skip native zkas tree parse_record(state_record?)?;
if NATIVE_CONTRACT_ZKAS_DB_NAMES.contains(&state_ptr) { let state_monotree_ptr = contract_id.hash_state_id(SMART_CONTRACT_MONOTREE_DB_NAME);
continue
} // Check it exists
states_pointers.push(state_ptr); 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 // Initialize the monotree
let mut root = None; let mut root = None;
let monotree_db = monotree::MemoryDb::new(); let monotree_db = MemoryDb::new();
let mut tree = Monotree::new(monotree_db); let mut tree = Monotree::new(monotree_db);
// Iterate over contract states pointers // Iterate over contract states monotrees pointers
// TODO: parallelize this with a threadpool for (contract_id, state_monotree_ptr) in states_monotrees_pointers {
for state_ptr in states_pointers { // Grab its monotree
// Open the state tree in the overlay let state_monotree_db = SledOverlayDb::new(&lock, &state_monotree_ptr)?;
lock.open_tree(&state_ptr, false)?; let state_monotree = Monotree::new(state_monotree_db);
// Compute its checksum // Insert its root to the global monotree
let checksum = sled_overlay_tree_checksum(&lock, &state_ptr)?; let state_monotree_root = match state_monotree.get_headroot()? {
Some(hash) => hash,
// Insert record to monotree None => *EMPTY_HASH,
root = tree.insert(root.as_ref(), &state_ptr, &checksum)?; };
tree.set_headroot(root.as_ref()); root = tree.insert(root.as_ref(), &contract_id.to_bytes(), &state_monotree_root)?;
} }
// Iterate over current contracts wasm bincodes to compute its checksum // Iterate over current contracts wasm bincodes to compute its monotree root
let mut hasher = blake3::Hasher::new(); 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)? { for record in lock.iter(SLED_BINCODE_TREE)? {
let (key, value) = record?; let (key, value) = record?;
@@ -501,32 +545,38 @@ impl ContractStoreOverlay {
continue continue
} }
// Hash record // Insert record
hasher.update(&key); wasm_monotree_root = wasm_monotree.insert(
hasher.update(&value); 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 = tree.insert(
root.as_ref(), root.as_ref(),
blake3::hash(SLED_BINCODE_TREE).as_bytes(), blake3::hash(SLED_BINCODE_TREE).as_bytes(),
hasher.finalize().as_bytes(), &wasm_monotree_root,
)?; )?;
tree.set_headroot(root.as_ref()); tree.set_headroot(root.as_ref());
Ok(tree) Ok(tree)
} }
/// Compute all updated contracts states and wasm bincodes /// Retrieve all updated contracts states and wasm bincodes
/// checksums and update their records in the provided /// monotrees roots and update their records in the provided
/// Monotree(SMT). /// Monotree(SMT).
/// ///
/// Note: native contracts zkas tree and wasm bincodes are excluded. /// Note: native contracts zkas tree and wasm bincodes are excluded.
pub fn update_state_monotree(&self, tree: &mut Monotree<monotree::MemoryDb>) -> Result<()> { pub fn update_state_monotree(&self, tree: &mut Monotree<MemoryDb>) -> Result<()> {
let lock = self.0.lock().unwrap(); let lock = self.0.lock().unwrap();
// Iterate over overlay's caches // Iterate over overlay's caches
// TODO: parallelize this with a threadpool
let mut root = tree.get_headroot()?; let mut root = tree.get_headroot()?;
for (state_key, state_cache) in &lock.state.caches { for (state_key, state_cache) in &lock.state.caches {
// Check if that cache is a contract state one. // Check if that cache is a contract state one.
@@ -539,13 +589,48 @@ impl ContractStoreOverlay {
continue continue
} }
// Compute its checksum // Grab its contract id
let checksum = sled_overlay_tree_checksum(&lock, &state_key)?; 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 // Grab its monotree
root = tree.insert(root.as_ref(), &state_key, &checksum)?; 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()); tree.set_headroot(root.as_ref());
debug!(target: "blockchain::contractstore::update_state_monotree", "New global root: {}", blake3::hash(&root.unwrap()));
continue continue
} }
@@ -559,10 +644,12 @@ impl ContractStoreOverlay {
continue continue
} }
// Iterate over current contracts wasm bincodes to compute // Iterate over current contracts wasm bincodes to compute its monotree root
// its checksum. debug!(target: "blockchain::contractstore::update_state_monotree", "Updating wasm bincodes monotree...");
let mut hasher = blake3::Hasher::new(); let mut wasm_monotree_root = None;
for record in lock.iter(SLED_BINCODE_TREE)? { 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?; let (key, value) = record?;
// Skip native ones // Skip native ones
@@ -570,54 +657,32 @@ impl ContractStoreOverlay {
continue continue
} }
// Hash record // Insert record
hasher.update(&key); let key = blake3::hash(&key);
hasher.update(&value); 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 = tree.insert(
root.as_ref(), root.as_ref(),
blake3::hash(SLED_BINCODE_TREE).as_bytes(), blake3::hash(SLED_BINCODE_TREE).as_bytes(),
hasher.finalize().as_bytes(), &wasm_monotree_root,
)?; )?;
tree.set_headroot(root.as_ref()); tree.set_headroot(root.as_ref());
debug!(target: "blockchain::contractstore::update_state_monotree", "New global root: {}", blake3::hash(&root.unwrap()));
} }
Ok(()) 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())
}

View File

@@ -51,6 +51,7 @@ pub use tx_store::{
pub mod contract_store; pub mod contract_store;
pub use contract_store::{ pub use contract_store::{
ContractStore, ContractStoreOverlay, SLED_BINCODE_TREE, SLED_CONTRACTS_TREE, ContractStore, ContractStoreOverlay, SLED_BINCODE_TREE, SLED_CONTRACTS_TREE,
SLED_CONTRACTS_TREES_TREE,
}; };
/// Monero definitions needed for merge mining /// Monero definitions needed for merge mining
@@ -447,6 +448,7 @@ impl BlockchainOverlay {
SLED_PENDING_TX_TREE, SLED_PENDING_TX_TREE,
SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_ORDER_TREE,
SLED_CONTRACTS_TREE, SLED_CONTRACTS_TREE,
SLED_CONTRACTS_TREES_TREE,
SLED_BINCODE_TREE, SLED_BINCODE_TREE,
]; ];
let overlay = Arc::new(Mutex::new(sled_overlay::SledDbOverlay::new( let overlay = Arc::new(Mutex::new(sled_overlay::SledDbOverlay::new(

View File

@@ -19,7 +19,9 @@
use std::io::Cursor; use std::io::Cursor;
use darkfi_sdk::{ 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, wasm,
}; };
use darkfi_serial::{deserialize, serialize, Decodable}; use darkfi_serial::{deserialize, serialize, Decodable};
@@ -138,6 +140,15 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len: u
return darkfi_sdk::error::CALLER_ACCESS_DENIED 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: // Nor can we allow another contract to initialize a db for someone else:
if cid != read_cid { if cid != read_cid {
error!( error!(
@@ -293,6 +304,14 @@ pub(crate) fn db_lookup(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, ptr_len:
return darkfi_sdk::error::CALLER_ACCESS_DENIED 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 // Lookup contract state
let tree_handle = match contracts.lookup(&cid, &db_name) { let tree_handle = match contracts.lookup(&cid, &db_name) {
Ok(v) => v, Ok(v) => v,

View File

@@ -22,7 +22,9 @@ use std::{
}; };
use darkfi_sdk::{ 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, tx::TransactionHash,
wasm, AsHex, wasm, AsHex,
}; };
@@ -466,6 +468,12 @@ impl Runtime {
Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?, 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(); let mut db_handles = env_mut.db_handles.borrow_mut();
db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle)); db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle));
} }

View File

@@ -28,6 +28,9 @@ use crate::error::ContractError;
/// The hardcoded db name for the zkas circuits database tree /// The hardcoded db name for the zkas circuits database tree
pub const SMART_CONTRACT_ZKAS_DB_NAME: &str = "_zkas"; 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! { lazy_static! {
// The idea here is that 0 is not a valid x coordinate for any pallas point, // 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 // therefore a signature cannot be produced for such IDs. This allows us to

View File

@@ -41,7 +41,7 @@ pub mod bits;
pub mod node; pub mod node;
pub mod tree; pub mod tree;
pub use tree::{MemoryDb, Monotree, SledOverlayDb}; pub use tree::{MemoryDb, Monotree, SledOverlayDb, SledTreeDb};
pub mod utils; pub mod utils;

View File

@@ -17,10 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::sync::{Arc, Mutex};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use sled_overlay::SledDbOverlay; use sled_overlay::{sled::Tree, SledDbOverlay};
use super::{ use super::{
bits::Bits, bits::Bits,
@@ -150,28 +148,25 @@ impl MonotreeStorageAdapter for MemoryDb {
} }
} }
/// sled-overlay-based storage for Monotree /// sled-tree based storage for Monotree
#[derive(Clone)] #[derive(Clone)]
pub struct SledOverlayDb { pub struct SledTreeDb {
overlay: Arc<Mutex<SledDbOverlay>>, tree: Tree,
tree: [u8; 32],
batch: MemCache, batch: MemCache,
batch_on: bool, batch_on: bool,
} }
impl SledOverlayDb { impl SledTreeDb {
pub fn new(overlay: &Arc<Mutex<SledDbOverlay>>, tree: &[u8; 32]) -> Self { pub fn new(tree: &Tree) -> Self {
Self { overlay: overlay.clone(), tree: *tree, batch: MemCache::new(), batch_on: false } 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<u8>) -> GenericResult<()> { fn put(&mut self, key: &Hash, value: Vec<u8>) -> GenericResult<()> {
if self.batch_on { if self.batch_on {
self.batch.put(key, value); self.batch.put(key, value);
} else if let Err(e) = } else if let Err(e) = self.tree.insert(slice_to_hash(key), value) {
self.overlay.lock().unwrap().insert(&self.tree, &slice_to_hash(key), &value)
{
return Err(ContractError::IoError(e.to_string())) return Err(ContractError::IoError(e.to_string()))
} }
@@ -183,7 +178,7 @@ impl MonotreeStorageAdapter for SledOverlayDb {
return Ok(self.batch.get(key)); 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(Some(v)) => Ok(Some(v.to_vec())),
Ok(None) => Ok(None), Ok(None) => Ok(None),
Err(e) => Err(ContractError::IoError(e.to_string())), Err(e) => Err(ContractError::IoError(e.to_string())),
@@ -193,7 +188,7 @@ impl MonotreeStorageAdapter for SledOverlayDb {
fn del(&mut self, key: &Hash) -> GenericResult<()> { fn del(&mut self, key: &Hash) -> GenericResult<()> {
if self.batch_on { if self.batch_on {
self.batch.del(key); 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())); return Err(ContractError::IoError(e.to_string()));
} }
@@ -212,12 +207,92 @@ impl MonotreeStorageAdapter for SledOverlayDb {
fn finish_batch(&mut self) -> GenericResult<()> { fn finish_batch(&mut self) -> GenericResult<()> {
if self.batch_on { if self.batch_on {
for (key, value) in self.batch.map.drain() { 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())) return Err(ContractError::IoError(e.to_string()))
} }
} }
for key in self.batch.set.drain() { 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<Self> {
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<u8>) -> 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<Option<Vec<u8>>> {
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())) return Err(ContractError::IoError(e.to_string()))
} }
} }