diff --git a/src/contract/money/src/entrypoint.rs b/src/contract/money/src/entrypoint.rs index 84f402b2c..4b6100362 100644 --- a/src/contract/money/src/entrypoint.rs +++ b/src/contract/money/src/entrypoint.rs @@ -35,8 +35,8 @@ use crate::{ }, MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_DB_VERSION, MONEY_CONTRACT_INFO_TREE, - MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE, - MONEY_CONTRACT_TOTAL_FEES_PAID, + MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, + MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, }; /// `Money::Fee` functions @@ -123,12 +123,18 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult { zkas_db_set(&token_mint_v1_bincode[..])?; zkas_db_set(&token_frz_v1_bincode[..])?; - // Set up a database tree to hold Merkle roots of all coins + // Set up a database tree to hold Merkle roots of all coin trees // k=MerkleNode, v=[] if db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE).is_err() { db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?; } + // Set up a database tree to hold Merkle roots of all nullifier trees + // k=MerkleNode, v=[] + if db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE).is_err() { + db_init(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?; + } + // Set up a database tree to hold all coins ever seen // k=Coin, v=[] if db_lookup(cid, MONEY_CONTRACT_COINS_TREE).is_err() { diff --git a/src/contract/money/src/entrypoint/fee_v1.rs b/src/contract/money/src/entrypoint/fee_v1.rs index 4bd5a1ab2..6389625fb 100644 --- a/src/contract/money/src/entrypoint/fee_v1.rs +++ b/src/contract/money/src/entrypoint/fee_v1.rs @@ -41,7 +41,9 @@ use crate::{ model::{MoneyFeeParamsV1, MoneyFeeUpdateV1, DARK_TOKEN_ID}, MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT, - MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, MONEY_CONTRACT_ZKAS_FEE_NS_V1, + MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE, + MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, + MONEY_CONTRACT_ZKAS_FEE_NS_V1, }; /// `get_metadata` function for `Money::FeeV1` @@ -205,10 +207,17 @@ pub(crate) fn money_fee_process_update_v1( let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?; let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?; let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?; + let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?; db_set(info_db, MONEY_CONTRACT_TOTAL_FEES_PAID, &serialize(&update.fee))?; - sparse_merkle_insert_batch(nullifiers_db, &[update.nullifier.inner()])?; + sparse_merkle_insert_batch( + info_db, + nullifiers_db, + nullifier_roots_db, + MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, + &[update.nullifier.inner()], + )?; db_set(coins_db, &serialize(&update.coin), &[])?; diff --git a/src/contract/money/src/entrypoint/transfer_v1.rs b/src/contract/money/src/entrypoint/transfer_v1.rs index ae879f182..de5c494e4 100644 --- a/src/contract/money/src/entrypoint/transfer_v1.rs +++ b/src/contract/money/src/entrypoint/transfer_v1.rs @@ -40,7 +40,9 @@ use crate::{ model::{MoneyTransferParamsV1, MoneyTransferUpdateV1}, MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT, - MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1, + MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE, + MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, + MONEY_CONTRACT_ZKAS_MINT_NS_V1, }; /// `get_metadata` function for `Money::TransferV1` @@ -234,10 +236,14 @@ pub(crate) fn money_transfer_process_update_v1( let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?; let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?; let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?; + let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?; msg!("[TransferV1] Adding new nullifiers to the set"); sparse_merkle_insert_batch( + info_db, nullifiers_db, + nullifier_roots_db, + MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, &update.nullifiers.iter().map(|n| n.inner()).collect::>(), )?; diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs index ddd2c6f8c..01e9dc816 100644 --- a/src/contract/money/src/lib.rs +++ b/src/contract/money/src/lib.rs @@ -73,12 +73,14 @@ pub const MONEY_CONTRACT_INFO_TREE: &str = "info"; pub const MONEY_CONTRACT_COINS_TREE: &str = "coins"; pub const MONEY_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots"; pub const MONEY_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers"; +pub const MONEY_CONTRACT_NULLIFIER_ROOTS_TREE: &str = "nullifier_roots"; pub const MONEY_CONTRACT_TOKEN_FREEZE_TREE: &str = "token_freezes"; // These are keys inside the info tree pub const MONEY_CONTRACT_DB_VERSION: &[u8] = b"db_version"; -pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &[u8] = b"coin_tree"; -pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_root"; +pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &[u8] = b"coins_tree"; +pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_coins_root"; +pub const MONEY_CONTRACT_LATEST_NULLIFIER_ROOT: &[u8] = b"last_nullifiers_root"; pub const MONEY_CONTRACT_TOTAL_FEES_PAID: &[u8] = b"total_fees_paid"; /// zkas fee circuit namespace diff --git a/src/runtime/import/merkle.rs b/src/runtime/import/merkle.rs index 6ec59058e..476ba1624 100644 --- a/src/runtime/import/merkle.rs +++ b/src/runtime/import/merkle.rs @@ -279,10 +279,23 @@ pub(crate) fn merkle_add(mut ctx: FunctionEnvMut, ptr: WasmPtr, len: u3 ); let latest_root_data = serialize(latest_root); assert_eq!(latest_root_data.len(), 32); - let blockheight_data = serialize(&env.verifying_block_height); - assert_eq!(blockheight_data.len(), 8); - if overlay.insert(&db_roots.tree, &latest_root_data, &blockheight_data).is_err() { + let blockheight_data = serialize(&(env.verifying_block_height as u32)); + // This is hardcoded but should not be + let tx_idx: u16 = 0; + let call_idx: u16 = 0; + + assert_eq!(blockheight_data.len(), 4); + // Little-endian + assert_eq!(blockheight_data[3], 0); + + let mut value_data = Vec::with_capacity(7); + value_data.write_slice(&blockheight_data[..3]).expect("Unable to serialize blockheight data"); + tx_idx.encode(&mut value_data).expect("Unable to serialize tx_id"); + call_idx.encode(&mut value_data).expect("Unable to serialize call_idx"); + assert_eq!(value_data.len(), 7); + + if overlay.insert(&db_roots.tree, &latest_root_data, &value_data).is_err() { error!( target: "runtime::merkle::merkle_add", "[WASM] [{}] merkle_add(): Couldn't insert to db_roots tree", cid, diff --git a/src/runtime/import/smt.rs b/src/runtime/import/smt.rs index b2ee0fade..75fe9c1dc 100644 --- a/src/runtime/import/smt.rs +++ b/src/runtime/import/smt.rs @@ -22,9 +22,9 @@ use darkfi_sdk::crypto::{ pasta_prelude::*, smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, EMPTY_NODES_FP, SMT_FP_DEPTH}, }; -use darkfi_serial::Decodable; +use darkfi_serial::{serialize, Decodable, Encodable, WriteExt}; use halo2_proofs::pasta::pallas; -use log::{error, warn}; +use log::{debug, error, warn}; use num_bigint::BigUint; use wasmer::{FunctionEnvMut, WasmPtr}; @@ -112,8 +112,21 @@ pub(crate) fn sparse_merkle_insert_batch( // The buffer should deserialize into: // - db_smt + // - db_roots // - nullifiers (as Vec) let mut buf_reader = Cursor::new(buf); + let db_info_index: u32 = match Decodable::decode(&mut buf_reader) { + Ok(v) => v, + Err(e) => { + error!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode db_info DbHandle: {}", cid, e, + ); + return darkfi_sdk::error::INTERNAL_ERROR + } + }; + let db_info_index = db_info_index as usize; + let db_smt_index: u32 = match Decodable::decode(&mut buf_reader) { Ok(v) => v, Err(e) => { @@ -126,20 +139,37 @@ pub(crate) fn sparse_merkle_insert_batch( }; let db_smt_index = db_smt_index as usize; + let db_roots_index: u32 = match Decodable::decode(&mut buf_reader) { + Ok(v) => v, + Err(e) => { + error!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode db_roots DbHandle: {}", cid, e, + ); + return darkfi_sdk::error::INTERNAL_ERROR + } + }; + let db_roots_index = db_roots_index as usize; + let db_handles = env.db_handles.borrow(); let n_dbs = db_handles.len(); - if n_dbs <= db_smt_index { + if n_dbs <= db_info_index || n_dbs <= db_smt_index || n_dbs <= db_roots_index { error!( target: "runtime::smt::sparse_merkle_insert_batch", "[WASM] [{}] sparse_merkle_insert_batch(): Requested DbHandle that is out of bounds", cid, ); return darkfi_sdk::error::INTERNAL_ERROR } + let db_info = &db_handles[db_info_index]; let db_smt = &db_handles[db_smt_index]; + let db_roots = &db_handles[db_roots_index]; // Make sure that the contract owns the dbs it wants to write to - if db_smt.contract_id != env.contract_id { + if db_info.contract_id != env.contract_id || + db_smt.contract_id != env.contract_id || + db_roots.contract_id != env.contract_id + { error!( target: "runtime::smt::sparse_merkle_insert_batch", "[WASM] [{}] sparse_merkle_insert_batch(): Unauthorized to write to DbHandle", cid, @@ -147,6 +177,18 @@ pub(crate) fn sparse_merkle_insert_batch( return darkfi_sdk::error::CALLER_ACCESS_DENIED } + // This `key` represents the sled key in info where the latest root is + let root_key: Vec = match Decodable::decode(&mut buf_reader) { + Ok(v) => v, + Err(e) => { + error!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode key vec: {}", cid, e, + ); + return darkfi_sdk::error::INTERNAL_ERROR + } + }; + // This `nullifier` represents the leaf we're adding to the Merkle tree let nullifiers: Vec = match Decodable::decode(&mut buf_reader) { Ok(v) => v, @@ -201,6 +243,54 @@ pub(crate) fn sparse_merkle_insert_batch( return darkfi_sdk::error::INTERNAL_ERROR }; + // Here we add the SMT root to our set of roots + // Since each update to the tree is atomic, we only need to add the last root. + let latest_root = smt.root(); + + debug!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Appending SMT root to db: {:?}", cid, latest_root, + ); + let latest_root_data = serialize(&latest_root); + assert_eq!(latest_root_data.len(), 32); + + let blockheight_data = serialize(&(env.verifying_block_height as u32)); + // This is hardcoded but should not be + let tx_idx: u16 = 0; + let call_idx: u16 = 0; + + assert_eq!(blockheight_data.len(), 4); + // Little-endian + assert_eq!(blockheight_data[3], 0); + + let mut value_data = Vec::with_capacity(7); + value_data.write_slice(&blockheight_data[..3]).expect("Unable to serialize blockheight data"); + tx_idx.encode(&mut value_data).expect("Unable to serialize tx_id"); + call_idx.encode(&mut value_data).expect("Unable to serialize call_idx"); + assert_eq!(value_data.len(), 7); + + if overlay.insert(&db_roots.tree, &latest_root_data, &value_data).is_err() { + error!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Couldn't insert to db_roots tree", cid, + ); + return darkfi_sdk::error::INTERNAL_ERROR + } + + // Write a pointer to the latest known root + debug!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Replacing latest SMT root pointer", cid, + ); + + if overlay.insert(&db_info.tree, &root_key, &latest_root_data).is_err() { + error!( + target: "runtime::smt::sparse_merkle_insert_batch", + "[WASM] [{}] sparse_merkle_insert_batch(): Couldn't insert latest root to db_info tree", cid, + ); + return darkfi_sdk::error::INTERNAL_ERROR + } + // Subtract used gas. // Here we count: // * The number of nullifiers we inserted into the DB diff --git a/src/sdk/src/merkle.rs b/src/sdk/src/merkle.rs index a3ce14d1c..6c4f0c02b 100644 --- a/src/sdk/src/merkle.rs +++ b/src/sdk/src/merkle.rs @@ -71,12 +71,18 @@ pub fn merkle_add( } pub fn sparse_merkle_insert_batch( + db_info: DbHandle, db_smt: DbHandle, + db_roots: DbHandle, + root_key: &[u8], elements: &[pallas::Base], ) -> GenericResult<()> { let mut buf = vec![]; let mut len = 0; + len += db_info.encode(&mut buf)?; len += db_smt.encode(&mut buf)?; + len += db_roots.encode(&mut buf)?; + len += root_key.to_vec().encode(&mut buf)?; len += elements.to_vec().encode(&mut buf)?; match unsafe { sparse_merkle_insert_batch_(buf.as_ptr(), len as u32) } {