cache.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use std::collections::HashMap; 20 21 use darkfi::{blockchain::HeaderHash, Error, Result}; 22 use darkfi_sdk::{ 23 crypto::{ 24 pasta_prelude::PrimeField, 25 smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, SMT_FP_DEPTH}, 26 MerkleTree, 27 }, 28 error::{ContractError, ContractResult}, 29 pasta::pallas, 30 }; 31 use darkfi_serial::{deserialize, serialize}; 32 use num_bigint::BigUint; 33 use sled_overlay::{sled, SledDbOverlay, SledDbOverlayStateDiff}; 34 use tracing::error; 35 36 pub const SLED_SCANNED_BLOCKS_TREE: &[u8] = b"_scanned_blocks"; 37 pub const SLED_STATE_INVERSE_DIFF_TREE: &[u8] = b"_state_inverse_diff"; 38 pub const SLED_MERKLE_TREES_TREE: &[u8] = b"_merkle_trees"; 39 pub const SLED_MONEY_SMT_TREE: &[u8] = b"_money_smt"; 40 41 /// Structure holding all sled trees that define the blockchain cache. 42 #[derive(Clone)] 43 pub struct Cache { 44 /// Main pointer to the sled db connection 45 pub sled_db: sled::Db, 46 /// The `sled` tree storing the scanned blocks from the blockchain, 47 /// where the key is the height number, and the value is the blocks' 48 /// hash. 49 pub scanned_blocks: sled::Tree, 50 /// The `sled` tree storing each blocks' full database state inverse 51 /// changes, where the key is the block height number, and the value 52 /// is the serialized database inverse diff. 53 pub state_inverse_diff: sled::Tree, 54 /// The `sled` tree storing the merkle trees of the blockchain, 55 /// where the key is the tree name, and the value is the serialized 56 /// merkle tree itself. 57 pub merkle_trees: sled::Tree, 58 /// The `sled` tree storing the Sparse Merkle Tree of the Money 59 /// contract. 60 // TODO: this could be a map of trees so more contracts can open 61 // SMTs if needed 62 pub money_smt: sled::Tree, 63 // TODO: Perhaps we should also move transactions history here 64 } 65 66 impl Cache { 67 /// Instantiate a new `Cache` with the given `sled` database. 68 pub fn new(db: &sled::Db) -> Result<Self> { 69 let scanned_blocks = db.open_tree(SLED_SCANNED_BLOCKS_TREE)?; 70 let state_inverse_diff = db.open_tree(SLED_STATE_INVERSE_DIFF_TREE)?; 71 let merkle_trees = db.open_tree(SLED_MERKLE_TREES_TREE)?; 72 let money_smt = db.open_tree(SLED_MONEY_SMT_TREE)?; 73 74 Ok(Self { 75 sled_db: db.clone(), 76 scanned_blocks, 77 state_inverse_diff, 78 merkle_trees, 79 money_smt, 80 }) 81 } 82 83 /// Execute an atomic sled batch corresponding to inserts to the 84 /// merkle trees tree. For each record, the bytes slice is used as 85 /// the key, and the serialized merkle tree is used as value. 86 pub fn insert_merkle_trees(&self, trees: &[(&[u8], &MerkleTree)]) -> Result<()> { 87 let mut batch = sled::Batch::default(); 88 for (key, tree) in trees { 89 batch.insert(*key, serialize(*tree)); 90 } 91 self.merkle_trees.apply_batch(batch)?; 92 Ok(()) 93 } 94 95 /// Insert a `u32` and a block inverse diff into store's inverse 96 /// diffs tree. The block height is used as the key, and the 97 /// serialized database inverse diff is used as value. 98 pub fn insert_state_inverse_diff( 99 &self, 100 height: &u32, 101 diff: &SledDbOverlayStateDiff, 102 ) -> Result<()> { 103 self.state_inverse_diff.insert(height.to_be_bytes(), serialize(diff))?; 104 Ok(()) 105 } 106 107 /// Fetch given block height number from the store's state inverse 108 /// diffs tree. The function will fail if the block height number 109 /// was not found. 110 pub fn get_state_inverse_diff(&self, height: &u32) -> Result<SledDbOverlayStateDiff> { 111 match self.state_inverse_diff.get(height.to_be_bytes())? { 112 Some(found) => Ok(deserialize(&found)?), 113 None => Err(Error::BlockStateInverseDiffNotFound(*height)), 114 } 115 } 116 } 117 118 /// Overlay structure over a [`Cache`] instance. 119 pub struct CacheOverlay(pub SledDbOverlay); 120 121 impl CacheOverlay { 122 /// Instantiate a new `CacheOverlay` over the given [`Cache`] instance. 123 pub fn new(cache: &Cache) -> Result<CacheOverlay> { 124 // Here we configure all our cache sled trees to be protected in the overlay 125 let protected_trees = vec![ 126 SLED_SCANNED_BLOCKS_TREE, 127 SLED_STATE_INVERSE_DIFF_TREE, 128 SLED_MERKLE_TREES_TREE, 129 SLED_MONEY_SMT_TREE, 130 ]; 131 let mut overlay = SledDbOverlay::new(&cache.sled_db, protected_trees); 132 133 // Open all our cache sled trees in the overlay 134 overlay.open_tree(SLED_SCANNED_BLOCKS_TREE, true)?; 135 overlay.open_tree(SLED_STATE_INVERSE_DIFF_TREE, true)?; 136 overlay.open_tree(SLED_MERKLE_TREES_TREE, true)?; 137 overlay.open_tree(SLED_MONEY_SMT_TREE, true)?; 138 139 Ok(Self(overlay)) 140 } 141 142 /// Insert a `u32` and a block hash into overlay's scanned blocks 143 /// tree. The block height is used as the key, and the serialized 144 /// blockhash string is used as value. 145 pub fn insert_scanned_block(&mut self, height: &u32, hash: &HeaderHash) -> Result<()> { 146 self.0.insert( 147 SLED_SCANNED_BLOCKS_TREE, 148 &height.to_be_bytes(), 149 &serialize(&hash.to_string()), 150 )?; 151 Ok(()) 152 } 153 } 154 155 pub type CacheSmt = SparseMerkleTree< 156 'static, 157 SMT_FP_DEPTH, 158 { SMT_FP_DEPTH + 1 }, 159 pallas::Base, 160 PoseidonFp, 161 CacheSmtStorage, 162 >; 163 164 pub struct CacheSmtStorage { 165 pub overlay: CacheOverlay, 166 tree: Vec<u8>, 167 } 168 169 impl CacheSmtStorage { 170 pub fn new(overlay: CacheOverlay, tree: &[u8]) -> Self { 171 Self { overlay, tree: tree.to_vec() } 172 } 173 174 pub fn snapshot(&self) -> Result<HashMap<BigUint, pallas::Base>> { 175 let mut smt = HashMap::new(); 176 for record in self.overlay.0.iter(&self.tree)? { 177 let (key, value) = record?; 178 let mut repr = [0; 32]; 179 repr.copy_from_slice(&value); 180 let Some(value) = pallas::Base::from_repr(repr).into() else { 181 return Err(Error::ParseFailed( 182 "[cache::CacheSmtStorage::snapshot] Value conversion failed", 183 )) 184 }; 185 smt.insert(BigUint::from_bytes_le(&key), value); 186 } 187 Ok(smt) 188 } 189 } 190 191 impl StorageAdapter for CacheSmtStorage { 192 type Value = pallas::Base; 193 194 fn put(&mut self, key: BigUint, value: pallas::Base) -> ContractResult { 195 if let Err(e) = self.overlay.0.insert(&self.tree, &key.to_bytes_le(), &value.to_repr()) { 196 error!(target: "cache::StorageAdapter::put", "Inserting key {key:?}, value {value:?} into DB failed: {e}"); 197 return Err(ContractError::SmtPutFailed) 198 } 199 Ok(()) 200 } 201 202 fn get(&self, key: &BigUint) -> Option<pallas::Base> { 203 let value = match self.overlay.0.get(&self.tree, &key.to_bytes_le()) { 204 Ok(v) => v, 205 Err(e) => { 206 error!(target: "cache::StorageAdapter::get", "Fetching key {key:?} from DB failed: {e}"); 207 return None 208 } 209 }; 210 211 let value = value?; 212 213 let mut repr = [0; 32]; 214 repr.copy_from_slice(&value); 215 216 pallas::Base::from_repr(repr).into() 217 } 218 219 fn del(&mut self, key: &BigUint) -> ContractResult { 220 if let Err(e) = self.overlay.0.remove(&self.tree, &key.to_bytes_le()) { 221 error!(target: "cache::StorageAdapter::del", "Removing key {key:?} from DB failed: {e}"); 222 return Err(ContractError::SmtDelFailed) 223 } 224 Ok(()) 225 } 226 } 227 228 #[cfg(test)] 229 mod tests { 230 use darkfi::{zk::halo2::Field, Result}; 231 use darkfi_sdk::{ 232 crypto::smt::{gen_empty_nodes, util::FieldHasher, PoseidonFp, SparseMerkleTree}, 233 pasta::pallas, 234 }; 235 use rand::rngs::OsRng; 236 use sled_overlay::sled; 237 238 use crate::cache::{Cache, CacheOverlay, CacheSmtStorage, SLED_MONEY_SMT_TREE}; 239 240 #[test] 241 fn test_cache_smt() -> Result<()> { 242 // Setup cache and its overlay 243 let sled_db = sled::Config::new().temporary(true).open()?; 244 let cache = Cache::new(&sled_db)?; 245 let overlay = CacheOverlay::new(&cache)?; 246 247 // Setup SMT 248 const HEIGHT: usize = 3; 249 let hasher = PoseidonFp::new(); 250 let empty_leaf = pallas::Base::ZERO; 251 let empty_nodes = gen_empty_nodes::<{ HEIGHT + 1 }, _, _>(&hasher, empty_leaf); 252 let store = CacheSmtStorage::new(overlay, SLED_MONEY_SMT_TREE); 253 let mut smt = SparseMerkleTree::<HEIGHT, { HEIGHT + 1 }, _, _, _>::new( 254 store, 255 hasher.clone(), 256 &empty_nodes, 257 ); 258 259 // Verify database is empty 260 assert!(cache.money_smt.is_empty()); 261 262 let leaves = vec![ 263 (pallas::Base::from(1), pallas::Base::random(&mut OsRng)), 264 (pallas::Base::from(2), pallas::Base::random(&mut OsRng)), 265 (pallas::Base::from(3), pallas::Base::random(&mut OsRng)), 266 ]; 267 smt.insert_batch(leaves.clone()).unwrap(); 268 269 let hash1 = leaves[0].1; 270 let hash2 = leaves[1].1; 271 let hash3 = leaves[2].1; 272 273 let hash = |l, r| hasher.hash([l, r]); 274 275 let hash01 = hash(empty_nodes[3], hash1); 276 let hash23 = hash(hash2, hash3); 277 278 let hash0123 = hash(hash01, hash23); 279 let root = hash(hash0123, empty_nodes[1]); 280 assert_eq!(root, smt.root()); 281 282 // Now try to construct a membership proof for leaf 3 283 let pos = leaves[2].0; 284 let path = smt.prove_membership(&pos); 285 assert_eq!(path.path[0], empty_nodes[1]); 286 assert_eq!(path.path[1], hash01); 287 assert_eq!(path.path[2], hash2); 288 289 assert_eq!(hash23, hash(path.path[2], hash3)); 290 assert_eq!(hash0123, hash(path.path[1], hash(path.path[2], hash3))); 291 assert_eq!(root, hash(hash(path.path[1], hash(path.path[2], hash3)), path.path[0])); 292 293 assert!(path.verify(&root, &hash3, &pos)); 294 295 // Grab the overlay diff 296 let diff = smt.store.overlay.0.diff(&[])?; 297 298 // Apply the overlay 299 smt.store.overlay.0.apply_diff(&diff)?; 300 301 // Verify database contains keys 302 assert!(!cache.money_smt.is_empty()); 303 304 // We are now going to rollback the changes 305 smt.store.overlay.0.apply_diff(&diff.inverse())?; 306 307 // Verify database is empty again 308 assert!(cache.money_smt.is_empty()); 309 310 Ok(()) 311 } 312 }