mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
blockchain/contract_store: use monotrees for contracts states instead of checksums
This commit is contained in:
@@ -1 +1 @@
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAOjwIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAGALfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
@@ -1 +1 @@
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAE9GG2gAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCMPjhlzo1AEcT03pj4ZfNZRxh9AwSl4slZFTeM+H5fyGAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAHQLfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
@@ -1 +1 @@
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAO/wIWgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCK/BaU3WspDYuSwz0/x0Zwfam+2FfrnpDxFoPS4kO4BHAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AYa7rEMKSzoYLxJbN6SG6cSGu/o02E70pmtKI+XwxiWxAAAAAG0LfmgAAAAAAAAAAAAAAAA7PNtvmfHqHEnjNnAS1ULkbCEM4uIYpCgNuv5kw2ETCNtqJ3nhmdGy6NXo7EAg7BLwPF3/KIUrAsdAvYbVh5izAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user