Просмотр исходного кода

DAO: changed nullifiers SMT logic to store a set of roots and updated propose entrypoint accordingly

skoupidi 2 лет назад
Родитель
Сommit
78eeefe1d5

+ 23 - 13
src/contract/dao/src/entrypoint/propose.rs

@@ -134,6 +134,13 @@ pub(crate) fn dao_propose_process_instruction(
             );
             return Err(DaoError::InvalidInputMerkleRoot.into())
         };
+        if coin_root_data.len() != 32 + 1 {
+            msg!(
+                "[Dao::Propose] Error: Coin roots data length is not expected(32 + 1): {}",
+                coin_root_data.len()
+            );
+            return Err(MoneyError::RootsValueDataMismatch.into())
+        }
 
         // Check the SMT roots for the input nullifiers are valid
         let Some(null_root_data) =
@@ -143,27 +150,30 @@ pub(crate) fn dao_propose_process_instruction(
             return Err(DaoError::InvalidInputMerkleRoot.into())
         };
 
-        // Both roots must snapshot the exact same state
-        if coin_root_data != null_root_data {
-            msg!("[Dao::Propose] Error: coin roots snapshot for {:?} does not match nulls root snapshot {:?}",
+        // Deserialize the SMT roots set
+        let null_root_data: Vec<Vec<u8>> = match deserialize(&null_root_data) {
+            Ok(set) => set,
+            Err(e) => {
+                msg!("[Dao::Propose] Error: Failed to deserialize nulls root snapshot: {}", e);
+                return Err(DaoError::SnapshotDeserializationError.into())
+            }
+        };
+
+        // Nullifiers roots snapshot must include the Merkle root data
+        if !null_root_data.contains(&coin_root_data) {
+            msg!("[Dao::Propose] Error: coin roots snapshot for {:?} does not exist in the nulls root snapshot {:?}",
                  input.merkle_coin_root.inner(), input.smt_null_root);
             return Err(DaoError::NonMatchingSnapshotRoots.into())
         }
 
-        if coin_root_data.len() != 32 + 1 {
-            msg!(
-                "[Dao::Propose] Error: Coin roots data length is not expected(32 + 1): {}",
-                coin_root_data.len()
-            );
-            return Err(MoneyError::RootsValueDataMismatch.into())
-        }
-
+        // Get block_height where tx_hash was confirmed
         let tx_hash_data: [u8; 32] = coin_root_data[0..32].try_into().unwrap();
         let tx_hash = TransactionHash(tx_hash_data);
-        // Get block_height where tx_hash was confirmed
         let (tx_height, _) = wasm::util::get_tx_location(&tx_hash)?;
+
+        // Check snapshot age againts current height
         let current_height = wasm::util::get_verifying_block_height()?;
-        if current_height - tx_height as u32 > PROPOSAL_SNAPSHOT_CUTOFF_LIMIT {
+        if current_height - tx_height > PROPOSAL_SNAPSHOT_CUTOFF_LIMIT {
             msg!("[Dao::Propose] Error: Snapshot is too old. Current height: {}, snapshot height: {}",
                  current_height, tx_height);
             return Err(DaoError::SnapshotTooOld.into())

+ 22 - 18
src/contract/dao/src/error.rs

@@ -38,6 +38,9 @@ pub enum DaoError {
     #[error("Snapshoot is past the cutoff limit")]
     SnapshotTooOld,
 
+    #[error("Failed to deserialize snapshot")]
+    SnapshotDeserializationError,
+
     #[error("Invalid DAO Merkle root")]
     InvalidDaoMerkleRoot,
 
@@ -102,24 +105,25 @@ impl From<DaoError> for ContractError {
             DaoError::InvalidInputMerkleRoot => Self::Custom(4),
             DaoError::NonMatchingSnapshotRoots => Self::Custom(5),
             DaoError::SnapshotTooOld => Self::Custom(6),
-            DaoError::InvalidDaoMerkleRoot => Self::Custom(7),
-            DaoError::ProposalAlreadyExists => Self::Custom(8),
-            DaoError::VoteInputsEmpty => Self::Custom(9),
-            DaoError::ProposalNonexistent => Self::Custom(10),
-            DaoError::ProposalEnded => Self::Custom(11),
-            DaoError::CoinAlreadySpent => Self::Custom(12),
-            DaoError::DoubleVote => Self::Custom(13),
-            DaoError::ExecCallWrongChildCallsLen => Self::Custom(14),
-            DaoError::ExecCallWrongChildCall => Self::Custom(15),
-            DaoError::ExecCallInvalidFormat => Self::Custom(16),
-            DaoError::ExecCallValueMismatch => Self::Custom(17),
-            DaoError::VoteCommitMismatch => Self::Custom(18),
-            DaoError::AuthXferSiblingWrongContractId => Self::Custom(19),
-            DaoError::AuthXferSiblingWrongFunctionCode => Self::Custom(20),
-            DaoError::AuthXferNonMatchingEncInputUserData => Self::Custom(21),
-            DaoError::AuthXferCallNotFoundInParent => Self::Custom(22),
-            DaoError::AuthXferWrongNumberOutputs => Self::Custom(23),
-            DaoError::AuthXferWrongOutputCoin => Self::Custom(24),
+            DaoError::SnapshotDeserializationError => Self::Custom(7),
+            DaoError::InvalidDaoMerkleRoot => Self::Custom(8),
+            DaoError::ProposalAlreadyExists => Self::Custom(9),
+            DaoError::VoteInputsEmpty => Self::Custom(10),
+            DaoError::ProposalNonexistent => Self::Custom(11),
+            DaoError::ProposalEnded => Self::Custom(12),
+            DaoError::CoinAlreadySpent => Self::Custom(13),
+            DaoError::DoubleVote => Self::Custom(14),
+            DaoError::ExecCallWrongChildCallsLen => Self::Custom(15),
+            DaoError::ExecCallWrongChildCall => Self::Custom(16),
+            DaoError::ExecCallInvalidFormat => Self::Custom(17),
+            DaoError::ExecCallValueMismatch => Self::Custom(18),
+            DaoError::VoteCommitMismatch => Self::Custom(19),
+            DaoError::AuthXferSiblingWrongContractId => Self::Custom(20),
+            DaoError::AuthXferSiblingWrongFunctionCode => Self::Custom(21),
+            DaoError::AuthXferNonMatchingEncInputUserData => Self::Custom(22),
+            DaoError::AuthXferCallNotFoundInParent => Self::Custom(23),
+            DaoError::AuthXferWrongNumberOutputs => Self::Custom(24),
+            DaoError::AuthXferWrongOutputCoin => Self::Custom(25),
         }
     }
 }

+ 6 - 2
src/contract/money/src/entrypoint.rs

@@ -146,10 +146,14 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     }
 
     // Set up a database tree to hold Merkle roots of all nullifier trees
-    // k=root_hash:32, v=(tx_hash:32, call_idx: 1)
+    // k=root_hash:32, v=[(tx_hash:32, call_idx: 1)]
     if wasm::db::db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE).is_err() {
         let db_null_roots = wasm::db::db_init(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
-        wasm::db::db_set(db_null_roots, &serialize(&EMPTY_NODES_FP[0]), &roots_value_data)?;
+        wasm::db::db_set(
+            db_null_roots,
+            &serialize(&EMPTY_NODES_FP[0]),
+            &serialize(&vec![roots_value_data]),
+        )?;
     }
 
     // Set up a database tree to hold all coins ever seen

+ 1 - 1
src/contract/money/src/entrypoint/genesis_mint_v1.rs

@@ -150,7 +150,7 @@ pub(crate) fn money_genesis_mint_process_update_v1(
         nullifiers_db,
         nullifier_roots_db,
         MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
-        &vec![],
+        &[],
     )?;
 
     msg!("[GenesisMintV1] Adding new coin to the set");

+ 1 - 1
src/contract/money/src/entrypoint/pow_reward_v1.rs

@@ -187,7 +187,7 @@ pub(crate) fn money_pow_reward_process_update_v1(
         nullifiers_db,
         nullifier_roots_db,
         MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
-        &vec![],
+        &[],
     )?;
 
     msg!("[PoWRewardV1] Adding new coin to the set");

+ 1 - 1
src/contract/money/src/entrypoint/token_mint_v1.rs

@@ -126,7 +126,7 @@ pub(crate) fn money_token_mint_process_update_v1(
         nullifiers_db,
         nullifier_roots_db,
         MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
-        &vec![],
+        &[],
     )?;
 
     msg!("[TokenMintV1] Adding new coin to the set");

+ 1 - 1
src/contract/money/src/entrypoint/transfer_v1.rs

@@ -58,7 +58,7 @@ pub(crate) fn money_transfer_get_metadata_v1(
     let mut signature_pubkeys: Vec<PublicKey> = vec![];
 
     // Calculate the spend hook
-    let spend_hook = match calls[call_idx as usize].parent_index {
+    let spend_hook = match calls[call_idx].parent_index {
         Some(parent_idx) => {
             let parent_call = &calls[parent_idx].data;
             let contract_id = parent_call.contract_id;

+ 84 - 20
src/runtime/import/smt.rs

@@ -26,7 +26,7 @@ use darkfi_sdk::{
     error::{ContractError, ContractResult},
     wasm,
 };
-use darkfi_serial::{serialize, Decodable, Encodable};
+use darkfi_serial::{deserialize, serialize, Decodable, Encodable};
 use halo2_proofs::pasta::pallas;
 use log::{debug, error};
 use num_bigint::BigUint;
@@ -237,15 +237,11 @@ pub(crate) fn sparse_merkle_insert_batch(
         return darkfi_sdk::error::INTERNAL_ERROR
     }
 
+    // Generate the SledStorage SMT
     let hasher = PoseidonFp::new();
-    let leaves: Vec<_> = nullifiers.into_iter().map(|x| (x, x)).collect();
-    // Used in gas calc
-    let leaves_len = leaves.len();
-
     let lock = env.blockchain.lock().unwrap();
     let mut overlay = lock.overlay.lock().unwrap();
     let smt_store = SledStorage { overlay: &mut overlay, tree_key: &db_smt.tree };
-
     let mut smt = SparseMerkleTree::<
         SMT_FP_DEPTH,
         { SMT_FP_DEPTH + 1 },
@@ -253,6 +249,12 @@ pub(crate) fn sparse_merkle_insert_batch(
         PoseidonFp,
         SledStorage,
     >::new(smt_store, hasher, &EMPTY_NODES_FP);
+
+    // Count the nullifiers for gas calculation
+    let inserted_nullifiers = nullifiers.len() * 32;
+
+    // Insert the new nullifiers
+    let leaves: Vec<_> = nullifiers.iter().map(|x| (*x, *x)).collect();
     if let Err(e) = smt.insert_batch(leaves) {
         error!(
             target: "runtime::smt::sparse_merkle_insert_batch",
@@ -261,23 +263,87 @@ pub(crate) fn sparse_merkle_insert_batch(
         return darkfi_sdk::error::INTERNAL_ERROR
     };
 
-    // Here we add the SMT root to our set of roots
+    // Grab the current SMT root to add in 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();
 
+    // Validate latest root data, to ensure their integrity
+    let latest_root_data = serialize(&latest_root);
+    if latest_root_data.len() != 32 {
+        error!(
+            target: "runtime::smt::sparse_merkle_insert_batch",
+            "[WASM] [{}] sparse_merkle_insert_batch(): Latest root data length missmatch: {}", cid, latest_root_data.len(),
+        );
+        return darkfi_sdk::error::INTERNAL_ERROR
+    }
+
+    // Validate the new value data, to ensure their integrity
+    let mut new_value_data = Vec::with_capacity(32 + 1);
+    if let Err(e) = env.tx_hash.inner().encode(&mut new_value_data) {
+        error!(
+            target: "runtime::smt::sparse_merkle_insert_batch",
+            "[WASM] [{}] sparse_merkle_insert_batch(): Failed to serialize transaction hash: {}", cid, e,
+        );
+        return darkfi_sdk::error::INTERNAL_ERROR
+    };
+    if let Err(e) = env.call_idx.encode(&mut new_value_data) {
+        error!(
+            target: "runtime::smt::sparse_merkle_insert_batch",
+            "[WASM] [{}] sparse_merkle_insert_batch(): Failed to serialize call index: {}", cid, e,
+        );
+        return darkfi_sdk::error::INTERNAL_ERROR
+    };
+    if new_value_data.len() != 32 + 1 {
+        error!(
+            target: "runtime::smt::sparse_merkle_insert_batch",
+            "[WASM] [{}] sparse_merkle_insert_batch(): New value data length missmatch: {}", cid, new_value_data.len(),
+        );
+        return darkfi_sdk::error::INTERNAL_ERROR
+    }
+
+    // Retrieve snapshot root data set
+    let root_value_data_set = match overlay.get(&db_roots.tree, &latest_root_data) {
+        Ok(data) => data,
+        Err(e) => {
+            error!(
+                target: "runtime::smt::sparse_merkle_insert_batch",
+                "[WASM] [{}] sparse_merkle_insert_batch(): SMT failed to retrieve current root snapshot: {}", cid, e,
+            );
+            return darkfi_sdk::error::INTERNAL_ERROR
+        }
+    };
+
+    // If the record exists, append the new value data,
+    // otherwise create a new set with it.
+    let root_value_data_set = match root_value_data_set {
+        Some(value_data_set) => {
+            let mut value_data_set: Vec<Vec<u8>> = match deserialize(&value_data_set) {
+                Ok(set) => set,
+                Err(e) => {
+                    error!(
+                        target: "runtime::smt::sparse_merkle_insert_batch",
+                        "[WASM] [{}] sparse_merkle_insert_batch(): Failed to deserialize current root snapshot: {}", cid, e,
+                    );
+                    return darkfi_sdk::error::INTERNAL_ERROR
+                }
+            };
+
+            if !value_data_set.contains(&new_value_data) {
+                value_data_set.push(new_value_data);
+            }
+
+            value_data_set
+        }
+        None => vec![new_value_data],
+    };
+
+    // Write the latest root snapshot
     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 mut value_data = Vec::with_capacity(32 + 1);
-    env.tx_hash.inner().encode(&mut value_data).expect("Unable to serialize tx_hash");
-    env.call_idx.encode(&mut value_data).expect("Unable to serialize call_idx");
-    assert_eq!(value_data.len(), 32 + 1);
-
-    if overlay.insert(&db_roots.tree, &latest_root_data, &value_data).is_err() {
+    if overlay.insert(&db_roots.tree, &latest_root_data, &serialize(&root_value_data_set)).is_err()
+    {
         error!(
             target: "runtime::smt::sparse_merkle_insert_batch",
             "[WASM] [{}] sparse_merkle_insert_batch(): Couldn't insert to db_roots tree", cid,
@@ -285,12 +351,11 @@ pub(crate) fn sparse_merkle_insert_batch(
         return darkfi_sdk::error::INTERNAL_ERROR
     }
 
-    // Write a pointer to the latest known root
+    // Update the 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",
@@ -305,8 +370,7 @@ pub(crate) fn sparse_merkle_insert_batch(
     drop(overlay);
     drop(lock);
     drop(db_handles);
-    let spent_gas = leaves_len * 32;
-    env.subtract_gas(&mut store, spent_gas as u64);
+    env.subtract_gas(&mut store, inserted_nullifiers as u64);
 
     wasm::entrypoint::SUCCESS
 }