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)
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);

View File

@@ -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<Self> {
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<Monotree<monotree::MemoryDb>> {
pub fn get_state_monotree(&self, db: &sled::Db) -> Result<Monotree<MemoryDb>> {
// 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<Self> {
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<Monotree<monotree::MemoryDb>> {
let mut lock = self.0.lock().unwrap();
pub fn get_state_monotree(&self) -> Result<Monotree<MemoryDb>> {
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<monotree::MemoryDb>) -> Result<()> {
pub fn update_state_monotree(&self, tree: &mut Monotree<MemoryDb>) -> 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())
}

View File

@@ -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(

View File

@@ -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<Env>, ptr: WasmPtr<u8>, 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<Env>, ptr: WasmPtr<u8>, 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,

View File

@@ -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));
}

View File

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

View File

@@ -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;

View File

@@ -17,10 +17,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<Mutex<SledDbOverlay>>,
tree: [u8; 32],
pub struct SledTreeDb {
tree: Tree,
batch: MemCache,
batch_on: bool,
}
impl SledOverlayDb {
pub fn new(overlay: &Arc<Mutex<SledDbOverlay>>, 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<u8>) -> 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<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()))
}
}