From 855d5935dd94660027d3dddb14163e5c0610e9e9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 16:12:00 +0700 Subject: [PATCH 01/25] feat(drive): add shielded pool storage, actions, and verification Adds the rs-drive layer for Orchard shielded transactions (Medusa): - Shielded pool GroveDB trees: commitment tree for encrypted notes, nullifier storage, anchor tracking, and pool balance - State transition actions for all 5 shielded transition types (shield, shield-from-asset-lock, shielded-transfer, unshield, shielded-withdrawal) plus penalize_shielded_pool system action - Drive operations: store/fetch/compact/cleanup nullifiers, batch insert into commitment trees, count tree operations - Proof generation and verification for shielded state (nullifiers, anchors, encrypted notes, pool balance) - Estimated costs for shielded operations Co-Authored-By: Claude Opus 4.6 --- .../rs-drive/src/drive/credit_pools/mod.rs | 2 +- .../v0/mod.rs | 2 +- .../src/drive/initialization/v3/mod.rs | 139 ++++++++- packages/rs-drive/src/drive/mod.rs | 3 + .../cleanup_expired_nullifiers/mod.rs | 49 ++++ .../cleanup_expired_nullifiers/v0/mod.rs | 95 ++++++ .../compact_nullifiers/mod.rs | 61 ++++ .../compact_nullifiers/v0/mod.rs | 194 ++++++++++++ .../fetch_compacted_nullifiers/mod.rs | 85 ++++++ .../fetch_compacted_nullifiers/v0/mod.rs | 277 ++++++++++++++++++ .../fetch_nullifiers/mod.rs | 85 ++++++ .../fetch_nullifiers/v0/mod.rs | 101 +++++++ .../src/drive/saved_block_transactions/mod.rs | 9 + .../drive/saved_block_transactions/queries.rs | 66 +++++ .../store_nullifiers/mod.rs | 55 ++++ .../store_nullifiers/v0/mod.rs | 154 ++++++++++ .../src/drive/shielded/estimated_costs.rs | 137 +++++++++ packages/rs-drive/src/drive/shielded/mod.rs | 11 + packages/rs-drive/src/drive/shielded/paths.rs | 117 ++++++++ .../rs-drive/src/drive/shielded/prove/mod.rs | 5 + .../prove_nullifiers_branch_query/mod.rs | 93 ++++++ .../prove_nullifiers_branch_query/v0/mod.rs | 70 +++++ .../prove/prove_nullifiers_trunk_query/mod.rs | 74 +++++ .../prove_nullifiers_trunk_query/v0/mod.rs | 56 ++++ packages/rs-drive/src/fees/op.rs | 15 +- .../prove/prove_state_transition/v0/mod.rs | 109 ++++++- .../action_convert_to_operations/mod.rs | 21 ++ .../shielded/mod.rs | 60 ++++ .../shield_from_asset_lock_transition.rs | 78 +++++ .../shielded/shield_transition.rs | 64 ++++ .../shielded/shielded_transfer_transition.rs | 59 ++++ .../shielded_withdrawal_transition.rs | 86 ++++++ .../shielded/unshield_transition.rs | 74 +++++ .../system/mod.rs | 1 + .../system/penalize_shielded_pool.rs | 38 +++ .../src/state_transition_action/mod.rs | 36 +++ .../state_transition_action/shielded/mod.rs | 21 ++ .../shielded/shield/mod.rs | 54 ++++ .../shielded/shield/transformer.rs | 32 ++ .../shielded/shield/v0/mod.rs | 24 ++ .../shielded/shield/v0/transformer.rs | 27 ++ .../shielded/shield_from_asset_lock/mod.rs | 53 ++++ .../shield_from_asset_lock/transformer.rs | 34 +++ .../shielded/shield_from_asset_lock/v0/mod.rs | 21 ++ .../shield_from_asset_lock/v0/transformer.rs | 27 ++ .../shielded/shielded_transfer/mod.rs | 37 +++ .../shielded/shielded_transfer/transformer.rs | 30 ++ .../shielded/shielded_transfer/v0/mod.rs | 17 ++ .../shielded_transfer/v0/transformer.rs | 23 ++ .../shielded/shielded_withdrawal/mod.rs | 80 +++++ .../shielded_withdrawal/transformer.rs | 30 ++ .../shielded/shielded_withdrawal/v0/mod.rs | 30 ++ .../shielded_withdrawal/v0/transformer.rs | 77 +++++ .../shielded/unshield/mod.rs | 50 ++++ .../shielded/unshield/transformer.rs | 28 ++ .../shielded/unshield/v0/mod.rs | 22 ++ .../shielded/unshield/v0/transformer.rs | 24 ++ .../transformer.rs | 23 ++ .../v0/transformer.rs | 18 ++ .../src/state_transition_action/system/mod.rs | 3 + .../partially_use_asset_lock_action/mod.rs | 3 +- .../partially_use_asset_lock_action/v0/mod.rs | 1 + .../penalize_shielded_pool_action/mod.rs | 40 +++ .../penalize_shielded_pool_action/v0/mod.rs | 12 + .../apply_drive_operations/v0/mod.rs | 2 +- .../batch/drive_op_batch/finalize_task.rs | 10 +- .../src/util/batch/drive_op_batch/mod.rs | 19 +- .../src/util/batch/drive_op_batch/shielded.rs | 119 ++++++++ .../src/util/batch/grovedb_op_batch/mod.rs | 2 +- .../mod.rs | 44 +++ .../v0/mod.rs | 63 ++++ .../grove_commitment_tree_count/mod.rs | 54 ++++ .../grove_commitment_tree_count/v0/mod.rs | 27 ++ .../grove_get_proved_path_query_v1/mod.rs | 46 +++ .../grove_get_proved_path_query_v1/v0/mod.rs | 25 ++ .../grove_insert_empty_tree/v0/mod.rs | 7 +- .../rs-drive/src/util/grove_operations/mod.rs | 8 + .../v0/mod.rs | 47 ++- packages/rs-drive/src/verify/mod.rs | 2 + packages/rs-drive/src/verify/shielded/mod.rs | 16 + .../verify_compacted_nullifier_changes/mod.rs | 56 ++++ .../v0/mod.rs | 207 +++++++++++++ .../verify_nullifiers_branch_query/mod.rs | 58 ++++ .../verify_nullifiers_branch_query/v0/mod.rs | 48 +++ .../verify_nullifiers_trunk_query/mod.rs | 50 ++++ .../verify_nullifiers_trunk_query/v0/mod.rs | 36 +++ .../verify_recent_nullifier_changes/mod.rs | 57 ++++ .../verify_recent_nullifier_changes/v0/mod.rs | 83 ++++++ .../shielded/verify_shielded_anchors/mod.rs | 31 ++ .../verify_shielded_anchors/v0/mod.rs | 52 ++++ .../verify_shielded_encrypted_notes/mod.rs | 41 +++ .../verify_shielded_encrypted_notes/v0/mod.rs | 96 ++++++ .../verify_shielded_nullifiers/mod.rs | 37 +++ .../verify_shielded_nullifiers/v0/mod.rs | 52 ++++ .../verify_shielded_pool_state/mod.rs | 33 +++ .../verify_shielded_pool_state/v0/mod.rs | 67 +++++ .../v0/mod.rs | 231 ++++++++++++++- 97 files changed, 5098 insertions(+), 50 deletions(-) create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/estimated_costs.rs create mode 100644 packages/rs-drive/src/drive/shielded/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/paths.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_from_asset_lock_transition.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_transition.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs create mode 100644 packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/unshield/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/unshield/v0/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs create mode 100644 packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/mod.rs create mode 100644 packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/v0/mod.rs create mode 100644 packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs create mode 100644 packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs create mode 100644 packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs create mode 100644 packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/mod.rs create mode 100644 packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/v0/mod.rs create mode 100644 packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/mod.rs create mode 100644 packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_anchors/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_anchors/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/mod.rs create mode 100644 packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/v0/mod.rs diff --git a/packages/rs-drive/src/drive/credit_pools/mod.rs b/packages/rs-drive/src/drive/credit_pools/mod.rs index 6d48db1634b..0d8e3f29726 100644 --- a/packages/rs-drive/src/drive/credit_pools/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/mod.rs @@ -261,7 +261,7 @@ mod tests { for (i, operation) in batch.into_iter().enumerate() { assert_eq!( - operation.key.expect("key should be present").get_key(), + operation.key.expect("expected key").get_key(), KEY_POOL_STORAGE_FEES ); diff --git a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs index 14107e8d863..ed2b1a5d8e3 100644 --- a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs @@ -128,7 +128,7 @@ mod tests { assert_eq!(operation.path.to_path(), pending_epoch_refunds_path_vec()); - let epoch_index_key = operation.key.expect("key should be present").get_key(); + let epoch_index_key = operation.key.expect("expected key").get_key(); let epoch_index = u16::from_be_bytes( epoch_index_key .try_into() diff --git a/packages/rs-drive/src/drive/initialization/v3/mod.rs b/packages/rs-drive/src/drive/initialization/v3/mod.rs index c53a438148d..68773d95eab 100644 --- a/packages/rs-drive/src/drive/initialization/v3/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v3/mod.rs @@ -1,23 +1,140 @@ -//! Drive Initialization v3 — adds shielded pool trees. +//! Drive Initialization -use crate::drive::Drive; +use crate::drive::saved_block_transactions::{ + COMPACTED_NULLIFIERS_KEY_U8, NULLIFIERS_EXPIRATION_TIME_KEY_U8, NULLIFIERS_KEY_U8, +}; +use crate::drive::shielded::paths::*; +use crate::drive::{Drive, RootTree}; use crate::error::Error; +use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; +use crate::util::batch::GroveDbOpBatch; use dpp::version::PlatformVersion; -use grovedb::TransactionArg; +use grovedb::{Element, TransactionArg, TreeType}; +use grovedb_path::SubtreePath; impl Drive { - /// Creates the initial state structure (v3). - /// - /// Extends v2 by initializing shielded pool trees (commitment tree, - /// nullifier set, anchor history). + /// Creates the initial state structure. pub(super) fn create_initial_state_structure_v3( &self, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result<(), Error> { - // v3 currently delegates to v2. - // Shielded pool tree initialization will be added here once the - // RootTree variants for the shielded pool are defined. - self.create_initial_state_structure_v2(transaction, platform_version) + let drive_version = &platform_version.drive; + self.create_initial_state_structure_top_level_0(transaction, platform_version)?; + + self.grove_insert_empty_tree( + SubtreePath::empty(), + &[RootTree::GroupActions as u8], + TreeType::NormalTree, + transaction, + None, + &mut vec![], + drive_version, + )?; + + // AddressBalances top-level sum tree (introduced in v2) + self.grove_insert_empty_tree( + SubtreePath::empty(), + &[RootTree::AddressBalances as u8], + TreeType::SumTree, + transaction, + None, + &mut vec![], + drive_version, + )?; + + // SavedBlockTransactions for address-based transaction sync + self.grove_insert_empty_tree( + SubtreePath::empty(), + &[RootTree::SavedBlockTransactions as u8], + TreeType::NormalTree, + transaction, + None, + &mut vec![], + drive_version, + )?; + + // On lower layers we can use batching + + let mut batch = + self.create_initial_state_structure_lower_layers_operations_0(platform_version)?; + + self.initial_state_structure_lower_layers_add_operations_2(&mut batch, platform_version)?; + + // Add shielded pool structures + self.initial_state_structure_shielded_pool_operations(&mut batch)?; + + self.grove_apply_batch(batch, false, transaction, drive_version)?; + + Ok(()) + } + + /// Adds shielded pool batch operations for initialization. + pub(in crate::drive::initialization) fn initial_state_structure_shielded_pool_operations( + &self, + batch: &mut GroveDbOpBatch, + ) -> Result<(), Error> { + // 1. Shielded credit pool SumTree under AddressBalances + batch.add_insert( + vec![vec![RootTree::AddressBalances as u8]], + vec![SHIELDED_CREDIT_POOL_KEY_U8], + Element::empty_sum_tree(), + ); + + // 2. Notes tree (CommitmentTree = CountTree items + Sinsemilla Frontier) + batch.add_insert( + shielded_credit_pool_path_vec(), + vec![SHIELDED_NOTES_KEY], + Element::empty_commitment_tree(SHIELDED_NOTES_CHUNK_POWER), + ); + + // 3. Nullifiers tree (ProvableCountTree) + batch.add_insert( + shielded_credit_pool_path_vec(), + vec![SHIELDED_NULLIFIERS_KEY], + Element::empty_provable_count_tree(), + ); + + // 4. Total balance SumItem(0) + batch.add_insert( + shielded_credit_pool_path_vec(), + vec![SHIELDED_TOTAL_BALANCE_KEY], + Element::new_sum_item(0), + ); + + // 5. Anchors tree (NormalTree) inside pool: block_height_be → anchor_bytes + batch.add_insert( + shielded_credit_pool_path_vec(), + vec![SHIELDED_ANCHORS_IN_POOL_KEY], + Element::empty_tree(), + ); + + // 6. Nullifiers CountSumTree under SavedBlockTransactions for storing + // per-block nullifier lists. Each item is an ItemWithSumItem + // (serialized Vec<[u8;32]> + nullifier count as sum). + batch.add_insert( + Self::saved_block_transactions_path(), + vec![NULLIFIERS_KEY_U8], + Element::empty_count_sum_tree(), + ); + + // 7. Compacted nullifiers NormalTree under SavedBlockTransactions for storing + // compacted/aggregated nullifier lists spanning multiple blocks. + // Key: (start_block, end_block) as 16 bytes, Value: serialized Vec<[u8;32]> + batch.add_insert( + Self::saved_block_transactions_path(), + vec![COMPACTED_NULLIFIERS_KEY_U8], + Element::empty_tree(), + ); + + // 8. Nullifiers expiration time NormalTree under SavedBlockTransactions for storing + // expiration timestamps for compacted nullifier ranges. + batch.add_insert( + Self::saved_block_transactions_path(), + vec![NULLIFIERS_EXPIRATION_TIME_KEY_U8], + Element::empty_tree(), + ); + + Ok(()) } } diff --git a/packages/rs-drive/src/drive/mod.rs b/packages/rs-drive/src/drive/mod.rs index 54f08566cd3..613dcceae61 100644 --- a/packages/rs-drive/src/drive/mod.rs +++ b/packages/rs-drive/src/drive/mod.rs @@ -71,6 +71,9 @@ pub mod address_funds; /// Saved block transactions module #[cfg(feature = "server")] pub mod saved_block_transactions; +/// Shielded pools module +#[cfg(any(feature = "server", feature = "verify"))] +pub mod shielded; /// Token module #[cfg(any(feature = "server", feature = "verify"))] pub mod tokens; diff --git a/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs new file mode 100644 index 00000000000..a3288990f1b --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs @@ -0,0 +1,49 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Cleans up expired compacted nullifier entries. + /// + /// This function queries the nullifier expiration time tree for entries with + /// expiration time <= current_block_time_ms, then deletes: + /// 1. The corresponding compacted nullifier entries + /// 2. The expiration time entries themselves + /// + /// # Arguments + /// * `current_block_time_ms` - The current block time in milliseconds + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// * `Ok(usize)` - The number of compacted entries that were cleaned up + /// * `Err` - An error occurred + pub fn cleanup_expired_nullifiers( + &self, + current_block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .saved_block_transactions + .cleanup_expired_nullifiers + { + 0 => self.cleanup_expired_nullifiers_v0( + current_block_time_ms, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "cleanup_expired_nullifiers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..e9a48f2e051 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs @@ -0,0 +1,95 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; +use crate::util::batch::GroveDbOpBatch; +use dpp::ProtocolError; +use grovedb::query_result_type::QueryResultType; +use grovedb::{PathQuery, Query, QueryItem, SizedQuery, TransactionArg}; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation of cleaning up expired compacted nullifier entries. + /// + /// Queries for all expiration entries with time <= current_block_time_ms, + /// then deletes the corresponding compacted entries and the expiration entries. + pub(super) fn cleanup_expired_nullifiers_v0( + &self, + current_block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let expiration_path = Self::saved_nullifiers_expiration_time_path_vec(); + + // Query all entries with expiration time <= current_block_time_ms + let mut query = Query::new(); + // Range from 0 to current_block_time_ms (inclusive) + query.insert_item(QueryItem::RangeToInclusive( + ..=current_block_time_ms.to_be_bytes().to_vec(), + )); + + let path_query = + PathQuery::new(expiration_path.clone(), SizedQuery::new(query, None, None)); + + let (results, _) = self.grove_get_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + let key_elements = results.to_key_elements(); + + if key_elements.is_empty() { + return Ok(0); + } + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + let mut batch = GroveDbOpBatch::new(); + let mut total_cleaned = 0usize; + + let compacted_path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + + for (expiration_key, element) in key_elements { + // Get the vec of block ranges from the element + let grovedb::Element::Item(serialized_ranges, _) = element else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "expected item element for expiration block ranges".to_string(), + ), + ))); + }; + + // Deserialize the vec of block ranges + let (ranges, _): (Vec<(u64, u64)>, usize) = + bincode::decode_from_slice(&serialized_ranges, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode expiration block ranges: {}", + e + )))) + })?; + + // Delete each compacted nullifier entry + for (start_block, end_block) in &ranges { + let mut compacted_key = Vec::with_capacity(16); + compacted_key.extend_from_slice(&start_block.to_be_bytes()); + compacted_key.extend_from_slice(&end_block.to_be_bytes()); + + batch.add_delete(compacted_path.clone(), compacted_key); + total_cleaned += 1; + } + + // Delete the expiration entry itself + batch.add_delete(expiration_path.clone(), expiration_key); + } + + if !batch.is_empty() { + self.grove_apply_batch(batch, false, transaction, &platform_version.drive)?; + } + + Ok(total_cleaned) + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs new file mode 100644 index 00000000000..d85d2815fa1 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs @@ -0,0 +1,61 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +/// One week in milliseconds (used for compacted nullifier expiration) +pub const ONE_WEEK_IN_MS: u64 = 7 * 24 * 60 * 60 * 1000; + +impl Drive { + /// Compacts nullifiers from recent blocks, including the current block, + /// into a single compacted entry. + /// + /// This function drains all entries from the nullifiers tree, concatenates them + /// with the provided current block's nullifiers, and stores the result in + /// the compacted nullifiers tree with a (start_block, end_block) key. + /// + /// Also stores the expiration time (current block time + 1 week) in the + /// nullifiers expiration time tree. + /// + /// # Arguments + /// * `current_nullifiers` - The current block's nullifiers to include + /// * `current_block_height` - The height of the current block + /// * `current_block_time_ms` - The current block time in milliseconds + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// * `Ok((start, end))` - The block range that was compacted + /// * `Err` - An error occurred + pub fn compact_nullifiers_with_current_block( + &self, + current_nullifiers: &[[u8; 32]], + current_block_height: u64, + current_block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(u64, u64), Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .compact_nullifiers + { + 0 => self.compact_nullifiers_with_current_block_v0( + current_nullifiers, + current_block_height, + current_block_time_ms, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "compact_nullifiers_with_current_block".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..849c6d7d037 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs @@ -0,0 +1,194 @@ +use crate::drive::saved_block_transactions::compact_nullifiers::ONE_WEEK_IN_MS; +use crate::drive::Drive; +use crate::error::Error; +use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; +use crate::util::batch::GroveDbOpBatch; +use crate::util::grove_operations::DirectQueryType; +use dpp::ProtocolError; +use grovedb::query_result_type::QueryResultType; +use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb_path::SubtreePath; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation of compacting nullifiers including a current block. + /// + /// Drains all entries from the nullifiers count sum tree, + /// concatenates them with the provided current block's nullifiers, + /// and stores the result in the compacted nullifiers tree + /// with a (start_block, end_block) key. + /// + /// Also stores the expiration time (current block time + 1 week) in the + /// nullifiers expiration time tree with the same (start_block, end_block) key. + /// + /// Returns the range of blocks that were compacted (start_block, end_block). + pub(super) fn compact_nullifiers_with_current_block_v0( + &self, + current_nullifiers: &[[u8; 32]], + current_block_height: u64, + current_block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(u64, u64), Error> { + let path = Self::saved_block_transactions_nullifiers_path_vec(); + + // Query all entries from the nullifiers tree + let mut query = Query::new(); + query.insert_all(); + + let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, None, None)); + + let (results, _) = self.grove_get_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + let key_elements = results.to_key_elements(); + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + // Track block range - start with current block + let mut start_block: u64 = current_block_height; + let mut end_block: u64 = current_block_height; + + // Concatenate all nullifiers together (no merge semantics needed) + let mut combined_nullifiers: Vec<[u8; 32]> = Vec::new(); + let mut keys_to_delete: Vec> = Vec::new(); + + // Process stored blocks in chronological order (ascending by block height) + // Keys are big-endian u64, so they're already in ascending order + for (key, element) in key_elements { + // Parse block height from key (8 bytes, big-endian) + let height_bytes: [u8; 8] = key.clone().try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid block height key length".to_string(), + ))) + })?; + let block_height = u64::from_be_bytes(height_bytes); + + // Track start and end blocks + if block_height < start_block { + start_block = block_height; + } + if block_height > end_block { + end_block = block_height; + } + + // Get the serialized data from the ItemWithSumItem element + let Element::ItemWithSumItem(serialized_data, _, _) = element else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "expected item with sum item element for nullifiers".to_string(), + ), + ))); + }; + + // Deserialize the nullifier list + let (block_nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode nullifiers: {}", + e + )))) + })?; + + // Simply concatenate - no merge semantics needed for nullifiers + combined_nullifiers.extend(block_nullifiers); + + keys_to_delete.push(key); + } + + // Append the current block's nullifiers + combined_nullifiers.extend_from_slice(current_nullifiers); + + // Serialize the combined nullifiers + let serialized = bincode::encode_to_vec(&combined_nullifiers, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode compacted nullifiers: {}", + e + )))) + })?; + + // Create the compacted key: (start_block, end_block) as 16 bytes + let mut compacted_key = Vec::with_capacity(16); + compacted_key.extend_from_slice(&start_block.to_be_bytes()); + compacted_key.extend_from_slice(&end_block.to_be_bytes()); + + // Build batch operations + let mut batch = GroveDbOpBatch::new(); + + // Delete all original entries from the count sum tree + for key in keys_to_delete { + batch.add_delete(path.clone(), key); + } + + // Insert the compacted entry as a plain Item (not ItemWithSumItem) + batch.add_insert( + Self::saved_compacted_block_transactions_nullifiers_path_vec(), + compacted_key.clone(), + Element::new_item(serialized), + ); + + // Calculate expiration time (current block time + 1 week) + let expiration_time_ms = current_block_time_ms.saturating_add(ONE_WEEK_IN_MS); + let expiration_key = expiration_time_ms.to_be_bytes().to_vec(); + + // Check if an entry with this expiration time already exists + // If so, we need to append to the existing vec of block ranges + let expiration_path = Self::saved_nullifiers_expiration_time_path(); + + let mut drive_operations = vec![]; + let existing_ranges = self.grove_get_raw_optional( + SubtreePath::from(expiration_path.as_ref()), + &expiration_key, + DirectQueryType::StatefulDirectQuery, + transaction, + &mut drive_operations, + &platform_version.drive, + )?; + + let expiration_value = if let Some(Element::Item(existing_data, _)) = existing_ranges { + // Deserialize existing vec of block ranges and append the new one + let (mut ranges, _): (Vec<(u64, u64)>, usize) = + bincode::decode_from_slice(&existing_data, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode expiration block ranges: {}", + e + )))) + })?; + ranges.push((start_block, end_block)); + bincode::encode_to_vec(&ranges, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode expiration block ranges: {}", + e + )))) + })? + } else { + // No existing entry, create new vec with single range + let ranges: Vec<(u64, u64)> = vec![(start_block, end_block)]; + bincode::encode_to_vec(&ranges, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode expiration block ranges: {}", + e + )))) + })? + }; + + // Store in the expiration tree: key = expiration_time, value = vec of (start_block, end_block) + batch.add_insert( + Self::saved_nullifiers_expiration_time_path_vec(), + expiration_key, + Element::new_item(expiration_value), + ); + + // Apply the batch + self.grove_apply_batch(batch, false, transaction, &platform_version.drive)?; + + Ok((start_block, end_block)) + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs new file mode 100644 index 00000000000..49892a3f085 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs @@ -0,0 +1,85 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +pub use v0::CompactedNullifierChanges; + +impl Drive { + /// Fetches compacted nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_block_height` - The block height to start fetching from + /// * `limit` - Optional maximum number of compacted entries to return + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A vector of (start_block, end_block, nullifiers) tuples + pub fn fetch_compacted_nullifier_changes( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.fetch_compacted_nullifier_changes_v0( + start_block_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_compacted_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Proves compacted nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_block_height` - The block height to start from + /// * `limit` - Optional maximum number of compacted entries to prove + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A grovedb proof + pub fn prove_compacted_nullifier_changes( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.prove_compacted_nullifier_changes_v0( + start_block_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_compacted_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..d95e5c5a3b3 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs @@ -0,0 +1,277 @@ +use crate::drive::Drive; +use crate::error::Error; +use dpp::ProtocolError; +use grovedb::query_result_type::QueryResultType; +use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; +use platform_version::version::PlatformVersion; + +/// Result type for fetched compacted nullifier changes +/// Each entry is (start_block, end_block, nullifiers) +pub type CompactedNullifierChanges = Vec<(u64, u64, Vec<[u8; 32]>)>; + +impl Drive { + /// Version 0 implementation of fetching compacted nullifier changes. + /// + /// Retrieves all compacted nullifier change records where `end_block >= start_block_height`. + /// This includes ranges that contain `start_block_height` (e.g., range 400-600 when querying + /// from block 505) as well as ranges that start after `start_block_height`. + /// + /// Returns a vector of (start_block, end_block, nullifiers) tuples. + pub(super) fn fetch_compacted_nullifier_changes_v0( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + + // Keys are 16 bytes: (start_block, end_block), both big-endian. + // We want ranges where end_block >= start_block_height, which includes: + // - Ranges that contain start_block_height (e.g., 400-600 contains 505) + // - Ranges that start at or after start_block_height + // + // Strategy: + // 1. First query: descending from (start_block_height, u64::MAX) with limit 1 + // to find any range where start_block <= start_block_height that might contain it + // 2. Second query: ascending from (start_block_height, 0) to get ranges + // that start at or after start_block_height + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + let mut compacted_changes = Vec::new(); + let limit_usize = limit.map(|l| l as usize); + + // Query 1: Find if there's a range containing start_block_height + // Query descending from (start_block_height, u64::MAX) with limit 1 + let mut desc_end_key = Vec::with_capacity(16); + desc_end_key.extend_from_slice(&start_block_height.to_be_bytes()); + desc_end_key.extend_from_slice(&u64::MAX.to_be_bytes()); + + let mut desc_query = Query::new_with_direction(false); // descending + desc_query.insert_range_to_inclusive(..=desc_end_key); + + let desc_path_query = + PathQuery::new(path.clone(), SizedQuery::new(desc_query, Some(1), None)); + + let (desc_results, _) = self.grove_get_path_query( + &desc_path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + // Check if we found a range that contains start_block_height + if let Some((key, element)) = desc_results.to_key_elements().into_iter().next() { + if key.len() != 16 { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "invalid compacted block key length, expected 16 bytes".to_string(), + ), + ))); + } + + let start_block = u64::from_be_bytes( + key[0..8] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + let end_block = u64::from_be_bytes( + key[8..16] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + + // Only include if end_block >= start_block_height (range contains our block) + if end_block >= start_block_height { + let Element::Item(serialized_data, _) = element else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "expected item element for compacted nullifiers".to_string(), + ), + ))); + }; + + let (nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode compacted nullifiers: {}", + e + )))) + })?; + + compacted_changes.push((start_block, end_block, nullifiers)); + } + } + + // Check if we've already hit the limit + if let Some(l) = limit_usize { + if compacted_changes.len() >= l { + return Ok(compacted_changes); + } + } + + // Query 2: Get ranges that start at or after start_block_height (ascending) + // Always use (start_block_height, 0) for consistent proof verification + // The result may overlap with descending query if descending found a range + // starting exactly at start_block_height - we dedupe below + let mut asc_start_key = Vec::with_capacity(16); + asc_start_key.extend_from_slice(&start_block_height.to_be_bytes()); + asc_start_key.extend_from_slice(&0u64.to_be_bytes()); + + let mut asc_query = Query::new(); + asc_query.insert_range_from(asc_start_key..); + + let asc_path_query = PathQuery::new(path, SizedQuery::new(asc_query, limit, None)); + + let (asc_results, _) = self.grove_get_path_query( + &asc_path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + // Track the start_block from descending query to avoid duplicates + let desc_start_block = compacted_changes.first().map(|(start, _, _)| *start); + + for (key, element) in asc_results.to_key_elements() { + // Check if we've reached the limit + if let Some(l) = limit_usize { + if compacted_changes.len() >= l { + break; + } + } + + if key.len() != 16 { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "invalid compacted block key length, expected 16 bytes".to_string(), + ), + ))); + } + + let start_block = u64::from_be_bytes( + key[0..8] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + let end_block = u64::from_be_bytes( + key[8..16] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + + // Skip if this is the same range we got from descending query + if Some(start_block) == desc_start_block { + continue; + } + + let Element::Item(serialized_data, _) = element else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "expected item element for compacted nullifiers".to_string(), + ), + ))); + }; + + let (nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode compacted nullifiers: {}", + e + )))) + })?; + + compacted_changes.push((start_block, end_block, nullifiers)); + } + + Ok(compacted_changes) + } + + /// Version 0 implementation for proving compacted nullifier changes. + /// + /// Uses a two-step approach: + /// 1. First query (non-proving): descending to find any range containing start_block_height + /// 2. Second query (proving): ascending from the found start_block or start_block_height + /// + /// This ensures the proof covers all relevant ranges efficiently. + pub(super) fn prove_compacted_nullifier_changes_v0( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + + // Step 1: Non-proving descending query to find any range containing start_block_height + let mut desc_end_key = Vec::with_capacity(16); + desc_end_key.extend_from_slice(&start_block_height.to_be_bytes()); + desc_end_key.extend_from_slice(&u64::MAX.to_be_bytes()); + + let mut desc_query = Query::new_with_direction(false); // descending + desc_query.insert_range_to_inclusive(..=desc_end_key); + + let desc_path_query = + PathQuery::new(path.clone(), SizedQuery::new(desc_query, Some(1), None)); + + let (desc_results, _) = self.grove_get_path_query( + &desc_path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + // Determine the actual start key for the proved query + // If we found a containing range, use its exact key + // Otherwise use (start_block_height, start_block_height) since end_block >= start_block always + let start_key = if let Some((key, _)) = desc_results.to_key_elements().into_iter().next() { + if key.len() == 16 { + let end_block = u64::from_be_bytes( + key[8..16] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + // If this range contains start_block_height, use its exact key + if end_block >= start_block_height { + key + } else { + // No containing range, use (start_block_height, start_block_height) + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + } + } else { + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + } + } else { + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + }; + + // Step 2: Proved ascending query from start_key + + let mut query = Query::new(); + query.insert_range_from(start_key..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + self.grove_get_proved_path_query( + &path_query, + transaction, + &mut vec![], + &platform_version.drive, + ) + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs new file mode 100644 index 00000000000..fb7ed9694ae --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs @@ -0,0 +1,85 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +pub use v0::NullifierChangesPerBlock; + +impl Drive { + /// Fetches recent nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_height` - The block height to start fetching from + /// * `limit` - Optional maximum number of blocks to return + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A vector of (block_height, nullifiers) tuples + pub fn fetch_recent_nullifier_changes( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.fetch_recent_nullifier_changes_v0( + start_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_recent_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Proves recent nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_height` - The block height to start from + /// * `limit` - Optional maximum number of blocks to prove + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A grovedb proof + pub fn prove_recent_nullifier_changes( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.prove_recent_nullifier_changes_v0( + start_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_recent_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..c44662fa8a3 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs @@ -0,0 +1,101 @@ +use crate::drive::Drive; +use crate::error::Error; +use dpp::ProtocolError; +use grovedb::query_result_type::QueryResultType; +use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; +use platform_version::version::PlatformVersion; + +/// Result type for fetched nullifier changes per block +pub type NullifierChangesPerBlock = Vec<(u64, Vec<[u8; 32]>)>; + +impl Drive { + /// Version 0 implementation of fetching nullifier changes from a start height. + /// + /// Retrieves all nullifier change records from `start_height` onwards. + /// Returns a vector of (block_height, nullifiers) tuples. + pub(super) fn fetch_recent_nullifier_changes_v0( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let path = Self::saved_block_transactions_nullifiers_path_vec(); + + // Create a range query starting from the specified height + let mut query = Query::new(); + query.insert_range_from(start_height.to_be_bytes().to_vec()..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + let (results, _) = self.grove_get_path_query( + &path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + let mut nullifier_changes = Vec::new(); + + for (key, element) in results.to_key_elements() { + // Parse block height from key (8 bytes, big-endian) + let height_bytes: [u8; 8] = key.try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid block height key length".to_string(), + ))) + })?; + let block_height = u64::from_be_bytes(height_bytes); + + // Get the serialized data from the ItemWithSumItem element + let Element::ItemWithSumItem(serialized_data, _, _) = element else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "expected item with sum item element for nullifiers".to_string(), + ), + ))); + }; + + // Deserialize the nullifier list + let (nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode nullifiers: {}", + e + )))) + })?; + + nullifier_changes.push((block_height, nullifiers)); + } + + Ok(nullifier_changes) + } + + /// Version 0 implementation for proving nullifier changes from a start height. + pub(super) fn prove_recent_nullifier_changes_v0( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path = Self::saved_block_transactions_nullifiers_path_vec(); + + // Create a range query starting from the specified height + let mut query = Query::new(); + query.insert_range_from(start_height.to_be_bytes().to_vec()..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + self.grove_get_proved_path_query( + &path_query, + transaction, + &mut vec![], + &platform_version.drive, + ) + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/mod.rs index 5b1a1652331..0e82340f0af 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/mod.rs @@ -5,6 +5,15 @@ mod fetch_compacted_address_balances; mod queries; mod store_address_balances; +mod cleanup_expired_nullifiers; +/// Compaction of per-block nullifier entries into range-keyed compacted entries +pub mod compact_nullifiers; +mod fetch_compacted_nullifiers; +mod fetch_nullifiers; +mod store_nullifiers; + pub use fetch_address_balances::AddressBalanceChangesPerBlock; pub use fetch_compacted_address_balances::CompactedAddressBalanceChanges; +pub use fetch_compacted_nullifiers::CompactedNullifierChanges; +pub use fetch_nullifiers::NullifierChangesPerBlock; pub use queries::*; diff --git a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs index 2b8556805d8..082056600fb 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs @@ -19,6 +19,24 @@ pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY: &[u8; 1] = b"e"; /// The subtree key for compacted addresses expiration time storage as u8 pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8: u8 = b'e'; +/// The subtree key for nullifiers storage (per-block nullifier lists) +pub const NULLIFIERS_KEY: &[u8; 1] = b"n"; + +/// The subtree key for nullifiers storage as u8 +pub const NULLIFIERS_KEY_U8: u8 = b'n'; + +/// The subtree key for compacted nullifiers storage +pub const COMPACTED_NULLIFIERS_KEY: &[u8; 1] = b"o"; + +/// The subtree key for compacted nullifiers storage as u8 +pub const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; + +/// The subtree key for nullifiers expiration time storage +pub const NULLIFIERS_EXPIRATION_TIME_KEY: &[u8; 1] = b"p"; + +/// The subtree key for nullifiers expiration time storage as u8 +pub const NULLIFIERS_EXPIRATION_TIME_KEY_U8: u8 = b'p'; + impl Drive { /// Path to saved block transactions storage. pub fn saved_block_transactions_path() -> Vec> { @@ -72,4 +90,52 @@ impl Drive { &[COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8], ] } + + /// Path to nullifiers under saved block transactions (Vec version). + pub fn saved_block_transactions_nullifiers_path_vec() -> Vec> { + vec![ + vec![RootTree::SavedBlockTransactions as u8], + vec![NULLIFIERS_KEY_U8], + ] + } + + /// Path to nullifiers under saved block transactions. + pub fn saved_block_transactions_nullifiers_path() -> [&'static [u8]; 2] { + [ + Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), + &[NULLIFIERS_KEY_U8], + ] + } + + /// Path to compacted nullifiers under saved block transactions (Vec version). + pub fn saved_compacted_block_transactions_nullifiers_path_vec() -> Vec> { + vec![ + vec![RootTree::SavedBlockTransactions as u8], + vec![COMPACTED_NULLIFIERS_KEY_U8], + ] + } + + /// Path to compacted nullifiers under saved block transactions. + pub fn saved_compacted_block_transactions_nullifiers_path() -> [&'static [u8]; 2] { + [ + Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), + &[COMPACTED_NULLIFIERS_KEY_U8], + ] + } + + /// Path to nullifiers expiration time under saved block transactions (Vec version). + pub fn saved_nullifiers_expiration_time_path_vec() -> Vec> { + vec![ + vec![RootTree::SavedBlockTransactions as u8], + vec![NULLIFIERS_EXPIRATION_TIME_KEY_U8], + ] + } + + /// Path to nullifiers expiration time under saved block transactions. + pub fn saved_nullifiers_expiration_time_path() -> [&'static [u8]; 2] { + [ + Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), + &[NULLIFIERS_EXPIRATION_TIME_KEY_U8], + ] + } } diff --git a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs new file mode 100644 index 00000000000..ece1654af51 --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs @@ -0,0 +1,55 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Stores nullifiers for a block in the SavedBlockTransactions tree. + /// + /// This method serializes the nullifiers using bincode and stores + /// them keyed by block height. If compaction thresholds are exceeded, it will + /// compact existing entries along with the current block's data and store + /// an expiration time for the compacted entry. + /// + /// # Parameters + /// - `nullifiers`: The nullifiers to store for this block + /// - `block_height`: The height of the block these nullifiers belong to + /// - `block_time_ms`: The block time in milliseconds (used for expiration calculation) + /// - `transaction`: The database transaction + /// - `platform_version`: The platform version + /// + /// # Returns + /// - `Ok(())` on success + /// - `Err(Error)` if the operation fails + pub fn store_nullifiers_for_block( + &self, + nullifiers: &[[u8; 32]], + block_height: u64, + block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .store_nullifiers + { + 0 => self.store_nullifiers_for_block_v0( + nullifiers, + block_height, + block_time_ms, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "store_nullifiers_for_block".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..15bcad2a6ef --- /dev/null +++ b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs @@ -0,0 +1,154 @@ +use crate::drive::saved_block_transactions::NULLIFIERS_KEY_U8; +use crate::drive::Drive; +use crate::error::Error; +use crate::util::grove_operations::DirectQueryType; +use dpp::ProtocolError; +use grovedb::Element; +use grovedb::TransactionArg; +use grovedb_path::SubtreePath; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation of storing nullifiers for a block. + /// + /// Serializes the nullifier list using bincode and stores it in the + /// SavedBlockTransactions/Nullifiers count sum tree keyed by block height. + /// Each entry is an ItemWithSumItem where: + /// - The item contains the serialized nullifiers + /// - The sum value is the number of nullifiers + /// + /// Before storing, checks if compaction thresholds are exceeded and triggers + /// compaction if necessary. If compaction occurs, the current block's nullifiers + /// are included in the compaction rather than stored separately. + pub(super) fn store_nullifiers_for_block_v0( + &self, + nullifiers: &[[u8; 32]], + block_height: u64, + block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + // Early return if there are no nullifiers to store + if nullifiers.is_empty() { + return Ok(()); + } + + // Check if compaction is needed - if so, include current nullifiers in compaction + let compacted = self.check_and_compact_nullifiers_if_needed( + nullifiers, + block_height, + block_time_ms, + transaction, + platform_version, + )?; + + // If we compacted, the current nullifiers are already included - don't store separately + if compacted { + return Ok(()); + } + + // Serialize the nullifiers using bincode + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + let serialized = bincode::encode_to_vec(nullifiers, config).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode nullifiers: {}", + e + )))) + })?; + + // The sum value is the number of nullifiers + let entry_count = nullifiers.len() as i64; + + // Store in the SavedBlockTransactions/Nullifiers count sum tree with block height as key + let path: [&[u8]; 2] = Drive::saved_block_transactions_nullifiers_path(); + + // Use block height as the key (big-endian for proper ordering) + let key = block_height.to_be_bytes(); + + // Insert as ItemWithSumItem where: + // - item data = serialized nullifiers + // - sum value = number of nullifiers + let mut drive_operations = vec![]; + self.grove_insert( + path.as_ref().into(), + &key, + Element::new_item_with_sum_item(serialized, entry_count), + transaction, + None, + &mut drive_operations, + &platform_version.drive, + )?; + + // Apply any operations that were generated + self.apply_batch_low_level_drive_operations( + None, + transaction, + drive_operations, + &mut vec![], + &platform_version.drive, + )?; + + Ok(()) + } + + /// Checks if compaction thresholds are exceeded and triggers compaction if needed. + /// If compaction occurs, the provided nullifiers are included in the compaction. + /// + /// Returns true if compaction was performed (meaning the nullifiers were included). + fn check_and_compact_nullifiers_if_needed( + &self, + nullifiers: &[[u8; 32]], + block_height: u64, + block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result { + let saved_block_tx_path = Self::saved_block_transactions_path(); + + // Get the count sum tree element to check current count and sum + let mut drive_operations = vec![]; + let tree_element = self.grove_get_raw( + SubtreePath::from(saved_block_tx_path.as_slice()), + &[NULLIFIERS_KEY_U8], + DirectQueryType::StatefulDirectQuery, + transaction, + &mut drive_operations, + &platform_version.drive, + )?; + + if let Some(Element::CountSumTree(_, count, sum, _)) = tree_element { + let max_blocks = platform_version + .drive + .methods + .saved_block_transactions + .max_blocks_before_nullifier_compaction as u64; + let max_nullifiers = platform_version + .drive + .methods + .saved_block_transactions + .max_nullifiers_before_compaction as i64; + + // Check if either threshold would be exceeded after adding the current block + // count + 1 for the new block, sum + current nullifiers count + let new_count = count + 1; + let new_sum = sum + nullifiers.len() as i64; + + if new_count >= max_blocks || new_sum >= max_nullifiers { + // Trigger compaction, including the current block's nullifiers + self.compact_nullifiers_with_current_block( + nullifiers, + block_height, + block_time_ms, + transaction, + platform_version, + )?; + return Ok(true); + } + } + + Ok(false) + } +} diff --git a/packages/rs-drive/src/drive/shielded/estimated_costs.rs b/packages/rs-drive/src/drive/shielded/estimated_costs.rs new file mode 100644 index 00000000000..30998a478e8 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/estimated_costs.rs @@ -0,0 +1,137 @@ +use crate::drive::shielded::paths::{ + shielded_credit_pool_anchors_path, shielded_credit_pool_notes_path, + shielded_credit_pool_nullifiers_path, shielded_credit_pool_path, SHIELDED_NOTES_CHUNK_POWER, +}; +use crate::drive::{Drive, RootTree}; +use grovedb::batch::KeyInfoPath; +use grovedb::EstimatedLayerCount::EstimatedLevel; +use grovedb::EstimatedLayerSizes::{AllItems, AllSubtrees, Mix}; +use grovedb::EstimatedSumTrees::SomeSumTrees; +use grovedb::{EstimatedLayerInformation, TreeType}; +use std::collections::HashMap; + +/// Average size of a note value: 32 cmx + 32 rho + 216 encrypted note = 280 bytes +/// (encrypted note = 32 epk + 104 enc_ciphertext + 80 out_ciphertext, using DashMemo 36-byte memos) +/// The cmx and rho are prepended by GroveDB's commitment_tree_insert_op for client retrieval. +const AVERAGE_NOTE_VALUE_SIZE: u32 = 280; + +/// Size of a nullifier key (32 bytes) +const NULLIFIER_KEY_SIZE: u8 = 32; + +/// Size of an anchor block height key (u64 big-endian = 8 bytes) +const ANCHOR_KEY_SIZE: u8 = 8; + +/// Size of an anchor value (32 bytes) +const ANCHOR_VALUE_SIZE: u32 = 32; + +impl Drive { + /// Adds estimation costs for shielded pool operations. + /// + /// Registers all shielded pool tree paths in the estimation cache so that + /// the fee estimation system can calculate costs for shielded operations. + /// Also registers parent paths (root and AddressBalances) needed by the + /// GroveDB cost estimation system. + pub(crate) fn add_estimation_costs_for_shielded_pool_operations( + estimated_costs_only_with_layer_info: &mut HashMap, + ) { + // Root level: [] — needed so GroveDB can estimate traversal to AddressBalances + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_path([]), + EstimatedLayerInformation { + tree_type: TreeType::NormalTree, + estimated_layer_count: EstimatedLevel(3, false), + estimated_layer_sizes: AllSubtrees( + 1, + SomeSumTrees { + sum_trees_weight: 2, + big_sum_trees_weight: 0, + count_trees_weight: 0, + count_sum_trees_weight: 0, + non_sum_trees_weight: 2, + }, + None, + ), + }, + ); + + // AddressBalances level: [[56]] — parent of shielded pool subtree + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_owned_path(vec![vec![RootTree::AddressBalances as u8]]), + EstimatedLayerInformation { + tree_type: TreeType::SumTree, + estimated_layer_count: EstimatedLevel(2, false), + estimated_layer_sizes: AllSubtrees( + 1, + SomeSumTrees { + sum_trees_weight: 1, + big_sum_trees_weight: 0, + count_trees_weight: 0, + count_sum_trees_weight: 1, + non_sum_trees_weight: 0, + }, + None, + ), + }, + ); + + // Shielded credit pool: [AddressBalances, "s"] + // SumTree containing: notes (CommitmentTree), nullifiers (NormalTree), + // total balance (SumItem), anchors (NormalTree) + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_path(shielded_credit_pool_path()), + EstimatedLayerInformation { + tree_type: TreeType::SumTree, + estimated_layer_count: EstimatedLevel(4, false), + estimated_layer_sizes: Mix { + subtrees_size: Some(( + 1, + SomeSumTrees { + sum_trees_weight: 0, + big_sum_trees_weight: 0, + count_trees_weight: 0, + count_sum_trees_weight: 0, + non_sum_trees_weight: 2, // nullifiers + anchors + }, + None, + 3, // 3 subtrees: notes (CommitmentTree), nullifiers, anchors + )), + items_size: Some((1, 8, None, 1)), // 1 item: total balance + references_size: None, + }, + }, + ); + + // Notes tree: [AddressBalances, "s", 1] + // CommitmentTree - stores notes (cmx||encrypted_note items + Sinsemilla frontier) + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_path(shielded_credit_pool_notes_path()), + EstimatedLayerInformation { + tree_type: TreeType::CommitmentTree(SHIELDED_NOTES_CHUNK_POWER), + estimated_layer_count: EstimatedLevel(16, false), + estimated_layer_sizes: AllItems(8, AVERAGE_NOTE_VALUE_SIZE, None), + }, + ); + + // Nullifiers tree: [AddressBalances, "s", 2] + // NormalTree - stores spent nullifiers (32-byte key -> empty item) + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_path(shielded_credit_pool_nullifiers_path()), + EstimatedLayerInformation { + tree_type: TreeType::NormalTree, + estimated_layer_count: EstimatedLevel(16, false), + estimated_layer_sizes: AllItems(NULLIFIER_KEY_SIZE, 0, None), + }, + ); + + // Anchors tree: [AddressBalances, "s", 6] + // NormalTree - stores block_height_be -> anchor_bytes + estimated_costs_only_with_layer_info.insert( + KeyInfoPath::from_known_path(shielded_credit_pool_anchors_path()), + EstimatedLayerInformation { + tree_type: TreeType::NormalTree, + estimated_layer_count: EstimatedLevel(7, false), + estimated_layer_sizes: AllItems(ANCHOR_KEY_SIZE, ANCHOR_VALUE_SIZE, None), + }, + ); + } +} diff --git a/packages/rs-drive/src/drive/shielded/mod.rs b/packages/rs-drive/src/drive/shielded/mod.rs new file mode 100644 index 00000000000..1515f442dd6 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/mod.rs @@ -0,0 +1,11 @@ +/// Shielded pool paths and constants +#[cfg(any(feature = "server", feature = "verify"))] +pub mod paths; + +/// Estimation costs for shielded pool operations +#[cfg(feature = "server")] +pub(crate) mod estimated_costs; + +/// Prove methods for shielded pool queries +#[cfg(feature = "server")] +pub mod prove; diff --git a/packages/rs-drive/src/drive/shielded/paths.rs b/packages/rs-drive/src/drive/shielded/paths.rs new file mode 100644 index 00000000000..8698b2dd3b1 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/paths.rs @@ -0,0 +1,117 @@ +use crate::drive::RootTree; + +/// The subtree key for the shielded credit pool under AddressBalances +pub const SHIELDED_CREDIT_POOL_KEY: &[u8; 1] = b"s"; + +/// The subtree key for the shielded credit pool as a u8 +pub const SHIELDED_CREDIT_POOL_KEY_U8: u8 = b's'; + +/// Key for the notes tree (CommitmentTree) inside a shielded pool +pub const SHIELDED_NOTES_KEY: u8 = 1; + +/// Key for the nullifiers tree inside a shielded pool +pub const SHIELDED_NULLIFIERS_KEY: u8 = 2; + +/// Key for the total balance sum item inside a shielded pool +pub const SHIELDED_TOTAL_BALANCE_KEY: u8 = 5; + +/// Key for the anchors tree inside a shielded pool +pub const SHIELDED_ANCHORS_IN_POOL_KEY: u8 = 6; + +/// Chunk power for the notes CommitmentTree (2^11 = 2048 items per chunk) +pub const SHIELDED_NOTES_CHUNK_POWER: u8 = 11; + +/// Path to the shielded credit pool: [AddressBalances, "s"] +pub fn shielded_credit_pool_path() -> [&'static [u8]; 2] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + ] +} + +/// Path to the shielded credit pool as a vec +pub fn shielded_credit_pool_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + ] +} + +/// Path to the notes tree: [AddressBalances, "s", [1]] +pub fn shielded_credit_pool_notes_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + &[SHIELDED_NOTES_KEY], + ] +} + +/// Path to the notes tree as a vec: [AddressBalances, "s", [1]] +pub fn shielded_credit_pool_notes_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_NOTES_KEY], + ] +} + +/// Path to the nullifiers tree: [AddressBalances, "s", [2]] +pub fn shielded_credit_pool_nullifiers_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + &[SHIELDED_NULLIFIERS_KEY], + ] +} + +/// Path to the nullifiers tree as a vec: [AddressBalances, "s", [2]] +pub fn shielded_credit_pool_nullifiers_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_NULLIFIERS_KEY], + ] +} + +/// Path to the anchors tree: [AddressBalances, "s", [6]] +pub fn shielded_credit_pool_anchors_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + &[SHIELDED_ANCHORS_IN_POOL_KEY], + ] +} + +/// Path to the anchors tree as a vec: [AddressBalances, "s", [6]] +pub fn shielded_credit_pool_anchors_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_ANCHORS_IN_POOL_KEY], + ] +} + +/// Resolves the nullifiers path based on pool type. +/// +/// Pool types: +/// - 0: Main credit shielded pool → `[AddressBalances, "s", [2]]` +/// - 1: Main token shielded pool (not yet implemented) +/// - 2: Individual token shielded pool (not yet implemented, requires pool_identifier) +pub fn nullifiers_path_for_pool( + pool_type: u32, + _pool_identifier: Option<&[u8]>, +) -> Result>, crate::error::Error> { + use crate::error::drive::DriveError; + use crate::error::Error; + + match pool_type { + 0 => Ok(shielded_credit_pool_nullifiers_path_vec()), + 1 | 2 => Err(Error::Drive(DriveError::NotSupported( + "Token shielded pools not yet implemented", + ))), + _ => Err(Error::Drive(DriveError::InvalidInput(format!( + "Unknown pool type: {}", + pool_type + )))), + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/mod.rs b/packages/rs-drive/src/drive/shielded/prove/mod.rs new file mode 100644 index 00000000000..c979a8d57c5 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/mod.rs @@ -0,0 +1,5 @@ +/// Proves nullifiers using a trunk chunk query. +pub mod prove_nullifiers_trunk_query; + +/// Proves nullifiers using a branch chunk query. +pub mod prove_nullifiers_branch_query; diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs new file mode 100644 index 00000000000..a393eb3639e --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs @@ -0,0 +1,93 @@ +//! Proves nullifiers using a branch chunk query. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::prelude::BlockHeight; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Proves nullifiers using a branch chunk query. + /// + /// This function generates a branch chunk proof for navigating deeper into + /// the nullifiers tree after an initial trunk query. + /// + /// # Parameters + /// - `pool_type`: The shielded pool type (0 = credit, 1 = main token, 2 = individual token). + /// - `pool_identifier`: Optional 32-byte identifier for individual token pools (pool_type=2). + /// - `key`: The key to navigate to in the nullifiers tree before extracting the branch. + /// - `depth`: The depth of the branch to return from the key. + /// - `checkpoint_height`: Block height of the checkpoint to use for consistency. + /// - `platform_version`: The version of the platform that determines the correct method version. + /// + /// # Returns + /// - `Ok(Vec)`: The serialized proof bytes. + /// - `Err(Error)`: If an error occurs during the proof generation. + pub fn prove_nullifiers_branch_query( + &self, + pool_type: u32, + pool_identifier: Option>, + key: Vec, + depth: u8, + checkpoint_height: BlockHeight, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .shielded + .prove_nullifiers_branch_query + { + 0 => self.prove_nullifiers_branch_query_v0( + pool_type, + pool_identifier, + key, + depth, + checkpoint_height, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_nullifiers_branch_query".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Proves nullifiers using a branch chunk query and tracks operations. + pub fn prove_nullifiers_branch_query_operations( + &self, + pool_type: u32, + pool_identifier: Option>, + key: Vec, + depth: u8, + checkpoint_height: BlockHeight, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .shielded + .prove_nullifiers_branch_query + { + 0 => self.prove_nullifiers_branch_query_operations_v0( + pool_type, + pool_identifier, + key, + depth, + checkpoint_height, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_nullifiers_branch_query_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs new file mode 100644 index 00000000000..2712a8f63b8 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs @@ -0,0 +1,70 @@ +use crate::drive::shielded::paths::nullifiers_path_for_pool; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::grove_operations::GroveDBToUse; +use dpp::prelude::BlockHeight; +use dpp::version::PlatformVersion; +use grovedb::PathBranchChunkQuery; + +impl Drive { + pub(super) fn prove_nullifiers_branch_query_v0( + &self, + pool_type: u32, + pool_identifier: Option>, + key: Vec, + depth: u8, + checkpoint_height: BlockHeight, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.prove_nullifiers_branch_query_operations_v0( + pool_type, + pool_identifier, + key, + depth, + checkpoint_height, + &mut vec![], + platform_version, + ) + } + + pub(super) fn prove_nullifiers_branch_query_operations_v0( + &self, + pool_type: u32, + pool_identifier: Option>, + key: Vec, + depth: u8, + checkpoint_height: BlockHeight, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let min_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_min_depth; + let max_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_max_depth; + + if depth < min_depth || depth > max_depth { + return Err(Error::Drive(DriveError::InvalidInput(format!( + "depth {} is outside the allowed range [{}, {}]", + depth, min_depth, max_depth + )))); + } + + let path = nullifiers_path_for_pool(pool_type, pool_identifier.as_deref())?; + let query = PathBranchChunkQuery { path, key, depth }; + + self.grove_get_proved_branch_chunk_query( + &query, + GroveDBToUse::Checkpoint(checkpoint_height), + drive_operations, + &platform_version.drive, + ) + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/mod.rs new file mode 100644 index 00000000000..bc0eff645d9 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/mod.rs @@ -0,0 +1,74 @@ +//! Proves nullifiers using a trunk chunk query. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Proves nullifiers using a trunk chunk query. + /// + /// This function generates a trunk chunk proof for the nullifiers tree of a shielded pool, + /// useful for retrieving the initial structure of the tree up to the configured depth. + /// + /// # Parameters + /// - `pool_type`: The shielded pool type (0 = credit, 1 = main token, 2 = individual token). + /// - `pool_identifier`: Optional 32-byte identifier for individual token pools (pool_type=2). + /// - `platform_version`: The version of the platform that determines the correct method version + /// and the trunk query depth. + /// + /// # Returns + /// - `Ok(Vec)`: The serialized proof bytes. + /// - `Err(Error)`: If an error occurs during the proof generation. + pub fn prove_nullifiers_trunk_query( + &self, + pool_type: u32, + pool_identifier: Option>, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .shielded + .prove_nullifiers_trunk_query + { + 0 => self.prove_nullifiers_trunk_query_v0(pool_type, pool_identifier, platform_version), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_nullifiers_trunk_query".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Proves nullifiers using a trunk chunk query and tracks operations. + pub fn prove_nullifiers_trunk_query_operations( + &self, + pool_type: u32, + pool_identifier: Option>, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .shielded + .prove_nullifiers_trunk_query + { + 0 => self.prove_nullifiers_trunk_query_operations_v0( + pool_type, + pool_identifier, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_nullifiers_trunk_query_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/v0/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/v0/mod.rs new file mode 100644 index 00000000000..0b632a78a10 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_trunk_query/v0/mod.rs @@ -0,0 +1,56 @@ +use crate::drive::shielded::paths::nullifiers_path_for_pool; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::grove_operations::GroveDBToUse; +use dpp::version::PlatformVersion; +use grovedb::PathTrunkChunkQuery; + +impl Drive { + pub(super) fn prove_nullifiers_trunk_query_v0( + &self, + pool_type: u32, + pool_identifier: Option>, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.prove_nullifiers_trunk_query_operations_v0( + pool_type, + pool_identifier, + &mut vec![], + platform_version, + ) + } + + pub(super) fn prove_nullifiers_trunk_query_operations_v0( + &self, + pool_type: u32, + pool_identifier: Option>, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path = nullifiers_path_for_pool(pool_type, pool_identifier.as_deref())?; + let min_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_min_depth; + let max_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_max_depth; + + let query = PathTrunkChunkQuery { + path, + min_depth: Some(min_depth), + max_depth, + }; + + self.grove_get_proved_trunk_chunk_query( + &query, + GroveDBToUse::LatestCheckpoint, + drive_operations, + &platform_version.drive, + ) + } +} diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 7406591e031..f1cb042f63b 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -579,7 +579,16 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { TreeType::ProvableCountSumTree => { Element::empty_provable_count_sum_tree_with_flags(element_flags) } - _ => todo!("new tree types not yet implemented"), + TreeType::CommitmentTree(chunk_power) => { + Element::empty_commitment_tree_with_flags(*chunk_power, element_flags) + } + TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags), + TreeType::BulkAppendTree(chunk_power) => { + Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags) + } + TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => { + Element::empty_dense_tree_with_flags(*chunk_power, element_flags) + } }; LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element) @@ -600,7 +609,7 @@ impl DriveCost for OperationCost { storage_cost, storage_loaded_bytes, hash_node_calls, - sinsemilla_hash_calls: _, + sinsemilla_hash_calls, } = self; let epoch_cost_for_processing_credit_per_byte = fee_version.storage.storage_processing_credit_per_byte; @@ -626,12 +635,14 @@ impl DriveCost for OperationCost { let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block; // this can't overflow let hash_node_cost = blake3_total * (*hash_node_calls as u64); + let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64); seek_cost .checked_add(storage_added_bytes_ephemeral_cost) .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost)) .and_then(|c| c.checked_add(storage_loaded_bytes_cost)) .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost)) .and_then(|c| c.checked_add(hash_node_cost)) + .and_then(|c| c.checked_add(sinsemilla_cost)) .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow")) } } diff --git a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs index 97d4647c957..d1a62293b78 100644 --- a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs +++ b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs @@ -301,12 +301,109 @@ impl Drive { .chain(st.output().into_iter().map(|(address, _)| address)); Drive::balances_for_clear_addresses_query(addresses_to_check) } - StateTransition::Shield(_) - | StateTransition::ShieldedTransfer(_) - | StateTransition::Unshield(_) - | StateTransition::ShieldFromAssetLock(_) - | StateTransition::ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented in prove_state_transition") + StateTransition::ShieldedTransfer(st) => { + use crate::drive::shielded::paths::shielded_credit_pool_nullifiers_path_vec; + use dpp::state_transition::shielded_transfer_transition::accessors::ShieldedTransferTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + let count = nullifier_keys.len() as u16; + + let mut query = grovedb::Query::new(); + query.insert_keys(nullifier_keys); + + PathQuery::new( + shielded_credit_pool_nullifiers_path_vec(), + grovedb::SizedQuery::new(query, Some(count), None), + ) + } + StateTransition::Shield(st) => { + Drive::balances_for_clear_addresses_query(st.inputs().keys()) + } + StateTransition::Unshield(st) => { + use crate::drive::shielded::paths::shielded_credit_pool_nullifiers_path_vec; + use dpp::state_transition::unshield_transition::accessors::UnshieldTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + let mut nf_query = grovedb::Query::new(); + nf_query.insert_keys(nullifier_keys); + let nullifier_pq = PathQuery::new( + shielded_credit_pool_nullifiers_path_vec(), + grovedb::SizedQuery::new(nf_query, None, None), + ); + + let mut address_pq = + Drive::balances_for_clear_addresses_query(std::iter::once(st.output_address())); + address_pq.query.limit = None; + + PathQuery::merge( + vec![&nullifier_pq, &address_pq], + &platform_version.drive.grove_version, + )? + } + StateTransition::ShieldedWithdrawal(st) => { + use crate::drive::shielded::paths::shielded_credit_pool_nullifiers_path_vec; + use dpp::data_contracts::withdrawals_contract; + use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; + use dpp::document::Document; + use dpp::state_transition::shielded_withdrawal_transition::accessors::ShieldedWithdrawalTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + let mut nf_query = grovedb::Query::new(); + nf_query.insert_keys(nullifier_keys.clone()); + let nullifier_pq = PathQuery::new( + shielded_credit_pool_nullifiers_path_vec(), + grovedb::SizedQuery::new(nf_query, None, None), + ); + + // Compute withdrawal document ID deterministically + let first_nullifier = nullifier_keys.first().ok_or_else(|| { + Error::Proof(ProofError::InvalidTransition( + "shielded withdrawal has no nullifiers".to_string(), + )) + })?; + let mut entropy = Vec::new(); + entropy.extend_from_slice(first_nullifier); + entropy.extend_from_slice(st.output_script().as_bytes()); + let document_id = Document::generate_document_id_v0( + &withdrawals_contract::ID, + &withdrawals_contract::OWNER_ID, + withdrawal::NAME, + &entropy, + ); + + let doc_query = SingleDocumentDriveQuery { + contract_id: withdrawals_contract::ID.to_buffer(), + document_type_name: withdrawal::NAME.to_string(), + document_type_keeps_history: false, + document_id: document_id.to_buffer(), + block_time_ms: None, + contested_status: SingleDocumentDriveQueryContestedStatus::NotContested, + }; + let mut doc_pq = doc_query.construct_path_query(platform_version)?; + doc_pq.query.limit = None; + + PathQuery::merge( + vec![&nullifier_pq, &doc_pq], + &platform_version.drive.grove_version, + )? + } + StateTransition::ShieldFromAssetLock(st) => { + use dpp::identity::state_transition::AssetLockProved; + + let outpoint = st.asset_lock_proof().out_point().ok_or_else(|| { + Error::Proof(ProofError::InvalidTransition( + "shield from asset lock has no outpoint".to_string(), + )) + })?; + let outpoint_bytes: [u8; 36] = outpoint.into(); + + let mut query = grovedb::Query::new(); + query.insert_key(outpoint_bytes.to_vec()); + + PathQuery::new( + vec![vec![72u8]], // RootTree::SpentAssetLockTransactions + grovedb::SizedQuery::new(query, Some(1), None), + ) } }; diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/mod.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/mod.rs index dd57109a435..dbfa6c50e04 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/mod.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/mod.rs @@ -7,6 +7,7 @@ mod address_funds; mod batch; mod contract; mod identity; +mod shielded; mod system; use crate::error::Error; @@ -106,6 +107,26 @@ impl DriveHighLevelOperationConverter for StateTransitionAction { address_funding_from_asset_lock .into_high_level_drive_operations(epoch, platform_version) } + StateTransitionAction::ShieldAction(shield_action) => { + shield_action.into_high_level_drive_operations(epoch, platform_version) + } + StateTransitionAction::ShieldedTransferAction(shielded_transfer_action) => { + shielded_transfer_action.into_high_level_drive_operations(epoch, platform_version) + } + StateTransitionAction::UnshieldAction(unshield_action) => { + unshield_action.into_high_level_drive_operations(epoch, platform_version) + } + StateTransitionAction::ShieldFromAssetLockAction(shield_from_asset_lock_action) => { + shield_from_asset_lock_action + .into_high_level_drive_operations(epoch, platform_version) + } + StateTransitionAction::ShieldedWithdrawalAction(shielded_withdrawal_action) => { + shielded_withdrawal_action.into_high_level_drive_operations(epoch, platform_version) + } + StateTransitionAction::PenalizeShieldedPoolAction(penalize_shielded_pool_action) => { + penalize_shielded_pool_action + .into_high_level_drive_operations(epoch, platform_version) + } } } } diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs new file mode 100644 index 00000000000..a3ec63c304f --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs @@ -0,0 +1,60 @@ +mod shield_from_asset_lock_transition; +mod shield_transition; +mod shielded_transfer_transition; +mod shielded_withdrawal_transition; +mod unshield_transition; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use crate::util::batch::drive_op_batch::ShieldedPoolOperationType; +use crate::util::batch::DriveOperation; + +/// Insert each nullifier (InsertOnly to prevent double-spend). +pub(super) fn insert_nullifiers<'a>( + ops: &mut Vec>, + notes: &[ShieldedActionNote], +) { + for note in notes { + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::InsertNullifier { + nullifier: note.nullifier, + }, + )); + } +} + +/// Insert notes into the CommitmentTree (appends cmx to frontier + stores cmx||rho||encrypted_note). +/// +/// Each action's nullifier (rho) is stored alongside the note so light clients can derive +/// Rho for trial decryption. +pub(super) fn insert_notes<'a>(ops: &mut Vec>, notes: &[ShieldedActionNote]) { + for note in notes { + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::InsertNote { + nullifier: note.nullifier, + cmx: note.cmx, + encrypted_note: note.encrypted_note.clone(), + }, + )); + } +} + +/// Update pool total balance. +pub(super) fn update_balance<'a>(ops: &mut Vec>, new_total_balance: u64) { + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance }, + )); +} + +/// Store nullifiers to recent block storage for catch-up sync RPCs. +pub(super) fn store_nullifiers_for_block<'a>( + ops: &mut Vec>, + notes: &[ShieldedActionNote], +) { + if !notes.is_empty() { + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::StoreNullifiersForBlock { + nullifiers: notes.iter().map(|n| n.nullifier).collect(), + }, + )); + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_from_asset_lock_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_from_asset_lock_transition.rs new file mode 100644 index 00000000000..58d878e68ed --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_from_asset_lock_transition.rs @@ -0,0 +1,78 @@ +use super::{insert_notes, update_balance}; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::shielded::shield_from_asset_lock::ShieldFromAssetLockTransitionAction; +use crate::util::batch::drive_op_batch::SystemOperationType; +use crate::util::batch::DriveOperation; +use dpp::asset_lock::reduced_asset_lock_value::AssetLockValue; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for ShieldFromAssetLockTransitionAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .state_transitions + .convert_to_high_level_operations + .shield_from_asset_lock_transition + { + 0 => match self { + ShieldFromAssetLockTransitionAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Add credits to system from the asset lock + ops.push(DriveOperation::SystemOperation( + SystemOperationType::AddToSystemCredits { + amount: v0.asset_lock_value_to_be_consumed, + }, + )); + + // 2. Record asset lock as consumed (prevent replay) + let asset_lock_value = AssetLockValue::new( + v0.asset_lock_value_to_be_consumed, + vec![], // tx_out_script not needed for shielded + 0, // remaining_credit_value = 0 (fully consumed) + vec![], // no used tags for shielded + platform_version, + ) + .map_err(|e| Error::Protocol(Box::new(e)))?; + ops.push(DriveOperation::SystemOperation( + SystemOperationType::AddUsedAssetLock { + asset_lock_outpoint: v0.asset_lock_outpoint.into(), + asset_lock_value, + }, + )); + + // 3. Insert notes into CommitmentTree + insert_notes(&mut ops, &v0.notes); + + // 4. Update total balance + let new_total_balance = + v0.current_total_balance + .checked_add(v0.shield_amount) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance overflow when adding shield_from_asset_lock amount" + .to_string(), + )) + })?; + update_balance(&mut ops, new_total_balance); + + Ok(ops) + } + }, + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "ShieldFromAssetLockTransitionAction::into_high_level_drive_operations" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_transition.rs new file mode 100644 index 00000000000..4572c2f9d51 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shield_transition.rs @@ -0,0 +1,64 @@ +use super::{insert_notes, update_balance}; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::shielded::shield::ShieldTransitionAction; +use crate::util::batch::drive_op_batch::AddressFundsOperationType; +use crate::util::batch::DriveOperation; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for ShieldTransitionAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .state_transitions + .convert_to_high_level_operations + .shield_transition + { + 0 => match self { + ShieldTransitionAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Debit each input address: set remaining balance + for (address, (nonce, remaining_balance)) in v0.inputs_with_remaining_balance { + ops.push(DriveOperation::AddressFundsOperation( + AddressFundsOperationType::SetBalanceToAddress { + address, + nonce, + balance: remaining_balance, + }, + )); + } + + // 2. Insert notes into CommitmentTree + insert_notes(&mut ops, &v0.notes); + + // 3. Update total balance + let new_total_balance = v0 + .current_total_balance + .checked_add(v0.shield_amount) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance overflow when adding shield amount" + .to_string(), + )) + })?; + update_balance(&mut ops, new_total_balance); + + Ok(ops) + } + }, + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "ShieldTransitionAction::into_high_level_drive_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs new file mode 100644 index 00000000000..2c7f8eb9783 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs @@ -0,0 +1,59 @@ +use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::shielded::shielded_transfer::ShieldedTransferTransitionAction; +use crate::util::batch::DriveOperation; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for ShieldedTransferTransitionAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .state_transitions + .convert_to_high_level_operations + .shielded_transfer_transition + { + 0 => match self { + ShieldedTransferTransitionAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Insert each nullifier (InsertOnly to prevent double-spend) + insert_nullifiers(&mut ops, &v0.notes); + + // 2. Insert notes into CommitmentTree + insert_notes(&mut ops, &v0.notes); + + // 3. Update total balance (pool decreases by fee_amount) + let new_total_balance = v0 + .current_total_balance + .checked_sub(v0.fee_amount) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance underflow when subtracting fee_amount" + .to_string(), + )) + })?; + update_balance(&mut ops, new_total_balance); + + // 4. Store nullifiers to recent block storage for catch-up sync + store_nullifiers_for_block(&mut ops, &v0.notes); + + Ok(ops) + } + }, + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "ShieldedTransferTransitionAction::into_high_level_drive_operations" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs new file mode 100644 index 00000000000..1c3860384a0 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs @@ -0,0 +1,86 @@ +use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::shielded::shielded_withdrawal::ShieldedWithdrawalTransitionAction; +use crate::util::batch::drive_op_batch::SystemOperationType; +use crate::util::batch::{DocumentOperationType, DriveOperation}; +use crate::util::object_size_info::{DocumentInfo, OwnedDocumentInfo}; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for ShieldedWithdrawalTransitionAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .state_transitions + .convert_to_high_level_operations + .shielded_withdrawal_transition + { + 0 => match self { + ShieldedWithdrawalTransitionAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Insert nullifiers (prevent double-spend) + insert_nullifiers(&mut ops, &v0.notes); + + // 2. Insert change notes into CommitmentTree + insert_notes(&mut ops, &v0.notes); + + // 3. Update total balance: subtract withdrawal amount + fee (both leave the pool) + let total_deduction = + v0.amount.checked_add(v0.fee_amount).ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "overflow when adding shielded_withdrawal amount and fee" + .to_string(), + )) + })?; + let new_total_balance = + v0.current_total_balance + .checked_sub(total_deduction) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance underflow when subtracting shielded_withdrawal amount and fee" + .to_string(), + )) + })?; + update_balance(&mut ops, new_total_balance); + + // 4. Add withdrawal document + ops.push(DriveOperation::DocumentOperation( + DocumentOperationType::AddWithdrawalDocument { + owned_document_info: OwnedDocumentInfo { + document_info: DocumentInfo::DocumentOwnedInfo(( + v0.prepared_withdrawal_document, + None, + )), + owner_id: None, + }, + }, + )); + + // 5. Remove credits from system (they leave the system to Core) + ops.push(DriveOperation::SystemOperation( + SystemOperationType::RemoveFromSystemCredits { amount: v0.amount }, + )); + + // 6. Store nullifiers to recent block storage for catch-up sync + store_nullifiers_for_block(&mut ops, &v0.notes); + + Ok(ops) + } + }, + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "ShieldedWithdrawalTransitionAction::into_high_level_drive_operations" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs new file mode 100644 index 00000000000..a40a7cac0bf --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs @@ -0,0 +1,74 @@ +use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::shielded::unshield::UnshieldTransitionAction; +use crate::util::batch::drive_op_batch::AddressFundsOperationType; +use crate::util::batch::DriveOperation; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for UnshieldTransitionAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + platform_version: &PlatformVersion, + ) -> Result>, Error> { + match platform_version + .drive + .methods + .state_transitions + .convert_to_high_level_operations + .unshield_transition + { + 0 => match self { + UnshieldTransitionAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Insert each nullifier (InsertOnly to prevent double-spend) + insert_nullifiers(&mut ops, &v0.notes); + + // 2. Credit the output address with the unshielded amount + ops.push(DriveOperation::AddressFundsOperation( + AddressFundsOperationType::AddBalanceToAddress { + address: v0.output_address, + balance_to_add: v0.amount, + }, + )); + + // 3. Insert notes into CommitmentTree (change outputs) + insert_notes(&mut ops, &v0.notes); + + // 4. Update total balance + // Pool decreases by amount (to output address) + fee_amount (to proposers) + let total_deduction = + v0.amount.checked_add(v0.fee_amount).ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "overflow when adding unshield amount and fee".to_string(), + )) + })?; + let new_total_balance = + v0.current_total_balance + .checked_sub(total_deduction) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance underflow when subtracting unshield amount and fee" + .to_string(), + )) + })?; + update_balance(&mut ops, new_total_balance); + + // 5. Store nullifiers to recent block storage for catch-up sync + store_nullifiers_for_block(&mut ops, &v0.notes); + + Ok(ops) + } + }, + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "UnshieldTransitionAction::into_high_level_drive_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/mod.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/mod.rs index 84721e56f9d..f3fe110aac1 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/mod.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/mod.rs @@ -2,3 +2,4 @@ mod bump_address_input_nonces; mod bump_identity_data_contract_nonce; mod bump_identity_nonce; mod partially_use_asset_lock; +mod penalize_shielded_pool; diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs new file mode 100644 index 00000000000..a62d0b8a2aa --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs @@ -0,0 +1,38 @@ +use crate::error::Error; +use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; +use crate::state_transition_action::system::penalize_shielded_pool_action::PenalizeShieldedPoolAction; +use crate::util::batch::drive_op_batch::ShieldedPoolOperationType; +use crate::util::batch::DriveOperation; +use dpp::block::epoch::Epoch; +use dpp::version::PlatformVersion; + +impl DriveHighLevelOperationConverter for PenalizeShieldedPoolAction { + fn into_high_level_drive_operations<'a>( + self, + _epoch: &Epoch, + _platform_version: &PlatformVersion, + ) -> Result>, Error> { + match self { + PenalizeShieldedPoolAction::V0(v0) => { + let mut ops: Vec> = Vec::new(); + + // 1. Record nullifiers as spent (prevents replaying the same invalid proof) + for nullifier in v0.nullifiers.iter() { + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::InsertNullifier { + nullifier: *nullifier, + }, + )); + } + + // 2. Deduct penalty from pool total balance + let new_total_balance = v0.current_total_balance.saturating_sub(v0.penalty_amount); + ops.push(DriveOperation::ShieldedPoolOperation( + ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance }, + )); + + Ok(ops) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/mod.rs b/packages/rs-drive/src/state_transition_action/mod.rs index 24ef0fbc272..074b899e1e4 100644 --- a/packages/rs-drive/src/state_transition_action/mod.rs +++ b/packages/rs-drive/src/state_transition_action/mod.rs @@ -11,6 +11,8 @@ pub mod action_convert_to_operations; pub mod address_funds; /// documents_batch pub mod batch; +/// shielded +pub mod shielded; use crate::state_transition_action::address_funds::address_credit_withdrawal::AddressCreditWithdrawalTransitionAction; use crate::state_transition_action::address_funds::address_funding_from_asset_lock::AddressFundingFromAssetLockTransitionAction; @@ -27,6 +29,11 @@ use crate::state_transition_action::identity::identity_topup::IdentityTopUpTrans use crate::state_transition_action::identity::identity_topup_from_addresses::IdentityTopUpFromAddressesTransitionAction; use crate::state_transition_action::identity::identity_update::IdentityUpdateTransitionAction; use crate::state_transition_action::identity::masternode_vote::MasternodeVoteTransitionAction; +use crate::state_transition_action::shielded::shield::ShieldTransitionAction; +use crate::state_transition_action::shielded::shield_from_asset_lock::ShieldFromAssetLockTransitionAction; +use crate::state_transition_action::shielded::shielded_transfer::ShieldedTransferTransitionAction; +use crate::state_transition_action::shielded::shielded_withdrawal::ShieldedWithdrawalTransitionAction; +use crate::state_transition_action::shielded::unshield::UnshieldTransitionAction; use crate::state_transition_action::system::bump_address_input_nonces_action::{ BumpAddressInputNonceActionAccessorsV0, BumpAddressInputNoncesAction, }; @@ -39,6 +46,7 @@ use crate::state_transition_action::system::bump_identity_nonce_action::{ use crate::state_transition_action::system::partially_use_asset_lock_action::{ PartiallyUseAssetLockAction, PartiallyUseAssetLockActionAccessorsV0, }; +use crate::state_transition_action::system::penalize_shielded_pool_action::PenalizeShieldedPoolAction; use derive_more::From; use dpp::prelude::UserFeeIncrease; @@ -90,6 +98,18 @@ pub enum StateTransitionAction { /// partially use the asset lock for funding invalid asset lock transactions like /// identity top up and identity create BumpAddressInputNoncesAction(BumpAddressInputNoncesAction), + /// shield (address -> shielded pool) + ShieldAction(ShieldTransitionAction), + /// shielded transfer (shielded -> shielded) + ShieldedTransferAction(ShieldedTransferTransitionAction), + /// unshield (shielded pool -> address) + UnshieldAction(UnshieldTransitionAction), + /// shield from asset lock (L1 asset lock -> shielded pool) + ShieldFromAssetLockAction(ShieldFromAssetLockTransitionAction), + /// shielded withdrawal (shielded pool -> L1 core address) + ShieldedWithdrawalAction(ShieldedWithdrawalTransitionAction), + /// penalize shielded pool for invalid ZK proof + PenalizeShieldedPoolAction(PenalizeShieldedPoolAction), } impl StateTransitionAction { @@ -135,6 +155,22 @@ impl StateTransitionAction { StateTransitionAction::BumpAddressInputNoncesAction(action) => { action.user_fee_increase() } + StateTransitionAction::ShieldAction(action) => action.user_fee_increase(), + StateTransitionAction::ShieldedTransferAction(_) => { + UserFeeIncrease::default() // 0 (fee is locked by Orchard binding signature) + } + StateTransitionAction::UnshieldAction(_) => { + UserFeeIncrease::default() // 0 (fee is locked by Orchard binding signature) + } + StateTransitionAction::ShieldFromAssetLockAction(_) => { + UserFeeIncrease::default() // 0 (fee comes from asset lock excess) + } + StateTransitionAction::ShieldedWithdrawalAction(_) => { + UserFeeIncrease::default() // 0 (fee is locked by Orchard binding signature) + } + StateTransitionAction::PenalizeShieldedPoolAction(_) => { + UserFeeIncrease::default() // 0 (no user fee increase for penalty actions) + } } } } diff --git a/packages/rs-drive/src/state_transition_action/shielded/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/mod.rs new file mode 100644 index 00000000000..3c4be746467 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/mod.rs @@ -0,0 +1,21 @@ +/// Shield transition action +pub mod shield; +/// Shield from asset lock transition action +pub mod shield_from_asset_lock; +/// Shielded transfer transition action +pub mod shielded_transfer; +/// Shielded withdrawal transition action +pub mod shielded_withdrawal; +/// Unshield transition action +pub mod unshield; + +/// One note from an Orchard action: the three per-action fields that travel together. +#[derive(Debug, Clone)] +pub struct ShieldedActionNote { + /// Nullifier (needed for Rho derivation in trial decryption) + pub nullifier: [u8; 32], + /// Note commitment (cmx value) + pub cmx: [u8; 32], + /// Encrypted note ciphertext + pub encrypted_note: Vec, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/mod.rs new file mode 100644 index 00000000000..4e66570167d --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/mod.rs @@ -0,0 +1,54 @@ +/// transformer +pub mod transformer; +/// v0 +pub mod v0; + +use crate::state_transition_action::shielded::shield::v0::ShieldTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use derive_more::From; +use dpp::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +use dpp::fee::Credits; +use dpp::prelude::{AddressNonce, UserFeeIncrease}; +use std::collections::BTreeMap; + +/// Shield transition action +#[derive(Debug, Clone, From)] +pub enum ShieldTransitionAction { + /// v0 + V0(ShieldTransitionActionV0), +} + +impl ShieldTransitionAction { + /// Get inputs with remaining balance + pub fn inputs_with_remaining_balance( + &self, + ) -> &BTreeMap { + match self { + ShieldTransitionAction::V0(transition) => &transition.inputs_with_remaining_balance, + } + } + /// Get the shield amount + pub fn shield_amount(&self) -> Credits { + match self { + ShieldTransitionAction::V0(transition) => transition.shield_amount, + } + } + /// Get notes + pub fn notes(&self) -> &[ShieldedActionNote] { + match self { + ShieldTransitionAction::V0(transition) => &transition.notes, + } + } + /// fee multiplier + pub fn user_fee_increase(&self) -> UserFeeIncrease { + match self { + ShieldTransitionAction::V0(transition) => transition.user_fee_increase, + } + } + /// fee strategy + pub fn fee_strategy(&self) -> &AddressFundsFeeStrategy { + match self { + ShieldTransitionAction::V0(transition) => &transition.fee_strategy, + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs new file mode 100644 index 00000000000..be833da00d7 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs @@ -0,0 +1,32 @@ +use crate::state_transition_action::shielded::shield::v0::ShieldTransitionActionV0; +use crate::state_transition_action::shielded::shield::ShieldTransitionAction; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::address_funds::PlatformAddress; +use dpp::fee::Credits; +use dpp::prelude::{AddressNonce, ConsensusValidationResult}; +use dpp::state_transition::shield_transition::ShieldTransition; +use std::collections::BTreeMap; + +impl ShieldTransitionAction { + /// Transforms the state transition into an action + pub fn try_from_transition( + value: &ShieldTransition, + inputs_with_remaining_balance: BTreeMap, + shield_amount: Credits, + notes: Vec, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + match value { + ShieldTransition::V0(v0) => { + let result = ShieldTransitionActionV0::try_from_transition( + v0, + inputs_with_remaining_balance, + shield_amount, + notes, + current_total_balance, + ); + result.map(|action| action.into()) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/v0/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/mod.rs new file mode 100644 index 00000000000..b4802fe6436 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/mod.rs @@ -0,0 +1,24 @@ +mod transformer; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +use dpp::fee::Credits; +use dpp::prelude::{AddressNonce, UserFeeIncrease}; +use std::collections::BTreeMap; + +/// Shield transition action v0 +#[derive(Debug, Clone)] +pub struct ShieldTransitionActionV0 { + /// inputs with remaining balance after shielding + pub inputs_with_remaining_balance: BTreeMap, + /// The amount being shielded (sent into the shielded pool) + pub shield_amount: Credits, + /// Notes from the orchard bundle actions + pub notes: Vec, + /// fee strategy + pub fee_strategy: AddressFundsFeeStrategy, + /// fee multiplier + pub user_fee_increase: UserFeeIncrease, + /// Current total balance of the shielded pool + pub current_total_balance: Credits, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs new file mode 100644 index 00000000000..2a9f5bbab2d --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs @@ -0,0 +1,27 @@ +use crate::state_transition_action::shielded::shield::v0::ShieldTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::address_funds::PlatformAddress; +use dpp::fee::Credits; +use dpp::prelude::{AddressNonce, ConsensusValidationResult}; +use dpp::state_transition::state_transitions::shielded::shield_transition::v0::ShieldTransitionV0; +use std::collections::BTreeMap; + +impl ShieldTransitionActionV0 { + /// Transforms the shield transition into an action + pub fn try_from_transition( + value: &ShieldTransitionV0, + inputs_with_remaining_balance: BTreeMap, + shield_amount: Credits, + notes: Vec, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + ConsensusValidationResult::new_with_data(ShieldTransitionActionV0 { + inputs_with_remaining_balance, + shield_amount, + notes, + fee_strategy: value.fee_strategy.clone(), + user_fee_increase: value.user_fee_increase, + current_total_balance, + }) + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/mod.rs new file mode 100644 index 00000000000..0147b57be76 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/mod.rs @@ -0,0 +1,53 @@ +/// transformer +pub mod transformer; +/// v0 +pub mod v0; + +use crate::state_transition_action::shielded::shield_from_asset_lock::v0::ShieldFromAssetLockTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use derive_more::From; +use dpp::fee::Credits; + +/// Shield from asset lock transition action +#[derive(Debug, Clone, From)] +pub enum ShieldFromAssetLockTransitionAction { + /// v0 + V0(ShieldFromAssetLockTransitionActionV0), +} + +impl ShieldFromAssetLockTransitionAction { + /// Get asset lock outpoint + pub fn asset_lock_outpoint(&self) -> &[u8; 36] { + match self { + ShieldFromAssetLockTransitionAction::V0(transition) => &transition.asset_lock_outpoint, + } + } + /// Get remaining asset lock value to be consumed + pub fn asset_lock_value_to_be_consumed(&self) -> Credits { + match self { + ShieldFromAssetLockTransitionAction::V0(transition) => { + transition.asset_lock_value_to_be_consumed + } + } + } + /// Get signable bytes hasher + pub fn signable_bytes_hasher(&self) -> &[u8; 32] { + match self { + ShieldFromAssetLockTransitionAction::V0(transition) => { + &transition.signable_bytes_hasher + } + } + } + /// Get the shield amount + pub fn shield_amount(&self) -> Credits { + match self { + ShieldFromAssetLockTransitionAction::V0(transition) => transition.shield_amount, + } + } + /// Get notes + pub fn notes(&self) -> &[ShieldedActionNote] { + match self { + ShieldFromAssetLockTransitionAction::V0(transition) => &transition.notes, + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs new file mode 100644 index 00000000000..0cec6e308c1 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs @@ -0,0 +1,34 @@ +use crate::state_transition_action::shielded::shield_from_asset_lock::v0::ShieldFromAssetLockTransitionActionV0; +use crate::state_transition_action::shielded::shield_from_asset_lock::ShieldFromAssetLockTransitionAction; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; + +impl ShieldFromAssetLockTransitionAction { + /// Transforms the state transition into an action + pub fn try_from_transition( + value: &ShieldFromAssetLockTransition, + asset_lock_outpoint: [u8; 36], + asset_lock_value_to_be_consumed: Credits, + signable_bytes_hasher: [u8; 32], + shield_amount: Credits, + notes: Vec, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + match value { + ShieldFromAssetLockTransition::V0(v0) => { + let result = ShieldFromAssetLockTransitionActionV0::try_from_transition( + v0, + asset_lock_outpoint, + asset_lock_value_to_be_consumed, + signable_bytes_hasher, + shield_amount, + notes, + current_total_balance, + ); + result.map(|action| action.into()) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/mod.rs new file mode 100644 index 00000000000..3bb14e3f942 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/mod.rs @@ -0,0 +1,21 @@ +mod transformer; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; + +/// Shield from asset lock transition action v0 +#[derive(Debug, Clone)] +pub struct ShieldFromAssetLockTransitionActionV0 { + /// Asset lock outpoint bytes (txid + vout) + pub asset_lock_outpoint: [u8; 36], + /// Remaining asset lock value to be consumed + pub asset_lock_value_to_be_consumed: Credits, + /// SHA256(signable_bytes) for replay protection + pub signable_bytes_hasher: [u8; 32], + /// Amount going into shielded pool (|value_balance|) + pub shield_amount: Credits, + /// Notes from the orchard bundle actions + pub notes: Vec, + /// Current total balance of the shielded pool + pub current_total_balance: Credits, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs new file mode 100644 index 00000000000..56f5c4969d3 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs @@ -0,0 +1,27 @@ +use crate::state_transition_action::shielded::shield_from_asset_lock::v0::ShieldFromAssetLockTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::state_transitions::shielded::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; + +impl ShieldFromAssetLockTransitionActionV0 { + /// Transforms the shield from asset lock transition into an action + pub fn try_from_transition( + _value: &ShieldFromAssetLockTransitionV0, + asset_lock_outpoint: [u8; 36], + asset_lock_value_to_be_consumed: Credits, + signable_bytes_hasher: [u8; 32], + shield_amount: Credits, + notes: Vec, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + ConsensusValidationResult::new_with_data(ShieldFromAssetLockTransitionActionV0 { + asset_lock_outpoint, + asset_lock_value_to_be_consumed, + signable_bytes_hasher, + shield_amount, + notes, + current_total_balance, + }) + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/mod.rs new file mode 100644 index 00000000000..c5430e83d29 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/mod.rs @@ -0,0 +1,37 @@ +/// transformer +pub mod transformer; +/// v0 +pub mod v0; + +use crate::state_transition_action::shielded::shielded_transfer::v0::ShieldedTransferTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use derive_more::From; +use dpp::fee::Credits; + +/// Shielded transfer transition action +#[derive(Debug, Clone, From)] +pub enum ShieldedTransferTransitionAction { + /// v0 + V0(ShieldedTransferTransitionActionV0), +} + +impl ShieldedTransferTransitionAction { + /// Get notes + pub fn notes(&self) -> &[ShieldedActionNote] { + match self { + ShieldedTransferTransitionAction::V0(transition) => &transition.notes, + } + } + /// Get anchor + pub fn anchor(&self) -> &[u8; 32] { + match self { + ShieldedTransferTransitionAction::V0(transition) => &transition.anchor, + } + } + /// Get fee amount + pub fn fee_amount(&self) -> Credits { + match self { + ShieldedTransferTransitionAction::V0(transition) => transition.fee_amount, + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs new file mode 100644 index 00000000000..b1e329e233c --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs @@ -0,0 +1,30 @@ +use crate::state_transition_action::shielded::shielded_transfer::v0::ShieldedTransferTransitionActionV0; +use crate::state_transition_action::shielded::shielded_transfer::ShieldedTransferTransitionAction; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::shielded_transfer_transition::ShieldedTransferTransition; + +impl ShieldedTransferTransitionAction { + /// Transforms the state transition into an action + pub fn try_from_transition( + value: &ShieldedTransferTransition, + notes: Vec, + anchor: [u8; 32], + fee_amount: Credits, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + match value { + ShieldedTransferTransition::V0(v0) => { + let result = ShieldedTransferTransitionActionV0::try_from_transition( + v0, + notes, + anchor, + fee_amount, + current_total_balance, + ); + result.map(|action| action.into()) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/mod.rs new file mode 100644 index 00000000000..1c26ed90f05 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/mod.rs @@ -0,0 +1,17 @@ +mod transformer; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; + +/// Shielded transfer transition action v0 +#[derive(Debug, Clone)] +pub struct ShieldedTransferTransitionActionV0 { + /// Notes from the orchard bundle actions + pub notes: Vec, + /// The anchor (root of the commitment tree) used for verification + pub anchor: [u8; 32], + /// Fee amount extracted from the value balance + pub fee_amount: Credits, + /// Current total balance of the shielded pool + pub current_total_balance: Credits, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs new file mode 100644 index 00000000000..b4c356d8bec --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs @@ -0,0 +1,23 @@ +use crate::state_transition_action::shielded::shielded_transfer::v0::ShieldedTransferTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::state_transitions::shielded::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; + +impl ShieldedTransferTransitionActionV0 { + /// Transforms the shielded transfer transition into an action + pub fn try_from_transition( + _value: &ShieldedTransferTransitionV0, + notes: Vec, + anchor: [u8; 32], + fee_amount: Credits, + current_total_balance: Credits, + ) -> ConsensusValidationResult { + ConsensusValidationResult::new_with_data(ShieldedTransferTransitionActionV0 { + notes, + anchor, + fee_amount, + current_total_balance, + }) + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/mod.rs new file mode 100644 index 00000000000..fbe4a5782b4 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/mod.rs @@ -0,0 +1,80 @@ +/// transformer +pub mod transformer; +/// v0 +pub mod v0; + +use crate::state_transition_action::shielded::shielded_withdrawal::v0::ShieldedWithdrawalTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use derive_more::From; +use dpp::document::Document; +use dpp::fee::Credits; +use dpp::identity::core_script::CoreScript; +use dpp::withdrawal::Pooling; + +/// Shielded withdrawal transition action +#[derive(Debug, Clone, From)] +pub enum ShieldedWithdrawalTransitionAction { + /// v0 + V0(ShieldedWithdrawalTransitionActionV0), +} + +impl ShieldedWithdrawalTransitionAction { + /// Get withdrawal amount + pub fn amount(&self) -> Credits { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => transition.amount, + } + } + /// Get notes + pub fn notes(&self) -> &[ShieldedActionNote] { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => &transition.notes, + } + } + /// Get anchor + pub fn anchor(&self) -> &[u8; 32] { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => &transition.anchor, + } + } + /// Get core fee per byte + pub fn core_fee_per_byte(&self) -> u32 { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => transition.core_fee_per_byte, + } + } + /// Get pooling strategy + pub fn pooling(&self) -> Pooling { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => transition.pooling, + } + } + /// Get output script + pub fn output_script(&self) -> &CoreScript { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => &transition.output_script, + } + } + /// Fee amount (value_balance - amount), paid to proposers + pub fn fee_amount(&self) -> Credits { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => transition.fee_amount, + } + } + /// Get prepared withdrawal document + pub fn prepared_withdrawal_document(&self) -> &Document { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => { + &transition.prepared_withdrawal_document + } + } + } + /// Get prepared withdrawal document owned + pub fn prepared_withdrawal_document_owned(self) -> Document { + match self { + ShieldedWithdrawalTransitionAction::V0(transition) => { + transition.prepared_withdrawal_document + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs new file mode 100644 index 00000000000..7048f97ebda --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs @@ -0,0 +1,30 @@ +use crate::state_transition_action::shielded::shielded_withdrawal::v0::ShieldedWithdrawalTransitionActionV0; +use crate::state_transition_action::shielded::shielded_withdrawal::ShieldedWithdrawalTransitionAction; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; + +impl ShieldedWithdrawalTransitionAction { + /// Transforms the state transition into an action + pub fn try_from_transition( + value: &ShieldedWithdrawalTransition, + notes: Vec, + anchor: [u8; 32], + current_total_balance: Credits, + creation_time_ms: u64, + ) -> ConsensusValidationResult { + match value { + ShieldedWithdrawalTransition::V0(v0) => { + let result = ShieldedWithdrawalTransitionActionV0::try_from_transition( + v0, + notes, + anchor, + current_total_balance, + creation_time_ms, + ); + result.map(|action| action.into()) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/mod.rs new file mode 100644 index 00000000000..9ff494ca7f1 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/mod.rs @@ -0,0 +1,30 @@ +mod transformer; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::document::Document; +use dpp::fee::Credits; +use dpp::identity::core_script::CoreScript; +use dpp::withdrawal::Pooling; + +/// Shielded withdrawal transition action v0 +#[derive(Debug, Clone)] +pub struct ShieldedWithdrawalTransitionActionV0 { + /// Withdrawal amount in credits + pub amount: Credits, + /// Notes from the orchard bundle actions + pub notes: Vec, + /// Merkle root used for spends + pub anchor: [u8; 32], + /// Core transaction fee rate + pub core_fee_per_byte: u32, + /// Withdrawal pooling strategy + pub pooling: Pooling, + /// Core address receiving funds + pub output_script: CoreScript, + /// Fee amount (value_balance - amount), paid to proposers + pub fee_amount: Credits, + /// Current total balance of the shielded pool + pub current_total_balance: Credits, + /// Pre-built withdrawal document (status: QUEUED) + pub prepared_withdrawal_document: Document, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs new file mode 100644 index 00000000000..7e86164b84d --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs @@ -0,0 +1,77 @@ +use crate::state_transition_action::shielded::shielded_withdrawal::v0::ShieldedWithdrawalTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::data_contracts::withdrawals_contract; +use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; +use dpp::document::{Document, DocumentV0}; +use dpp::fee::Credits; +use dpp::platform_value::platform_value; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::state_transitions::shielded::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; + +impl ShieldedWithdrawalTransitionActionV0 { + /// Transforms the shielded withdrawal transition into an action + pub fn try_from_transition( + value: &ShieldedWithdrawalTransitionV0, + notes: Vec, + anchor: [u8; 32], + current_total_balance: Credits, + creation_time_ms: u64, + ) -> ConsensusValidationResult { + // Generate entropy from first nullifier + output_script for document ID + let mut entropy = Vec::new(); + if let Some(first_note) = notes.first() { + entropy.extend_from_slice(&first_note.nullifier); + } + entropy.extend_from_slice(value.output_script.as_bytes()); + + // The owner_id is the contract owner + let owner_id = withdrawals_contract::OWNER_ID; + + let document_id = Document::generate_document_id_v0( + &withdrawals_contract::ID, + &owner_id, + withdrawal::NAME, + &entropy, + ); + + let document_data = platform_value!({ + withdrawal::properties::AMOUNT: value.unshielding_amount, + withdrawal::properties::CORE_FEE_PER_BYTE: value.core_fee_per_byte, + withdrawal::properties::POOLING: value.pooling, + withdrawal::properties::OUTPUT_SCRIPT: value.output_script.as_bytes(), + withdrawal::properties::STATUS: withdrawals_contract::WithdrawalStatus::QUEUED, + }); + + let withdrawal_document = DocumentV0 { + id: document_id, + owner_id, + properties: document_data + .into_btree_string_map() + .expect("platform_value macro produces a map"), + revision: Some(1), + created_at: Some(creation_time_ms), + updated_at: Some(creation_time_ms), + transferred_at: None, + created_at_block_height: None, + updated_at_block_height: None, + transferred_at_block_height: None, + created_at_core_block_height: None, + updated_at_core_block_height: None, + transferred_at_core_block_height: None, + creator_id: None, + } + .into(); + + ConsensusValidationResult::new_with_data(ShieldedWithdrawalTransitionActionV0 { + amount: value.unshielding_amount, + notes, + anchor, + core_fee_per_byte: value.core_fee_per_byte, + pooling: value.pooling, + output_script: value.output_script.clone(), + fee_amount: 0, // TODO: fee calculation for shielded withdrawals + current_total_balance, + prepared_withdrawal_document: withdrawal_document, + }) + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/mod.rs new file mode 100644 index 00000000000..c84a9e065f8 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/mod.rs @@ -0,0 +1,50 @@ +/// transformer +pub mod transformer; +/// v0 +pub mod v0; + +use crate::state_transition_action::shielded::unshield::v0::UnshieldTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use derive_more::From; +use dpp::address_funds::PlatformAddress; +use dpp::fee::Credits; + +/// Unshield transition action +#[derive(Debug, Clone, From)] +pub enum UnshieldTransitionAction { + /// v0 + V0(UnshieldTransitionActionV0), +} + +impl UnshieldTransitionAction { + /// Get output address + pub fn output_address(&self) -> &PlatformAddress { + match self { + UnshieldTransitionAction::V0(transition) => &transition.output_address, + } + } + /// Get amount + pub fn amount(&self) -> Credits { + match self { + UnshieldTransitionAction::V0(transition) => transition.amount, + } + } + /// Get notes + pub fn notes(&self) -> &[ShieldedActionNote] { + match self { + UnshieldTransitionAction::V0(transition) => &transition.notes, + } + } + /// Get anchor + pub fn anchor(&self) -> &[u8; 32] { + match self { + UnshieldTransitionAction::V0(transition) => &transition.anchor, + } + } + /// Fee amount (value_balance - amount), paid to proposers + pub fn fee_amount(&self) -> Credits { + match self { + UnshieldTransitionAction::V0(transition) => transition.fee_amount, + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs new file mode 100644 index 00000000000..5984e3216d1 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs @@ -0,0 +1,28 @@ +use crate::state_transition_action::shielded::unshield::v0::UnshieldTransitionActionV0; +use crate::state_transition_action::shielded::unshield::UnshieldTransitionAction; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::unshield_transition::UnshieldTransition; + +impl UnshieldTransitionAction { + /// Transforms the state transition into an action + pub fn try_from_transition( + value: &UnshieldTransition, + notes: Vec, + anchor: [u8; 32], + current_total_balance: Credits, + ) -> ConsensusValidationResult { + match value { + UnshieldTransition::V0(v0) => { + let result = UnshieldTransitionActionV0::try_from_transition( + v0, + notes, + anchor, + current_total_balance, + ); + result.map(|action| action.into()) + } + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/mod.rs new file mode 100644 index 00000000000..bb9b113dd74 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/mod.rs @@ -0,0 +1,22 @@ +mod transformer; + +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::address_funds::PlatformAddress; +use dpp::fee::Credits; + +/// Unshield transition action v0 +#[derive(Debug, Clone)] +pub struct UnshieldTransitionActionV0 { + /// The address receiving unshielded funds + pub output_address: PlatformAddress, + /// Amount being unshielded + pub amount: Credits, + /// Notes from the orchard bundle actions + pub notes: Vec, + /// The anchor used for verification + pub anchor: [u8; 32], + /// Fee amount (value_balance - amount), paid to proposers + pub fee_amount: Credits, + /// Current total balance of the shielded pool + pub current_total_balance: Credits, +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs new file mode 100644 index 00000000000..09fc0c848d5 --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs @@ -0,0 +1,24 @@ +use crate::state_transition_action::shielded::unshield::v0::UnshieldTransitionActionV0; +use crate::state_transition_action::shielded::ShieldedActionNote; +use dpp::fee::Credits; +use dpp::prelude::ConsensusValidationResult; +use dpp::state_transition::state_transitions::shielded::unshield_transition::v0::UnshieldTransitionV0; + +impl UnshieldTransitionActionV0 { + /// Transforms the unshield transition into an action + pub fn try_from_transition( + value: &UnshieldTransitionV0, + notes: Vec, + anchor: [u8; 32], + current_total_balance: Credits, + ) -> ConsensusValidationResult { + ConsensusValidationResult::new_with_data(UnshieldTransitionActionV0 { + output_address: value.output_address.clone(), + amount: value.unshielding_amount, + notes, + anchor, + fee_amount: 0, // TODO: fee calculation for unshield + current_total_balance, + }) + } +} diff --git a/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/transformer.rs b/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/transformer.rs index cb4dc11153e..a21fd9ceed6 100644 --- a/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/transformer.rs @@ -4,12 +4,35 @@ use crate::state_transition_action::identity::identity_topup_from_addresses::Ide use crate::state_transition_action::system::bump_address_input_nonces_action::{ BumpAddressInputNoncesAction, BumpAddressInputNoncesActionV0, }; +use dpp::address_funds::fee_strategy::AddressFundsFeeStrategy; +use dpp::address_funds::PlatformAddress; use dpp::fee::Credits; +use dpp::prelude::{AddressNonce, UserFeeIncrease}; use dpp::state_transition::state_transitions::address_funds::address_funds_transfer_transition::AddressFundsTransferTransition; use dpp::state_transition::state_transitions::identity::identity_create_from_addresses_transition::IdentityCreateFromAddressesTransition; use dpp::state_transition::state_transitions::identity::identity_topup_from_addresses_transition::IdentityTopUpFromAddressesTransition; +use std::collections::BTreeMap; impl BumpAddressInputNoncesAction { + // Generic constructor from pre-computed inputs + + /// from inputs with remaining balance (used by shield transitions where + /// the remaining balances are computed by the processing pipeline) + pub fn from_inputs_with_remaining_balance( + inputs_with_remaining_balance: &BTreeMap, + fee_strategy: &AddressFundsFeeStrategy, + penalty_credits: Credits, + user_fee_increase: UserFeeIncrease, + ) -> Self { + BumpAddressInputNoncesActionV0::from_inputs_with_remaining_balance( + inputs_with_remaining_balance, + fee_strategy, + penalty_credits, + user_fee_increase, + ) + .into() + } + // IdentityCreateFromAddresses transformers /// from IdentityCreateFromAddresses transition diff --git a/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/v0/transformer.rs index 1c2aa1d91ff..17b41e0a4c2 100644 --- a/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/system/bump_address_input_nonces_action/v0/transformer.rs @@ -77,6 +77,24 @@ impl BumpAddressInputNoncesActionV0 { } } + // Generic constructor from pre-computed inputs + + /// from inputs with remaining balance (used by shield transitions where + /// the remaining balances are computed by the processing pipeline) + pub fn from_inputs_with_remaining_balance( + inputs_with_remaining_balance: &BTreeMap, + fee_strategy: &AddressFundsFeeStrategy, + penalty_credits: Credits, + user_fee_increase: UserFeeIncrease, + ) -> Self { + Self::new_with_penalty( + inputs_with_remaining_balance, + fee_strategy, + penalty_credits, + user_fee_increase, + ) + } + // IdentityCreateFromAddresses transformers /// from borrowed IdentityCreateFromAddresses transition diff --git a/packages/rs-drive/src/state_transition_action/system/mod.rs b/packages/rs-drive/src/state_transition_action/system/mod.rs index debd41e2d57..4bd2d5bde80 100644 --- a/packages/rs-drive/src/state_transition_action/system/mod.rs +++ b/packages/rs-drive/src/state_transition_action/system/mod.rs @@ -9,3 +9,6 @@ pub mod partially_use_asset_lock_action; /// bump address input nonce action pub mod bump_address_input_nonces_action; + +/// penalize shielded pool action +pub mod penalize_shielded_pool_action; diff --git a/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/mod.rs b/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/mod.rs index bfd70ca3d27..cccd20ef94a 100644 --- a/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/mod.rs +++ b/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/mod.rs @@ -1,4 +1,3 @@ -use crate::state_transition_action::system::partially_use_asset_lock_action::v0::PartiallyUseAssetLockActionV0; use derive_more::From; use dpp::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; use dpp::fee::Credits; @@ -9,7 +8,7 @@ use std::collections::BTreeMap; mod transformer; mod v0; -pub use v0::PartiallyUseAssetLockActionAccessorsV0; +pub use v0::{PartiallyUseAssetLockActionAccessorsV0, PartiallyUseAssetLockActionV0}; #[derive(Debug, Clone, From)] /// An action expressing that an asset lock should be partially used diff --git a/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/v0/mod.rs index 0179e9179b0..91f31c70432 100644 --- a/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/system/partially_use_asset_lock_action/v0/mod.rs @@ -5,6 +5,7 @@ use dpp::prelude::{AddressNonce, UserFeeIncrease}; use std::collections::BTreeMap; mod transformer; +/// Partially use asset lock action v0 #[derive(Default, Debug, Clone)] pub struct PartiallyUseAssetLockActionV0 { /// asset lock outpoint diff --git a/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/mod.rs b/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/mod.rs new file mode 100644 index 00000000000..a44aa21d4df --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/mod.rs @@ -0,0 +1,40 @@ +/// Penalize the shielded pool when proof verification fails for pool-spending transitions +pub mod v0; + +use derive_more::From; +use v0::PenalizeShieldedPoolActionV0; + +/// Action to deduct a penalty from the shielded pool and record nullifiers as spent +#[derive(Debug, Clone, From)] +pub enum PenalizeShieldedPoolAction { + /// V0 + V0(PenalizeShieldedPoolActionV0), +} + +/// Accessors for PenalizeShieldedPoolAction +pub trait PenalizeShieldedPoolActionAccessorsV0 { + /// The penalty amount to deduct from the pool + fn penalty_amount(&self) -> u64; + /// The nullifiers to record as spent (prevents replay) + fn nullifiers(&self) -> &[[u8; 32]]; + /// Current total pool balance + fn current_total_balance(&self) -> u64; +} + +impl PenalizeShieldedPoolActionAccessorsV0 for PenalizeShieldedPoolAction { + fn penalty_amount(&self) -> u64 { + match self { + PenalizeShieldedPoolAction::V0(v0) => v0.penalty_amount, + } + } + fn nullifiers(&self) -> &[[u8; 32]] { + match self { + PenalizeShieldedPoolAction::V0(v0) => &v0.nullifiers, + } + } + fn current_total_balance(&self) -> u64 { + match self { + PenalizeShieldedPoolAction::V0(v0) => v0.current_total_balance, + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/v0/mod.rs b/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/v0/mod.rs new file mode 100644 index 00000000000..7497050c7be --- /dev/null +++ b/packages/rs-drive/src/state_transition_action/system/penalize_shielded_pool_action/v0/mod.rs @@ -0,0 +1,12 @@ +use dpp::fee::Credits; + +/// V0 implementation of penalize shielded pool action +#[derive(Debug, Clone)] +pub struct PenalizeShieldedPoolActionV0 { + /// The penalty amount to deduct from the pool + pub penalty_amount: Credits, + /// Nullifiers to record as spent (prevents exact replay of the same invalid proof) + pub nullifiers: Vec<[u8; 32]>, + /// The current total balance of the pool before penalty + pub current_total_balance: Credits, +} diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs b/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs index 0f707af00b8..f5a1ada1a2d 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs @@ -84,7 +84,7 @@ impl Drive { // Execute drive operation callbacks after updating state for task in finalize_tasks { - task.execute(self, platform_version); + task.execute(self, transaction, platform_version)?; } Drive::calculate_fee( diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs index b45d7c154d6..270b34bfb46 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs @@ -2,7 +2,9 @@ use crate::drive::Drive; use crate::error::Error; use dpp::prelude::Identifier; use dpp::version::PlatformVersion; +use grovedb::TransactionArg; +#[derive(Clone, Debug)] pub enum DriveOperationFinalizeTask { RemoveDataContractFromCache { contract_id: Identifier }, } @@ -17,10 +19,16 @@ pub trait DriveOperationFinalizationTasks { } impl DriveOperationFinalizeTask { - pub fn execute(self, drive: &Drive, _platform_version: &PlatformVersion) { + pub fn execute( + self, + drive: &Drive, + _transaction: TransactionArg, + _platform_version: &PlatformVersion, + ) -> Result<(), Error> { match self { DriveOperationFinalizeTask::RemoveDataContractFromCache { contract_id } => { drive.cache.data_contracts.remove(contract_id.to_buffer()); + Ok(()) } } } diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/mod.rs b/packages/rs-drive/src/util/batch/drive_op_batch/mod.rs index e3703034296..ecaed96055b 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/mod.rs @@ -2,10 +2,11 @@ mod address_funds; mod contract; mod document; mod drive_methods; -mod finalize_task; +pub(crate) mod finalize_task; mod group; mod identity; mod prefunded_specialized_balance; +mod shielded; mod system; mod token; mod withdrawals; @@ -26,6 +27,7 @@ pub use document::UpdateOperationInfo; pub use group::GroupOperationType; pub use identity::IdentityOperationType; pub use prefunded_specialized_balance::PrefundedSpecializedBalanceOperationType; +pub use shielded::ShieldedPoolOperationType; pub use system::SystemOperationType; pub use token::TokenOperationType; pub use withdrawals::WithdrawalOperationType; @@ -90,10 +92,14 @@ pub enum DriveOperation<'a> { GroupOperation(GroupOperationType), /// An address funds operation AddressFundsOperation(AddressFundsOperationType), + /// A shielded pool operation + ShieldedPoolOperation(ShieldedPoolOperationType), /// A single low level groveDB operation GroveDBOperation(QualifiedGroveDbOp), /// Multiple low level groveDB operations GroveDBOpBatch(GroveDbOpBatch), + /// An operation that only produces finalization tasks (no low-level ops) + FinalizeOperation(DriveOperationFinalizeTask), } impl DriveLowLevelOperationConverter for DriveOperation<'_> { @@ -159,6 +165,15 @@ impl DriveLowLevelOperationConverter for DriveOperation<'_> { transaction, platform_version, ), + DriveOperation::ShieldedPoolOperation(shielded_pool_operation_type) => { + shielded_pool_operation_type.into_low_level_drive_operations( + drive, + estimated_costs_only_with_layer_info, + block_info, + transaction, + platform_version, + ) + } DriveOperation::GroveDBOperation(op) => Ok(vec![GroveOperation(op)]), DriveOperation::GroveDBOpBatch(operations) => Ok(operations .operations @@ -190,6 +205,7 @@ impl DriveLowLevelOperationConverter for DriveOperation<'_> { platform_version, ) } + DriveOperation::FinalizeOperation(_) => Ok(vec![]), } } } @@ -223,6 +239,7 @@ impl DriveOperation<'_> { ) -> Result>, Error> { match self { DriveOperation::DataContractOperation(o) => o.finalization_tasks(platform_version), + DriveOperation::FinalizeOperation(task) => Ok(Some(vec![task.clone()])), _ => Ok(None), } } diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs new file mode 100644 index 00000000000..647ee0e6ba6 --- /dev/null +++ b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs @@ -0,0 +1,119 @@ +use crate::drive::shielded::paths::{ + shielded_credit_pool_notes_path_vec, shielded_credit_pool_nullifiers_path_vec, + shielded_credit_pool_path_vec, SHIELDED_TOTAL_BALANCE_KEY, +}; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::GroveOperation; +use crate::util::batch::drive_op_batch::DriveLowLevelOperationConverter; +use dpp::block::block_info::BlockInfo; +use dpp::version::PlatformVersion; +use grovedb::batch::{KeyInfoPath, QualifiedGroveDbOp}; +use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +/// Operations on the Shielded Pool +#[derive(Clone, Debug)] +pub enum ShieldedPoolOperationType { + /// Insert a note into the CommitmentTree (appends cmx to frontier + stores cmx||rho||encrypted_note as item) + InsertNote { + /// The 32-byte nullifier (rho) of the spent note in this action, stored alongside + /// the ciphertext so light clients can derive Rho for trial decryption + nullifier: [u8; 32], + /// The 32-byte note commitment (cmx) + cmx: [u8; 32], + /// The encrypted note payload (216 bytes) + encrypted_note: Vec, + }, + /// Insert a nullifier to prevent double-spend + InsertNullifier { + /// The 32-byte nullifier + nullifier: [u8; 32], + }, + /// Update the shielded pool total balance + UpdateTotalBalance { + /// The new total balance value + new_total_balance: u64, + }, + /// Store nullifiers to recent block storage for catch-up sync RPCs. + /// Block height and time are taken from BlockInfo during low-level conversion. + StoreNullifiersForBlock { + /// The nullifiers to store for this block + nullifiers: Vec<[u8; 32]>, + }, +} + +impl DriveLowLevelOperationConverter for ShieldedPoolOperationType { + fn into_low_level_drive_operations( + self, + drive: &Drive, + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + block_info: &BlockInfo, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + if let Some(ref mut estimated_costs) = estimated_costs_only_with_layer_info { + Drive::add_estimation_costs_for_shielded_pool_operations(estimated_costs); + } + + match self { + ShieldedPoolOperationType::InsertNote { + nullifier, + cmx, + encrypted_note, + } => { + let notes_path = shielded_credit_pool_notes_path_vec(); + Ok(vec![GroveOperation( + QualifiedGroveDbOp::commitment_tree_insert_op( + notes_path, + cmx, + nullifier, + encrypted_note, + ), + )]) + } + ShieldedPoolOperationType::InsertNullifier { nullifier } => { + let nullifiers_path = shielded_credit_pool_nullifiers_path_vec(); + Ok(vec![GroveOperation(QualifiedGroveDbOp::insert_only_op( + nullifiers_path, + nullifier.to_vec(), + Element::new_item(vec![]), + ))]) + } + ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance } => { + let pool_path = shielded_credit_pool_path_vec(); + let balance_i64 = i64::try_from(new_total_balance).map_err(|_| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance exceeds i64::MAX".to_string(), + )) + })?; + Ok(vec![GroveOperation( + QualifiedGroveDbOp::insert_or_replace_op( + pool_path, + vec![SHIELDED_TOTAL_BALANCE_KEY], + Element::new_sum_item(balance_i64), + ), + )]) + } + ShieldedPoolOperationType::StoreNullifiersForBlock { nullifiers } => { + // Store nullifiers to recent block storage for catch-up sync RPCs. + // This is a side-effect operation — it doesn't produce low-level grove ops + // but instead calls store_nullifiers_for_block directly. + if !nullifiers.is_empty() { + drive.store_nullifiers_for_block( + &nullifiers, + block_info.height, + block_info.time_ms, + transaction, + platform_version, + )?; + } + Ok(vec![]) + } + } + } +} diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index 52aec081552..be36450e88d 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -335,7 +335,7 @@ impl fmt::Display for GroveDbOpBatch { let (key_string, _) = if let Some(ref key) = op.key { readable_key_info(known_path, key) } else { - ("None".to_string(), None) + ("(none)".to_string(), None) }; writeln!(f, "{{")?; writeln!(f, " Path: {}", path_string)?; diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs new file mode 100644 index 00000000000..588876aeb4c --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs @@ -0,0 +1,44 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::version::drive_versions::DriveVersion; +use grovedb::{Element, TransactionArg}; + +impl Drive { + /// Inserts multiple items into a count tree with auto-incremented u64 big-endian keys. + /// + /// Reads the current count from the count tree, then generates sequential insert + /// operations starting from that count. + pub fn batch_insert_auto_incremented_items_in_count_tree( + &self, + parent_path: Vec>, + count_tree_key: &[u8], + items: Vec, + transaction: TransactionArg, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result<(), Error> { + match drive_version + .grove_methods + .batch + .batch_insert_auto_incremented_items_in_count_tree + { + 0 => self.batch_insert_auto_incremented_items_in_count_tree_v0( + parent_path, + count_tree_key, + items, + transaction, + drive_operations, + drive_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "batch_insert_auto_incremented_items_in_count_tree".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs new file mode 100644 index 00000000000..80d80ed3e44 --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs @@ -0,0 +1,63 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::grove_operations::DirectQueryType; +use dpp::version::drive_versions::DriveVersion; +use grovedb::{Element, TransactionArg}; + +impl Drive { + /// Inserts multiple items into a count tree with auto-incremented u64 big-endian keys. + /// + /// Reads the current count from the count tree, then generates sequential insert + /// operations starting from that count. + /// + /// # Parameters + /// * `parent_path` - Path to the parent tree containing the count tree + /// * `count_tree_key` - Key of the count tree under the parent path + /// * `items` - Items (Elements) to insert with auto-incremented keys + /// * `transaction` - GroveDB transaction + /// * `drive_operations` - Accumulator for low-level drive operations + /// * `drive_version` - Drive version for method dispatch + pub(crate) fn batch_insert_auto_incremented_items_in_count_tree_v0( + &self, + parent_path: Vec>, + count_tree_key: &[u8], + items: Vec, + transaction: TransactionArg, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result<(), Error> { + if items.is_empty() { + return Ok(()); + } + + // Read the current count from the count tree element + let current_count = self + .grove_get_raw_optional( + parent_path.as_slice().into(), + count_tree_key, + DirectQueryType::StatefulDirectQuery, + transaction, + drive_operations, + drive_version, + )? + .map(|element| element.count_value_or_default()) + .unwrap_or(0); + + // Build the full path to the count tree (parent_path + count_tree_key) + let mut insert_path = parent_path; + insert_path.push(count_tree_key.to_vec()); + + // Insert each item with a sequential key + for (i, element) in items.into_iter().enumerate() { + let index = current_count + i as u64; + drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element( + insert_path.clone(), + index.to_be_bytes().to_vec(), + element, + )); + } + + Ok(()) + } +} diff --git a/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/mod.rs new file mode 100644 index 00000000000..ff402130db6 --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/mod.rs @@ -0,0 +1,54 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; + +use dpp::version::drive_versions::DriveVersion; + +use grovedb::TransactionArg; +use grovedb_path::SubtreePath; + +impl Drive { + /// Returns the total number of items in a CommitmentTree. + /// The operation's cost is then added to `drive_operations` for later processing. + /// + /// # Parameters + /// * `path`: The path to the CommitmentTree's parent. + /// * `key`: The key of the CommitmentTree element. + /// * `transaction`: The groveDB transaction associated with this operation. + /// * `drive_operations`: A vector to collect the costs of operations for later computation. + /// * `drive_version`: The drive version to select the correct function version to run. + /// + /// # Returns + /// * `Ok(u64)` — the total number of items in the CommitmentTree. + /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions. + pub fn grove_commitment_tree_count>( + &self, + path: SubtreePath<'_, B>, + key: &[u8], + transaction: TransactionArg, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result { + match drive_version + .grove_methods + .basic + .grove_commitment_tree_count + { + 0 => self.grove_commitment_tree_count_v0( + path, + key, + transaction, + drive_operations, + drive_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "grove_commitment_tree_count".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/v0/mod.rs new file mode 100644 index 00000000000..bef6d837192 --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/grove_commitment_tree_count/v0/mod.rs @@ -0,0 +1,27 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::CalculatedCostOperation; +use grovedb::TransactionArg; +use grovedb_costs::CostContext; +use grovedb_path::SubtreePath; +use platform_version::version::drive_versions::DriveVersion; + +impl Drive { + /// Returns the total number of items in a CommitmentTree. + /// Pushes the cost to `drive_operations` and returns the count. + pub(super) fn grove_commitment_tree_count_v0>( + &self, + path: SubtreePath<'_, B>, + key: &[u8], + transaction: TransactionArg, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result { + let CostContext { value, cost } = + self.grove + .commitment_tree_count(path, key, transaction, &drive_version.grove_version); + drive_operations.push(CalculatedCostOperation(cost)); + value.map_err(Error::from) + } +} diff --git a/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/mod.rs new file mode 100644 index 00000000000..6679f518813 --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/mod.rs @@ -0,0 +1,46 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; + +use dpp::version::drive_versions::DriveVersion; + +use grovedb::PathQuery; + +impl Drive { + /// Generates a V1 proof for the specified path query in groveDB. + /// V1 proofs support BulkAppendTree/CommitmentTree subqueries. + /// The operation's cost is then added to `drive_operations` for later processing. + /// + /// # Parameters + /// * `path_query`: The groveDB path query to generate a V1 proof for. + /// * `drive_operations`: A vector to collect the costs of operations for later computation. + /// * `drive_version`: The drive version to select the correct function version to run. + /// + /// # Returns + /// * `Ok(Vec)` if the operation was successful, returning the serialized proof bytes. + /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions. + pub fn grove_get_proved_path_query_v1( + &self, + path_query: &PathQuery, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result, Error> { + match drive_version + .grove_methods + .basic + .grove_get_proved_path_query_v1 + { + 0 => { + self.grove_get_proved_path_query_v1_v0(path_query, drive_operations, drive_version) + } + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "grove_get_proved_path_query_v1".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs new file mode 100644 index 00000000000..d7f8a9d0f85 --- /dev/null +++ b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs @@ -0,0 +1,25 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::CalculatedCostOperation; +use grovedb::PathQuery; +use grovedb_costs::CostContext; +use platform_version::version::drive_versions::DriveVersion; + +impl Drive { + /// Gets the return value and the cost of a groveDB V1 proved path query. + /// V1 proofs support BulkAppendTree and CommitmentTree subqueries. + /// Pushes the cost to `drive_operations` and returns the proof bytes. + pub(super) fn grove_get_proved_path_query_v1_v0( + &self, + path_query: &PathQuery, + drive_operations: &mut Vec, + drive_version: &DriveVersion, + ) -> Result, Error> { + let CostContext { value, cost } = + self.grove + .prove_query_v1(path_query, None, &drive_version.grove_version); + drive_operations.push(CalculatedCostOperation(cost)); + value.map_err(Error::from) + } +} diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs index 6abf5bdef4b..2494d17c56c 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs @@ -28,7 +28,12 @@ impl Drive { TreeType::CountSumTree => Element::empty_count_sum_tree(), TreeType::ProvableCountTree => Element::empty_provable_count_tree(), TreeType::ProvableCountSumTree => Element::empty_provable_count_sum_tree(), - _ => todo!("new tree types not yet implemented"), + TreeType::CommitmentTree(chunk_power) => Element::empty_commitment_tree(chunk_power), + TreeType::MmrTree => Element::empty_mmr_tree(), + TreeType::BulkAppendTree(chunk_power) => Element::empty_bulk_append_tree(chunk_power), + TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => { + Element::empty_dense_tree(chunk_power) + } }; let cost_context = self.grove.insert( path, diff --git a/packages/rs-drive/src/util/grove_operations/mod.rs b/packages/rs-drive/src/util/grove_operations/mod.rs index 8d8cd7f3c8a..eced6fa04d6 100644 --- a/packages/rs-drive/src/util/grove_operations/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/mod.rs @@ -48,6 +48,12 @@ pub mod grove_get_raw_path_query; /// Proved path query in grove pub mod grove_get_proved_path_query; +/// V1 proved path query in grove (supports BulkAppendTree/CommitmentTree) +pub mod grove_get_proved_path_query_v1; + +/// Get total count from a CommitmentTree +pub mod grove_commitment_tree_count; + /// Proved branch chunk query in grove pub mod grove_get_proved_branch_chunk_query; @@ -140,6 +146,8 @@ pub mod batch_insert_sum_item_if_not_exists; /// Moved items that are found in a path query to a new path. pub mod batch_move_items_in_path_query; +/// Batch inserts items into a count tree with auto-incremented keys +pub mod batch_insert_auto_incremented_items_in_count_tree; /// Batch inserts item with sum item if not already existing pub mod batch_insert_item_with_sum_item_if_not_exists; /// Keeps the item, but inserts or adds to the sum item if it already exists diff --git a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs index fef26e40c80..e01870da58c 100644 --- a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs @@ -8,7 +8,7 @@ use dpp::address_funds::PlatformAddress; /// The subtree key for compacted address balances storage as u8 const COMPACTED_ADDRESS_BALANCES_KEY_U8: u8 = b'c'; use dpp::balances::credits::BlockAwareCreditOperation; -use grovedb::operations::proof::GroveDBProof; +use grovedb::operations::proof::{GroveDBProof, ProofBytes}; use grovedb::{ GroveDb, MerkProofDecoder, MerkProofNode, MerkProofOp, PathQuery, Query, SizedQuery, }; @@ -75,25 +75,40 @@ impl Drive { // Navigate to the compacted address balances layer // Path: SavedBlockTransactions ('$' = 0x24) -> CompactedAddressBalances ('c' = 0x63) - let root_layer = match &grovedb_proof { - GroveDBProof::V0(v0) => &v0.root_layer, - _ => todo!("GroveDBProof::V1 not yet implemented"), - }; - let saved_block_key = vec![RootTree::SavedBlockTransactions as u8]; let compacted_key = vec![COMPACTED_ADDRESS_BALANCES_KEY_U8]; - let compacted_layer = root_layer - .lower_layers - .get(&saved_block_key) - .and_then(|layer| layer.lower_layers.get(&compacted_key)); - // Extract KV entries from the compacted layer's merk proof to find - // if there's a containing range for start_block_height - let kv_entries = compacted_layer - .map(|layer| extract_kv_entries_from_merk_proof(&layer.merk_proof)) - .transpose()? - .unwrap_or_default(); + // if there's a containing range for start_block_height. + // V0 and V1 proofs have different layer types (MerkOnlyLayerProof vs LayerProof), + // so we handle them separately. + let kv_entries = match &grovedb_proof { + GroveDBProof::V0(v0) => { + let compacted_layer = v0 + .root_layer + .lower_layers + .get(&saved_block_key) + .and_then(|layer| layer.lower_layers.get(&compacted_key)); + compacted_layer + .map(|layer| extract_kv_entries_from_merk_proof(&layer.merk_proof)) + .transpose()? + .unwrap_or_default() + } + GroveDBProof::V1(v1) => { + let compacted_layer = v1 + .root_layer + .lower_layers + .get(&saved_block_key) + .and_then(|layer| layer.lower_layers.get(&compacted_key)); + compacted_layer + .map(|layer| match &layer.merk_proof { + ProofBytes::Merk(bytes) => extract_kv_entries_from_merk_proof(bytes), + _ => Ok(vec![]), + }) + .transpose()? + .unwrap_or_default() + } + }; // Look for a KV entry where the range contains start_block_height // Keys are 16 bytes: (start_block, end_block), both big-endian diff --git a/packages/rs-drive/src/verify/mod.rs b/packages/rs-drive/src/verify/mod.rs index 10bbd254b1b..5179fcef77c 100644 --- a/packages/rs-drive/src/verify/mod.rs +++ b/packages/rs-drive/src/verify/mod.rs @@ -16,6 +16,8 @@ pub mod system; pub mod address_funds; /// Group proof verification module pub mod group; +/// Shielded pool proof verification module +pub mod shielded; /// Verifies that a state transition contents exist in the proof pub mod state_transition; /// Token proof verification module diff --git a/packages/rs-drive/src/verify/shielded/mod.rs b/packages/rs-drive/src/verify/shielded/mod.rs new file mode 100644 index 00000000000..1f9c6b31cad --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/mod.rs @@ -0,0 +1,16 @@ +/// Module for verifying compacted nullifier changes +pub mod verify_compacted_nullifier_changes; +/// Module for verifying nullifiers branch chunk queries +pub mod verify_nullifiers_branch_query; +/// Module for verifying nullifiers trunk chunk queries +pub mod verify_nullifiers_trunk_query; +/// Module for verifying recent nullifier changes +pub mod verify_recent_nullifier_changes; +/// Module for verifying shielded anchors +pub mod verify_shielded_anchors; +/// Module for verifying shielded encrypted notes +pub mod verify_shielded_encrypted_notes; +/// Module for verifying shielded nullifiers +pub mod verify_shielded_nullifiers; +/// Module for verifying shielded pool state (total balance) +pub mod verify_shielded_pool_state; diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs new file mode 100644 index 00000000000..01f5baa98a7 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs @@ -0,0 +1,56 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +/// Result type for verified compacted nullifier changes +/// Each entry is (start_block, end_block, nullifiers) +pub type VerifiedCompactedNullifierChanges = Vec<(u64, u64, Vec<[u8; 32]>)>; + +impl Drive { + /// Verifies the proof of compacted nullifier changes starting from a given block height. + /// + /// This method validates and extracts compacted nullifier changes from the provided proof. + /// Compacted entries represent concatenated data from multiple blocks. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the compacted nullifier changes. + /// - `start_block_height`: The block height to start verifying from. + /// - `limit`: Optional maximum number of compacted entries to verify. + /// - `platform_version`: A reference to the platform version. + /// + /// # Returns + /// - `Ok((RootHash, VerifiedCompactedNullifierChanges))`: On success, returns: + /// - `RootHash`: The root hash of the Merkle tree. + /// - `VerifiedCompactedNullifierChanges`: Vector of (start_block, end_block, nullifiers) tuples. + /// - `Err(Error)`: If verification fails. + pub fn verify_compacted_nullifier_changes( + proof: &[u8], + start_block_height: u64, + limit: Option, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, VerifiedCompactedNullifierChanges), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_compacted_nullifier_changes + { + 0 => Self::verify_compacted_nullifier_changes_v0( + proof, + start_block_height, + limit, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_compacted_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs new file mode 100644 index 00000000000..371c52588b9 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -0,0 +1,207 @@ +use crate::drive::Drive; +use crate::drive::RootTree; +use crate::error::proof::ProofError; +use crate::error::Error; +use crate::verify::RootHash; + +/// The subtree key for compacted nullifiers storage as u8 +const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; +use grovedb::operations::proof::{GroveDBProof, ProofBytes}; +use grovedb::{ + GroveDb, MerkProofDecoder, MerkProofNode, MerkProofOp, PathQuery, Query, SizedQuery, +}; +use platform_version::version::PlatformVersion; + +use super::VerifiedCompactedNullifierChanges; + +/// Extract KV entries from merk proof bytes using the proper decoder. +#[allow(clippy::type_complexity)] +fn extract_kv_entries_from_merk_proof(merk_proof: &[u8]) -> Result, Vec)>, Error> { + let mut entries = Vec::new(); + + let decoder = MerkProofDecoder::new(merk_proof); + + for op in decoder { + match op { + Ok(MerkProofOp::Push(MerkProofNode::KV(key, value))) + | Ok(MerkProofOp::PushInverted(MerkProofNode::KV(key, value))) => { + entries.push((key, value)); + } + Err(e) => { + tracing::error!(%e, "merk proof decode error"); + return Err(Error::Proof(ProofError::CorruptedProof(format!( + "failed to decode merk proof op: {}", + e + )))); + } + _ => {} + } + } + + Ok(entries) +} + +impl Drive { + /// Verifies compacted nullifier changes proof. + /// + /// This verification works by: + /// 1. Decoding the GroveDBProof structure + /// 2. Navigating to the compacted nullifiers layer ('o') + /// 3. Extracting KV entries from the merk proof + /// 4. Filtering entries where the key range contains start_block_height + /// 5. Verifying the root hash using a subset query + pub(super) fn verify_compacted_nullifier_changes_v0( + proof: &[u8], + start_block_height: u64, + limit: Option, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, VerifiedCompactedNullifierChanges), Error> { + let bincode_config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + // Decode the GroveDBProof to navigate its structure + let grovedb_proof: GroveDBProof = bincode::decode_from_slice(proof, bincode_config) + .map(|(p, _)| p) + .map_err(|e| { + Error::Proof(ProofError::CorruptedProof(format!( + "cannot decode GroveDBProof: {}", + e + ))) + })?; + + // Navigate to the compacted nullifiers layer + // Path: SavedBlockTransactions ('$' = 0x24) -> CompactedNullifiers ('o' = 0x6f) + let saved_block_key = vec![RootTree::SavedBlockTransactions as u8]; + let compacted_key = vec![COMPACTED_NULLIFIERS_KEY_U8]; + + // Extract KV entries from the compacted layer's merk proof to find + // if there's a containing range for start_block_height. + // V0 and V1 proofs have different layer types (MerkOnlyLayerProof vs LayerProof), + // so we handle them separately. + let kv_entries = match &grovedb_proof { + GroveDBProof::V0(v0) => { + let compacted_layer = v0 + .root_layer + .lower_layers + .get(&saved_block_key) + .and_then(|layer| layer.lower_layers.get(&compacted_key)); + compacted_layer + .map(|layer| extract_kv_entries_from_merk_proof(&layer.merk_proof)) + .transpose()? + .unwrap_or_default() + } + GroveDBProof::V1(v1) => { + let compacted_layer = v1 + .root_layer + .lower_layers + .get(&saved_block_key) + .and_then(|layer| layer.lower_layers.get(&compacted_key)); + compacted_layer + .map(|layer| match &layer.merk_proof { + ProofBytes::Merk(bytes) => extract_kv_entries_from_merk_proof(bytes), + _ => Ok(vec![]), + }) + .transpose()? + .unwrap_or_default() + } + }; + + // Look for a KV entry where the range contains start_block_height + // Keys are 16 bytes: (start_block, end_block), both big-endian + let containing_key = kv_entries.iter().find_map(|(key, _)| { + if key.len() != 16 { + return None; + } + let range_start = u64::from_be_bytes( + key[0..8] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + let range_end = u64::from_be_bytes( + key[8..16] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + + // Check if this range contains start_block_height + if range_start <= start_block_height && start_block_height <= range_end { + Some(key.clone()) + } else { + None + } + }); + + // Determine the start_key for the query + // Use the containing range's key if found, otherwise (start_block_height, start_block_height) + let start_key = containing_key.unwrap_or_else(|| { + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + }); + + // Verify the proof and get results using subset query + let path = vec![ + vec![RootTree::SavedBlockTransactions as u8], + vec![COMPACTED_NULLIFIERS_KEY_U8], + ]; + + let mut query = Query::new(); + query.insert_range_from(start_key..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + let (root_hash, proved_key_values) = GroveDb::verify_subset_query( + proof, + &path_query, + &platform_version.drive.grove_version, + )?; + + // Process the verified results + let mut compacted_changes = Vec::new(); + + for (_path, key, maybe_element) in proved_key_values { + let Some(element) = maybe_element else { + continue; + }; + + if key.len() != 16 { + return Err(Error::Proof(ProofError::CorruptedProof( + "invalid compacted block key length, expected 16 bytes".to_string(), + ))); + } + + let range_start = u64::from_be_bytes( + key[0..8] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + let range_end = u64::from_be_bytes( + key[8..16] + .try_into() + .expect("slice is exactly 8 bytes from a 16-byte key"), + ); + + // Get the serialized data from the Item element + let grovedb::Element::Item(serialized_data, _) = element else { + return Err(Error::Proof(ProofError::CorruptedProof( + "expected item element for compacted nullifiers".to_string(), + ))); + }; + + // Deserialize the nullifier list + let (nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, bincode_config).map_err(|e| { + Error::Proof(ProofError::CorruptedProof(format!( + "cannot decode compacted nullifiers: {}", + e + ))) + })?; + + compacted_changes.push((range_start, range_end, nullifiers)); + } + + Ok((root_hash, compacted_changes)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/mod.rs b/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/mod.rs new file mode 100644 index 00000000000..dd2b56fc9fb --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/mod.rs @@ -0,0 +1,58 @@ +//! Module for verifying nullifiers branch chunk queries. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::GroveBranchQueryResult; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Verifies a branch chunk proof for the nullifiers tree of a shielded pool. + /// + /// # Arguments + /// - `proof`: A byte slice containing the serialized branch chunk proof. + /// - `pool_type`: The shielded pool type (0 = credit, 1 = main token, 2 = individual token). + /// - `pool_identifier`: Optional 32-byte identifier for individual token pools. + /// - `key`: The key that was navigated to in the tree. + /// - `depth`: The depth of the branch that was returned. + /// - `expected_root_hash`: The expected root hash from the parent trunk/branch proof. + /// - `platform_version`: A reference to the platform version. + /// + /// # Returns + /// - `Ok(GroveBranchQueryResult)`: The verified branch query result. + /// - `Err(Error)`: If verification fails. + pub fn verify_nullifiers_branch_query( + proof: &[u8], + pool_type: u32, + pool_identifier: Option<&[u8]>, + key: Vec, + depth: u8, + expected_root_hash: [u8; 32], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .drive + .methods + .verify + .shielded + .verify_nullifiers_branch_query + { + 0 => Self::verify_nullifiers_branch_query_v0( + proof, + pool_type, + pool_identifier, + key, + depth, + expected_root_hash, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_nullifiers_branch_query".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/v0/mod.rs new file mode 100644 index 00000000000..3bf881ce221 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_nullifiers_branch_query/v0/mod.rs @@ -0,0 +1,48 @@ +use crate::drive::shielded::paths::nullifiers_path_for_pool; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::{GroveBranchQueryResult, GroveDb, PathBranchChunkQuery}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_nullifiers_branch_query_v0( + proof: &[u8], + pool_type: u32, + pool_identifier: Option<&[u8]>, + key: Vec, + depth: u8, + expected_root_hash: [u8; 32], + platform_version: &PlatformVersion, + ) -> Result { + let min_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_min_depth; + let max_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_max_depth; + + if depth < min_depth || depth > max_depth { + return Err(Error::Drive(DriveError::InvalidInput(format!( + "depth {} is outside the allowed range [{}, {}]", + depth, min_depth, max_depth + )))); + } + + let path = nullifiers_path_for_pool(pool_type, pool_identifier)?; + let query = PathBranchChunkQuery { path, key, depth }; + + let result = GroveDb::verify_branch_chunk_proof( + proof, + &query, + expected_root_hash, + &platform_version.drive.grove_version, + )?; + + Ok(result) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/mod.rs b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/mod.rs new file mode 100644 index 00000000000..1e4aa59717e --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/mod.rs @@ -0,0 +1,50 @@ +//! Module for verifying nullifiers trunk chunk queries. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::GroveTrunkQueryResult; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Verifies a trunk chunk proof for the nullifiers tree of a shielded pool. + /// + /// # Arguments + /// - `proof`: A byte slice containing the serialized trunk chunk proof. + /// - `pool_type`: The shielded pool type (0 = credit, 1 = main token, 2 = individual token). + /// - `pool_identifier`: Optional 32-byte identifier for individual token pools. + /// - `platform_version`: A reference to the platform version. + /// + /// # Returns + /// - `Ok((RootHash, GroveTrunkQueryResult))`: The root hash and verified trunk result. + /// - `Err(Error)`: If verification fails. + pub fn verify_nullifiers_trunk_query( + proof: &[u8], + pool_type: u32, + pool_identifier: Option<&[u8]>, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, GroveTrunkQueryResult), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_nullifiers_trunk_query + { + 0 => Self::verify_nullifiers_trunk_query_v0( + proof, + pool_type, + pool_identifier, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_nullifiers_trunk_query".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs new file mode 100644 index 00000000000..fdae3be8508 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs @@ -0,0 +1,36 @@ +use crate::drive::shielded::paths::nullifiers_path_for_pool; +use crate::drive::Drive; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::{GroveDb, GroveTrunkQueryResult, PathTrunkChunkQuery}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_nullifiers_trunk_query_v0( + proof: &[u8], + pool_type: u32, + pool_identifier: Option<&[u8]>, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, GroveTrunkQueryResult), Error> { + let path = nullifiers_path_for_pool(pool_type, pool_identifier)?; + let max_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_max_depth; + + let query = PathTrunkChunkQuery { + path, + max_depth, + min_depth: None, + }; + + let (root_hash, result) = GroveDb::verify_trunk_chunk_proof( + proof, + &query, + &platform_version.drive.grove_version, + )?; + + Ok((root_hash, result)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs new file mode 100644 index 00000000000..72ca1383e87 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs @@ -0,0 +1,57 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +/// Result type for verified nullifier changes per block +pub type VerifiedNullifierChangesPerBlock = Vec<(u64, Vec<[u8; 32]>)>; + +impl Drive { + /// Verifies the proof of recent nullifier changes starting from a given block height. + /// + /// This method validates and extracts nullifier changes from the provided proof. + /// + /// # Arguments + /// - `proof`: A byte slice containing the cryptographic proof for the nullifier changes. + /// - `start_block_height`: The block height to start verifying from. + /// - `limit`: Optional maximum number of blocks to verify. + /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof. + /// - `platform_version`: A reference to the platform version. + /// + /// # Returns + /// - `Ok((RootHash, VerifiedNullifierChangesPerBlock))`: On success, returns: + /// - `RootHash`: The root hash of the Merkle tree. + /// - `VerifiedNullifierChangesPerBlock`: Vector of (block_height, nullifiers) tuples. + /// - `Err(Error)`: If verification fails. + pub fn verify_recent_nullifier_changes( + proof: &[u8], + start_block_height: u64, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, VerifiedNullifierChangesPerBlock), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_recent_nullifier_changes + { + 0 => Self::verify_recent_nullifier_changes_v0( + proof, + start_block_height, + limit, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_recent_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs new file mode 100644 index 00000000000..e8da02c8a2b --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -0,0 +1,83 @@ +use crate::drive::Drive; +use crate::drive::RootTree; +use crate::error::proof::ProofError; +use crate::error::Error; +use crate::verify::RootHash; + +/// The subtree key for nullifiers storage as u8 +const NULLIFIERS_KEY_U8: u8 = b'n'; +use grovedb::{Element, GroveDb, PathQuery, Query, SizedQuery}; +use platform_version::version::PlatformVersion; + +use super::VerifiedNullifierChangesPerBlock; + +impl Drive { + /// Verifies recent nullifier changes proof. + /// + /// Uses the same query as the prove function: a simple range query + /// starting from start_block_height. + pub(super) fn verify_recent_nullifier_changes_v0( + proof: &[u8], + start_block_height: u64, + limit: Option, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, VerifiedNullifierChangesPerBlock), Error> { + let path = vec![ + vec![RootTree::SavedBlockTransactions as u8], + vec![NULLIFIERS_KEY_U8], + ]; + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + // Create the same range query as the prove function + let mut query = Query::new(); + query.insert_range_from(start_block_height.to_be_bytes().to_vec()..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + + let mut nullifier_changes = Vec::new(); + + for (_path, key, maybe_element) in proved_key_values { + let Some(element) = maybe_element else { + continue; + }; + + // Parse block height from key (8 bytes, big-endian) + let height_bytes: [u8; 8] = key.try_into().map_err(|_| { + Error::Proof(ProofError::CorruptedProof( + "invalid block height key length".to_string(), + )) + })?; + let block_height = u64::from_be_bytes(height_bytes); + + // Get the serialized data from the ItemWithSumItem element + let Element::ItemWithSumItem(serialized_data, _, _) = element else { + return Err(Error::Proof(ProofError::CorruptedProof( + "expected item with sum item element for nullifiers".to_string(), + ))); + }; + + // Deserialize the nullifier list + let (nullifiers, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, config).map_err(|e| { + Error::Proof(ProofError::CorruptedProof(format!( + "cannot decode nullifiers: {}", + e + ))) + })?; + + nullifier_changes.push((block_height, nullifiers)); + } + + Ok((root_hash, nullifier_changes)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/mod.rs new file mode 100644 index 00000000000..bd6d2a53294 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/mod.rs @@ -0,0 +1,31 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies a proof for the valid shielded anchors. + pub fn verify_shielded_anchors( + proof: &[u8], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<[u8; 32]>), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_shielded_anchors + { + 0 => Self::verify_shielded_anchors_v0(proof, verify_subset_of_proof, platform_version), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_shielded_anchors".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/v0/mod.rs new file mode 100644 index 00000000000..a2f08d7c83e --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_anchors/v0/mod.rs @@ -0,0 +1,52 @@ +use crate::drive::shielded::paths::shielded_credit_pool_anchors_path_vec; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::{Element, GroveDb, PathQuery, Query, SizedQuery}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_shielded_anchors_v0( + proof: &[u8], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<[u8; 32]>), Error> { + let path_query = PathQuery { + path: shielded_credit_pool_anchors_path_vec(), + query: SizedQuery { + query: Query::new_range_full(), + limit: None, + offset: None, + }, + }; + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + + let mut anchors = Vec::with_capacity(proved_key_values.len()); + for (_, _key, maybe_element) in proved_key_values { + match maybe_element { + Some(Element::Item(value, _)) => { + let anchor: [u8; 32] = value.try_into().map_err(|_v: Vec| { + Error::Drive(DriveError::CorruptedElementType( + "anchor value is not 32 bytes", + )) + })?; + anchors.push(anchor); + } + Some(_) => { + return Err(Error::Drive(DriveError::CorruptedElementType( + "expected Item element for anchor, got different element type", + ))); + } + None => {} + } + } + + Ok((root_hash, anchors)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs new file mode 100644 index 00000000000..969f319b450 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs @@ -0,0 +1,41 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies a proof for shielded encrypted notes. + pub fn verify_shielded_encrypted_notes( + proof: &[u8], + start_index: u64, + count: u32, + max_elements: u32, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<(Vec, Vec, Vec)>), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_shielded_encrypted_notes + { + 0 => Self::verify_shielded_encrypted_notes_v0( + proof, + start_index, + count, + max_elements, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_shielded_encrypted_notes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs new file mode 100644 index 00000000000..39cb6eded49 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs @@ -0,0 +1,96 @@ +use crate::drive::shielded::paths::{shielded_credit_pool_path_vec, SHIELDED_NOTES_KEY}; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::{Element, GroveDb, PathQuery, Query, QueryItem, SizedQuery, SubqueryBranch}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_shielded_encrypted_notes_v0( + proof: &[u8], + start_index: u64, + count: u32, + max_elements: u32, + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<(Vec, Vec, Vec)>), Error> { + // start_index must be chunk-aligned (multiple of max_elements) + let chunk_size = max_elements as u64; + if chunk_size > 0 && start_index % chunk_size != 0 { + return Err(Error::Drive(DriveError::CorruptedElementType( + "start_index is not chunk-aligned; must be a multiple of max_elements", + ))); + } + + let effective = if count == 0 || count > max_elements { + max_elements + } else { + count + }; + let limit = effective.min(u16::MAX as u32) as u16; + + // PathQuery must match the server-side proof generation exactly: + // path = [AddressBalances, "s"], key = [SHIELDED_NOTES_KEY], + // subquery = range_inclusive(start..=end) + let end_index = start_index + limit as u64 - 1; + let mut inner_query = Query::new(); + inner_query.insert_range_inclusive( + start_index.to_be_bytes().to_vec()..=end_index.to_be_bytes().to_vec(), + ); + + let path_query = PathQuery { + path: shielded_credit_pool_path_vec(), + query: SizedQuery { + query: Query { + items: vec![QueryItem::Key(vec![SHIELDED_NOTES_KEY])], + default_subquery_branch: SubqueryBranch { + subquery_path: None, + subquery: Some(inner_query.into()), + }, + left_to_right: true, + conditional_subquery_branches: None, + add_parent_tree_on_subquery: false, + }, + limit: None, + offset: None, + }, + }; + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query(proof, &path_query, &platform_version.drive.grove_version)? + } else { + GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)? + }; + + let mut notes = Vec::with_capacity(proved_key_values.len()); + for (_, _key, maybe_element) in proved_key_values { + match maybe_element { + Some(Element::Item(value, _)) => { + // Value format: cmx (32) || nullifier (32) || encrypted_note (rest) + if value.len() <= 64 { + return Err(Error::Drive(DriveError::CorruptedElementType( + "encrypted note value too short: expected more than 64 bytes (cmx + nullifier + encrypted_note)", + ))); + } + // Return (cmx, nullifier, encrypted_note) + notes.push(( + value[..32].to_vec(), + value[32..64].to_vec(), + value[64..].to_vec(), + )); + } + Some(_) => { + return Err(Error::Drive(DriveError::CorruptedElementType( + "expected Item element for encrypted note, got different element type", + ))); + } + None => { + // Absent elements in proof results are normal (key doesn't exist) + } + } + } + + Ok((root_hash, notes)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs new file mode 100644 index 00000000000..b8c0795c26f --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs @@ -0,0 +1,37 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies a proof for shielded nullifier spend status. + pub fn verify_shielded_nullifiers( + proof: &[u8], + nullifiers: &[Vec], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<(Vec, bool)>), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_shielded_nullifiers + { + 0 => Self::verify_shielded_nullifiers_v0( + proof, + nullifiers, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_shielded_nullifiers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..4725b9ba841 --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs @@ -0,0 +1,52 @@ +use crate::drive::shielded::paths::shielded_credit_pool_nullifiers_path_vec; +use crate::drive::Drive; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::{GroveDb, PathQuery, Query, SizedQuery}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_shielded_nullifiers_v0( + proof: &[u8], + nullifiers: &[Vec], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Vec<(Vec, bool)>), Error> { + let mut query = Query::new(); + query.insert_keys(nullifiers.to_vec()); + + let path_query = PathQuery { + path: shielded_credit_pool_nullifiers_path_vec(), + query: SizedQuery { + query, + limit: Some(nullifiers.len() as u16), + offset: None, + }, + }; + + let (root_hash, proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + + // Map each proved entry: if element is Some, nullifier is spent; if None, not spent + let statuses = proved_key_values + .into_iter() + .map(|(_, key, maybe_element)| { + let is_spent = maybe_element.is_some(); + (key, is_spent) + }) + .collect(); + + Ok((root_hash, statuses)) + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/mod.rs new file mode 100644 index 00000000000..184ad457add --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/mod.rs @@ -0,0 +1,33 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::verify::RootHash; +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies a proof for the shielded pool total balance. + pub fn verify_shielded_pool_state( + proof: &[u8], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + match platform_version + .drive + .methods + .verify + .shielded + .verify_shielded_pool_state + { + 0 => { + Self::verify_shielded_pool_state_v0(proof, verify_subset_of_proof, platform_version) + } + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_shielded_pool_state".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/v0/mod.rs new file mode 100644 index 00000000000..756d27c4aee --- /dev/null +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_pool_state/v0/mod.rs @@ -0,0 +1,67 @@ +use crate::drive::shielded::paths::{shielded_credit_pool_path_vec, SHIELDED_TOTAL_BALANCE_KEY}; +use crate::drive::Drive; +use crate::error::proof::ProofError; +use crate::error::Error; +use crate::verify::RootHash; +use grovedb::{Element, GroveDb, PathQuery, Query, SizedQuery}; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_shielded_pool_state_v0( + proof: &[u8], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + let path_query = PathQuery { + path: shielded_credit_pool_path_vec(), + query: SizedQuery { + query: Query::new_single_key(vec![SHIELDED_TOTAL_BALANCE_KEY]), + limit: Some(1), + offset: None, + }, + }; + + let (root_hash, mut proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + + if proved_key_values.len() > 1 { + return Err(Error::Proof(ProofError::TooManyElements( + "expected at most 1 element for shielded pool state", + ))); + } + + let balance = if let Some(proved) = proved_key_values.pop() { + match proved.2 { + Some(Element::SumItem(value, _)) => { + if value < 0 { + return Err(Error::Proof(ProofError::CorruptedProof( + "shielded pool balance cannot be negative".to_string(), + ))); + } + Some(value as u64) + } + Some(_) => { + return Err(Error::Proof(ProofError::CorruptedProof( + "expected a sum item for shielded pool balance".to_string(), + ))); + } + None => None, + } + } else { + None + }; + + Ok((root_hash, balance)) + } +} diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index 4f238162684..eb4250d7c2a 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1115,12 +1115,231 @@ impl Drive { Ok((root_hash, VerifiedAddressInfos(balances))) } - StateTransition::Shield(_) - | StateTransition::ShieldedTransfer(_) - | StateTransition::Unshield(_) - | StateTransition::ShieldFromAssetLock(_) - | StateTransition::ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented in verify_state_transition_was_executed_with_proof") + StateTransition::Shield(st) => { + use dpp::state_transition::StateTransitionWitnessSigned; + let (root_hash, balances): ( + RootHash, + BTreeMap>, + ) = Drive::verify_addresses_infos( + proof, + st.inputs().keys(), + false, + platform_version, + )?; + Ok((root_hash, VerifiedAddressInfos(balances))) + } + StateTransition::Unshield(st) => { + use dpp::state_transition::proof_result::StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos; + use dpp::state_transition::unshield_transition::accessors::UnshieldTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + + let (root_hash_nf, statuses) = Drive::verify_shielded_nullifiers( + proof, + &nullifier_keys, + true, + platform_version, + )?; + + for (nf, is_spent) in &statuses { + if !is_spent { + return Err(Error::Proof(ProofError::IncorrectProof(format!( + "nullifier {} was not found as spent in the unshield proof", + hex::encode(nf) + )))); + } + } + + let (root_hash_addr, balances): ( + RootHash, + BTreeMap>, + ) = Drive::verify_addresses_infos( + proof, + std::iter::once(st.output_address()), + true, + platform_version, + )?; + + if root_hash_nf != root_hash_addr { + return Err(Error::Proof(ProofError::CorruptedProof( + "unshield proof root hashes do not match between nullifiers and address" + .to_string(), + ))); + } + + Ok(( + root_hash_nf, + VerifiedShieldedNullifiersWithAddressInfos(statuses, balances), + )) + } + StateTransition::ShieldedTransfer(st) => { + use dpp::state_transition::proof_result::StateTransitionProofResult::VerifiedShieldedNullifiers; + use dpp::state_transition::shielded_transfer_transition::accessors::ShieldedTransferTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + + let (root_hash, statuses) = Drive::verify_shielded_nullifiers( + proof, + &nullifier_keys, + false, + platform_version, + )?; + + // All nullifiers must be marked as spent + for (nf, is_spent) in &statuses { + if !is_spent { + return Err(Error::Proof(ProofError::IncorrectProof(format!( + "nullifier {} was not found as spent in the proof", + hex::encode(nf) + )))); + } + } + + Ok((root_hash, VerifiedShieldedNullifiers(statuses))) + } + StateTransition::ShieldedWithdrawal(st) => { + use dpp::data_contracts::withdrawals_contract; + use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; + use dpp::document::Document; + use dpp::state_transition::proof_result::StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument; + use dpp::state_transition::shielded_withdrawal_transition::accessors::ShieldedWithdrawalTransitionAccessorsV0; + + let nullifier_keys: Vec> = st.nullifiers(); + + let (root_hash_nf, statuses) = Drive::verify_shielded_nullifiers( + proof, + &nullifier_keys, + true, + platform_version, + )?; + + for (nf, is_spent) in &statuses { + if !is_spent { + return Err(Error::Proof(ProofError::IncorrectProof(format!( + "nullifier {} was not found as spent in the shielded withdrawal proof", + hex::encode(nf) + )))); + } + } + + // Compute withdrawal document ID deterministically (same as prove side) + let first_nullifier = nullifier_keys.first().ok_or_else(|| { + Error::Proof(ProofError::InvalidTransition( + "shielded withdrawal has no nullifiers".to_string(), + )) + })?; + let mut entropy = Vec::new(); + entropy.extend_from_slice(first_nullifier); + entropy.extend_from_slice(st.output_script().as_bytes()); + let document_id = Document::generate_document_id_v0( + &withdrawals_contract::ID, + &withdrawals_contract::OWNER_ID, + withdrawal::NAME, + &entropy, + ); + + let contract = + known_contracts_provider_fn(&withdrawals_contract::ID)?.ok_or_else(|| { + Error::Proof(ProofError::UnknownContract( + "withdrawals contract not available for shielded withdrawal verification" + .to_string(), + )) + })?; + let document_type = + contract + .document_type_for_name(withdrawal::NAME) + .map_err(|e| { + Error::Proof(ProofError::UnknownContract(format!( + "cannot fetch withdrawal document type: {}", + e + ))) + })?; + + let doc_query = SingleDocumentDriveQuery { + contract_id: withdrawals_contract::ID.to_buffer(), + document_type_name: withdrawal::NAME.to_string(), + document_type_keeps_history: false, + document_id: document_id.to_buffer(), + block_time_ms: None, + contested_status: SingleDocumentDriveQueryContestedStatus::NotContested, + }; + + let (root_hash_doc, maybe_doc) = + doc_query.verify_proof(true, proof, document_type, platform_version)?; + + if root_hash_nf != root_hash_doc { + return Err(Error::Proof(ProofError::CorruptedProof( + "shielded withdrawal proof root hashes do not match between nullifiers and document" + .to_string(), + ))); + } + + let documents = BTreeMap::from([(document_id, maybe_doc)]); + + Ok(( + root_hash_nf, + VerifiedShieldedNullifiersWithWithdrawalDocument(statuses, documents), + )) + } + StateTransition::ShieldFromAssetLock(st) => { + use dpp::asset_lock::reduced_asset_lock_value::AssetLockValue; + use dpp::asset_lock::StoredAssetLockInfo; + use dpp::identity::state_transition::AssetLockProved; + use dpp::serialization::PlatformDeserializable; + use dpp::state_transition::proof_result::StateTransitionProofResult::VerifiedAssetLockConsumed; + use grovedb::Element; + + let outpoint = st.asset_lock_proof().out_point().ok_or_else(|| { + Error::Proof(ProofError::InvalidTransition( + "shield from asset lock has no outpoint".to_string(), + )) + })?; + let outpoint_bytes: [u8; 36] = outpoint.into(); + + // Build the same PathQuery as the prove side + let mut query = grovedb::Query::new(); + query.insert_key(outpoint_bytes.to_vec()); + let path_query = grovedb::PathQuery::new( + vec![vec![72u8]], // RootTree::SpentAssetLockTransactions + grovedb::SizedQuery::new(query, Some(1), None), + ); + + let (root_hash, mut proved_key_values) = + grovedb::GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )?; + + if proved_key_values.len() > 1 { + return Err(Error::Proof(ProofError::TooManyElements( + "expected at most 1 element for asset lock outpoint", + ))); + } + + let info = if let Some(proved) = proved_key_values.pop() { + match proved.2 { + Some(Element::Item(bytes, _)) => { + if bytes.is_empty() { + StoredAssetLockInfo::FullyConsumed + } else { + StoredAssetLockInfo::PartiallyConsumed( + AssetLockValue::deserialize_from_bytes(&bytes)?, + ) + } + } + Some(_) => { + return Err(Error::Proof(ProofError::CorruptedProof( + "expected an item element for asset lock outpoint".to_string(), + ))); + } + None => StoredAssetLockInfo::NotPresent, + } + } else { + StoredAssetLockInfo::NotPresent + }; + + Ok((root_hash, VerifiedAssetLockConsumed(info))) } } } From 2c740986e9e8bdaa887e2e5c5fca7b0bcf6697c4 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:52:03 +0700 Subject: [PATCH 02/25] fix(drive): use RootTree enum instead of magic number 72u8 (#3205) Co-authored-by: Claude Opus 4.6 --- packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs index d1a62293b78..7139df4bd30 100644 --- a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs +++ b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs @@ -1,4 +1,4 @@ -use crate::drive::Drive; +use crate::drive::{Drive, RootTree}; use crate::error::proof::ProofError; use crate::error::Error; use crate::prove::prove_state_transition::ProofCreationResult; @@ -401,7 +401,7 @@ impl Drive { query.insert_key(outpoint_bytes.to_vec()); PathQuery::new( - vec![vec![72u8]], // RootTree::SpentAssetLockTransactions + vec![vec![RootTree::SpentAssetLockTransactions as u8]], grovedb::SizedQuery::new(query, Some(1), None), ) } From c93ae0e9ce570c3cba6dcd5f026c3f4232d40269 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:53:15 +0700 Subject: [PATCH 03/25] fix(drive): guard against max_elements=0 in encrypted notes verification (#3204) Co-authored-by: Claude Opus 4.6 --- .../shielded/verify_shielded_encrypted_notes/v0/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs index 39cb6eded49..83a627314aa 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs @@ -15,9 +15,15 @@ impl Drive { verify_subset_of_proof: bool, platform_version: &PlatformVersion, ) -> Result<(RootHash, Vec<(Vec, Vec, Vec)>), Error> { + if max_elements == 0 { + return Err(Error::Drive(DriveError::CorruptedElementType( + "max_elements must be greater than zero", + ))); + } + // start_index must be chunk-aligned (multiple of max_elements) let chunk_size = max_elements as u64; - if chunk_size > 0 && start_index % chunk_size != 0 { + if start_index % chunk_size != 0 { return Err(Error::Drive(DriveError::CorruptedElementType( "start_index is not chunk-aligned; must be a multiple of max_elements", ))); From 73c058a0ca4bfa8b2ec67fffc70711d939958581 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:55:06 +0700 Subject: [PATCH 04/25] fix(drive): replace expect/unwrap with error propagation in production code (#3203) Co-authored-by: Claude Opus 4.6 --- .../fetch_compacted_nullifiers/v0/mod.rs | 30 +++++++++++++++---- .../v0/mod.rs | 27 +++++++++-------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs index d95e5c5a3b3..479c5957a83 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs @@ -77,12 +77,20 @@ impl Drive { let start_block = u64::from_be_bytes( key[0..8] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?, ); let end_block = u64::from_be_bytes( key[8..16] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?, ); // Only include if end_block >= start_block_height (range contains our block) @@ -157,12 +165,20 @@ impl Drive { let start_block = u64::from_be_bytes( key[0..8] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?, ); let end_block = u64::from_be_bytes( key[8..16] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?, ); // Skip if this is the same range we got from descending query @@ -235,7 +251,11 @@ impl Drive { let end_block = u64::from_be_bytes( key[8..16] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?, ); // If this range contains start_block_height, use its exact key if end_block >= start_block_height { diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index 371c52588b9..76be0ca28a7 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -113,16 +113,11 @@ impl Drive { if key.len() != 16 { return None; } - let range_start = u64::from_be_bytes( - key[0..8] - .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), - ); - let range_end = u64::from_be_bytes( - key[8..16] - .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), - ); + // Safety: length verified to be 16 above + let range_start = + u64::from_be_bytes(key[0..8].try_into().expect("len checked to be 16")); + let range_end = + u64::from_be_bytes(key[8..16].try_into().expect("len checked to be 16")); // Check if this range contains start_block_height if range_start <= start_block_height && start_block_height <= range_end { @@ -175,12 +170,20 @@ impl Drive { let range_start = u64::from_be_bytes( key[0..8] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Proof(ProofError::CorruptedProof( + "invalid key slice length for block height".to_string(), + )) + })?, ); let range_end = u64::from_be_bytes( key[8..16] .try_into() - .expect("slice is exactly 8 bytes from a 16-byte key"), + .map_err(|_| { + Error::Proof(ProofError::CorruptedProof( + "invalid key slice length for block height".to_string(), + )) + })?, ); // Get the serialized data from the Item element From d1c1a61ec42cd0aa5b651f7a32320e992a2a4a26 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:55:38 +0700 Subject: [PATCH 05/25] fix(drive): import nullifier constants instead of duplicating them (#3202) Co-authored-by: Claude Opus 4.6 --- .../shielded/verify_compacted_nullifier_changes/v0/mod.rs | 4 +--- .../verify/shielded/verify_recent_nullifier_changes/v0/mod.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index 76be0ca28a7..a260f25c168 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -1,11 +1,9 @@ +use crate::drive::saved_block_transactions::COMPACTED_NULLIFIERS_KEY_U8; use crate::drive::Drive; use crate::drive::RootTree; use crate::error::proof::ProofError; use crate::error::Error; use crate::verify::RootHash; - -/// The subtree key for compacted nullifiers storage as u8 -const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; use grovedb::operations::proof::{GroveDBProof, ProofBytes}; use grovedb::{ GroveDb, MerkProofDecoder, MerkProofNode, MerkProofOp, PathQuery, Query, SizedQuery, diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs index e8da02c8a2b..5391aa95f1d 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -1,11 +1,9 @@ +use crate::drive::saved_block_transactions::NULLIFIERS_KEY_U8; use crate::drive::Drive; use crate::drive::RootTree; use crate::error::proof::ProofError; use crate::error::Error; use crate::verify::RootHash; - -/// The subtree key for nullifiers storage as u8 -const NULLIFIERS_KEY_U8: u8 = b'n'; use grovedb::{Element, GroveDb, PathQuery, Query, SizedQuery}; use platform_version::version::PlatformVersion; From 357e9b0298308ef02010f536cebb6560ea51d572 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:56:32 +0700 Subject: [PATCH 06/25] fix(drive): replace unchecked integer casts with try_from (#3201) Co-authored-by: Claude Opus 4.6 --- .../saved_block_transactions/store_nullifiers/v0/mod.rs | 6 +++++- .../verify/shielded/verify_shielded_nullifiers/v0/mod.rs | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs index 15bcad2a6ef..34569f69203 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs @@ -60,7 +60,11 @@ impl Drive { })?; // The sum value is the number of nullifiers - let entry_count = nullifiers.len() as i64; + let entry_count = i64::try_from(nullifiers.len()).map_err(|_| { + Error::Drive(crate::error::drive::DriveError::CorruptedDriveState( + "nullifier count exceeds i64::MAX".to_string(), + )) + })?; // Store in the SavedBlockTransactions/Nullifiers count sum tree with block height as key let path: [&[u8]; 2] = Drive::saved_block_transactions_nullifiers_path(); diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs index 4725b9ba841..3b47a0cef96 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs @@ -19,7 +19,13 @@ impl Drive { path: shielded_credit_pool_nullifiers_path_vec(), query: SizedQuery { query, - limit: Some(nullifiers.len() as u16), + limit: Some( + u16::try_from(nullifiers.len()).map_err(|_| { + Error::Drive(crate::error::drive::DriveError::CorruptedDriveState( + "nullifier count exceeds u16::MAX".to_string(), + )) + })?, + ), offset: None, }, }; From 5218ddff7695dd6d725108954c9680863381f5da Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:57:42 +0700 Subject: [PATCH 07/25] fix(drive): correct tree types in shielded pool estimated costs (#3200) Co-authored-by: Claude Opus 4.6 --- packages/rs-drive/src/drive/shielded/estimated_costs.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rs-drive/src/drive/shielded/estimated_costs.rs b/packages/rs-drive/src/drive/shielded/estimated_costs.rs index 30998a478e8..2e3107063f3 100644 --- a/packages/rs-drive/src/drive/shielded/estimated_costs.rs +++ b/packages/rs-drive/src/drive/shielded/estimated_costs.rs @@ -88,9 +88,9 @@ impl Drive { SomeSumTrees { sum_trees_weight: 0, big_sum_trees_weight: 0, - count_trees_weight: 0, + count_trees_weight: 1, // nullifiers (ProvableCountTree) count_sum_trees_weight: 0, - non_sum_trees_weight: 2, // nullifiers + anchors + non_sum_trees_weight: 1, // anchors }, None, 3, // 3 subtrees: notes (CommitmentTree), nullifiers, anchors @@ -113,11 +113,11 @@ impl Drive { ); // Nullifiers tree: [AddressBalances, "s", 2] - // NormalTree - stores spent nullifiers (32-byte key -> empty item) + // ProvableCountTree - stores spent nullifiers (32-byte key -> empty item) estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(shielded_credit_pool_nullifiers_path()), EstimatedLayerInformation { - tree_type: TreeType::NormalTree, + tree_type: TreeType::ProvableCountTree, estimated_layer_count: EstimatedLevel(16, false), estimated_layer_sizes: AllItems(NULLIFIER_KEY_SIZE, 0, None), }, From 743faebfe4331f1e5a7919282f812deee4c7f11b Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Fri, 6 Mar 2026 17:58:39 +0700 Subject: [PATCH 08/25] fix(drive): match min_depth between prove and verify for trunk query (#3199) Co-authored-by: Claude Opus 4.6 --- .../shielded/verify_nullifiers_trunk_query/v0/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs index fdae3be8508..d25d8752d32 100644 --- a/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_nullifiers_trunk_query/v0/mod.rs @@ -13,6 +13,11 @@ impl Drive { platform_version: &PlatformVersion, ) -> Result<(RootHash, GroveTrunkQueryResult), Error> { let path = nullifiers_path_for_pool(pool_type, pool_identifier)?; + let min_depth = platform_version + .drive + .methods + .shielded + .nullifiers_query_min_depth; let max_depth = platform_version .drive .methods @@ -21,8 +26,8 @@ impl Drive { let query = PathTrunkChunkQuery { path, + min_depth: Some(min_depth), max_depth, - min_depth: None, }; let (root_hash, result) = GroveDb::verify_trunk_chunk_proof( From 77615a6fe680ff40400fb3019a2b34f9545d1f2a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 6 Mar 2026 18:11:31 +0700 Subject: [PATCH 09/25] fix(drive): resolve clippy lints in shielded module - Allow clippy::too_many_arguments on prove_nullifiers_branch_query - Allow clippy::type_complexity on verify_shielded_encrypted_notes and verify_shielded_nullifiers - Fix clippy::clone_on_copy on PlatformAddress in unshield transformer - Fix clippy::manual_is_multiple_of in verify_shielded_encrypted_notes Co-Authored-By: Claude Opus 4.6 --- .../drive/shielded/prove/prove_nullifiers_branch_query/mod.rs | 1 + .../shielded/prove/prove_nullifiers_branch_query/v0/mod.rs | 1 + .../shielded/unshield/v0/transformer.rs | 2 +- .../src/verify/shielded/verify_shielded_encrypted_notes/mod.rs | 1 + .../verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs | 3 ++- .../src/verify/shielded/verify_shielded_nullifiers/mod.rs | 1 + .../src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs | 1 + 7 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs index a393eb3639e..4e18c88646c 100644 --- a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/mod.rs @@ -58,6 +58,7 @@ impl Drive { } /// Proves nullifiers using a branch chunk query and tracks operations. + #[allow(clippy::too_many_arguments)] pub fn prove_nullifiers_branch_query_operations( &self, pool_type: u32, diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs index 2712a8f63b8..29ae94b3beb 100644 --- a/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/prove/prove_nullifiers_branch_query/v0/mod.rs @@ -29,6 +29,7 @@ impl Drive { ) } + #[allow(clippy::too_many_arguments)] pub(super) fn prove_nullifiers_branch_query_operations_v0( &self, pool_type: u32, diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs index 09fc0c848d5..f86f5b298ab 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs @@ -13,7 +13,7 @@ impl UnshieldTransitionActionV0 { current_total_balance: Credits, ) -> ConsensusValidationResult { ConsensusValidationResult::new_with_data(UnshieldTransitionActionV0 { - output_address: value.output_address.clone(), + output_address: value.output_address, amount: value.unshielding_amount, notes, anchor, diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs index 969f319b450..32d5fa73c64 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/mod.rs @@ -8,6 +8,7 @@ use dpp::version::PlatformVersion; impl Drive { /// Verifies a proof for shielded encrypted notes. + #[allow(clippy::type_complexity)] pub fn verify_shielded_encrypted_notes( proof: &[u8], start_index: u64, diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs index 83a627314aa..e48bd1b0fa8 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_encrypted_notes/v0/mod.rs @@ -7,6 +7,7 @@ use grovedb::{Element, GroveDb, PathQuery, Query, QueryItem, SizedQuery, Subquer use platform_version::version::PlatformVersion; impl Drive { + #[allow(clippy::type_complexity)] pub(super) fn verify_shielded_encrypted_notes_v0( proof: &[u8], start_index: u64, @@ -23,7 +24,7 @@ impl Drive { // start_index must be chunk-aligned (multiple of max_elements) let chunk_size = max_elements as u64; - if start_index % chunk_size != 0 { + if !start_index.is_multiple_of(chunk_size) { return Err(Error::Drive(DriveError::CorruptedElementType( "start_index is not chunk-aligned; must be a multiple of max_elements", ))); diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs index b8c0795c26f..d89ddc7c634 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/mod.rs @@ -8,6 +8,7 @@ use dpp::version::PlatformVersion; impl Drive { /// Verifies a proof for shielded nullifier spend status. + #[allow(clippy::type_complexity)] pub fn verify_shielded_nullifiers( proof: &[u8], nullifiers: &[Vec], diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs index 3b47a0cef96..8372943f340 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs @@ -6,6 +6,7 @@ use grovedb::{GroveDb, PathQuery, Query, SizedQuery}; use platform_version::version::PlatformVersion; impl Drive { + #[allow(clippy::type_complexity)] pub(super) fn verify_shielded_nullifiers_v0( proof: &[u8], nullifiers: &[Vec], From 254334175f8417dd9668de68a6b53b7b934e2ea8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 7 Mar 2026 19:59:21 +0700 Subject: [PATCH 10/25] fix(drive): fix CI failures for shielded module - Define NULLIFIERS_KEY_U8 and COMPACTED_NULLIFIERS_KEY_U8 locally in verify modules instead of importing from server-only saved_block_transactions module (fixes verify-only feature builds) - Add missing shielded action match arms in drive-abci strategy tests - Run cargo fmt on affected files Co-Authored-By: Claude Opus 4.6 --- .../verify_state_transitions.rs | 6 ++ .../fetch_compacted_nullifiers/v0/mod.rs | 70 +++++++------------ .../v0/mod.rs | 31 ++++---- .../verify_recent_nullifier_changes/v0/mod.rs | 3 +- .../verify_shielded_nullifiers/v0/mod.rs | 12 ++-- 5 files changed, 50 insertions(+), 72 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs index ca2f6cf6943..09be30ab6c5 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs @@ -1445,6 +1445,12 @@ pub(crate) fn verify_state_transitions_were_or_were_not_executed( } } StateTransitionAction::BumpAddressInputNoncesAction(_) => {} + StateTransitionAction::ShieldAction(_) => {} + StateTransitionAction::ShieldedTransferAction(_) => {} + StateTransitionAction::UnshieldAction(_) => {} + StateTransitionAction::ShieldFromAssetLockAction(_) => {} + StateTransitionAction::ShieldedWithdrawalAction(_) => {} + StateTransitionAction::PenalizeShieldedPoolAction(_) => {} } } else { // if we don't have an action this means there was a problem in the validation of the state transition diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs index 479c5957a83..93f1e6d54ba 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs @@ -74,24 +74,16 @@ impl Drive { ))); } - let start_block = u64::from_be_bytes( - key[0..8] - .try_into() - .map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?, - ); - let end_block = u64::from_be_bytes( - key[8..16] - .try_into() - .map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?, - ); + let start_block = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); + let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); // Only include if end_block >= start_block_height (range contains our block) if end_block >= start_block_height { @@ -162,24 +154,16 @@ impl Drive { ))); } - let start_block = u64::from_be_bytes( - key[0..8] - .try_into() - .map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?, - ); - let end_block = u64::from_be_bytes( - key[8..16] - .try_into() - .map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?, - ); + let start_block = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); + let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); // Skip if this is the same range we got from descending query if Some(start_block) == desc_start_block { @@ -248,15 +232,11 @@ impl Drive { // Otherwise use (start_block_height, start_block_height) since end_block >= start_block always let start_key = if let Some((key, _)) = desc_results.to_key_elements().into_iter().next() { if key.len() == 16 { - let end_block = u64::from_be_bytes( - key[8..16] - .try_into() - .map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?, - ); + let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); // If this range contains start_block_height, use its exact key if end_block >= start_block_height { key diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index a260f25c168..fcb154174f6 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -1,4 +1,5 @@ -use crate::drive::saved_block_transactions::COMPACTED_NULLIFIERS_KEY_U8; +/// The subtree key for compacted nullifiers storage as u8 (must match saved_block_transactions::COMPACTED_NULLIFIERS_KEY_U8) +const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; use crate::drive::Drive; use crate::drive::RootTree; use crate::error::proof::ProofError; @@ -165,24 +166,16 @@ impl Drive { ))); } - let range_start = u64::from_be_bytes( - key[0..8] - .try_into() - .map_err(|_| { - Error::Proof(ProofError::CorruptedProof( - "invalid key slice length for block height".to_string(), - )) - })?, - ); - let range_end = u64::from_be_bytes( - key[8..16] - .try_into() - .map_err(|_| { - Error::Proof(ProofError::CorruptedProof( - "invalid key slice length for block height".to_string(), - )) - })?, - ); + let range_start = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { + Error::Proof(ProofError::CorruptedProof( + "invalid key slice length for block height".to_string(), + )) + })?); + let range_end = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Proof(ProofError::CorruptedProof( + "invalid key slice length for block height".to_string(), + )) + })?); // Get the serialized data from the Item element let grovedb::Element::Item(serialized_data, _) = element else { diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs index 5391aa95f1d..2f71970a239 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -1,4 +1,5 @@ -use crate::drive::saved_block_transactions::NULLIFIERS_KEY_U8; +/// The subtree key for nullifiers storage as u8 (must match saved_block_transactions::NULLIFIERS_KEY_U8) +const NULLIFIERS_KEY_U8: u8 = b'n'; use crate::drive::Drive; use crate::drive::RootTree; use crate::error::proof::ProofError; diff --git a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs index 8372943f340..9b899350b23 100644 --- a/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_shielded_nullifiers/v0/mod.rs @@ -20,13 +20,11 @@ impl Drive { path: shielded_credit_pool_nullifiers_path_vec(), query: SizedQuery { query, - limit: Some( - u16::try_from(nullifiers.len()).map_err(|_| { - Error::Drive(crate::error::drive::DriveError::CorruptedDriveState( - "nullifier count exceeds u16::MAX".to_string(), - )) - })?, - ), + limit: Some(u16::try_from(nullifiers.len()).map_err(|_| { + Error::Drive(crate::error::drive::DriveError::CorruptedDriveState( + "nullifier count exceeds u16::MAX".to_string(), + )) + })?), offset: None, }, }; From 0432425c8db6f32496cb0b79187f188c1fd294f6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sat, 7 Mar 2026 23:28:17 +0700 Subject: [PATCH 11/25] fix(drive): fix bugs found in CodeRabbit review of shielded module - Add checked_add for auto-incremented count tree index to prevent overflow - Require withdrawal document to be present in executed ShieldedWithdrawal proof - Reject NotPresent asset lock info in executed ShieldFromAssetLock proof - Fix compacted nullifier dedup to compare full (start_block, end_block) tuple - Error on unsupported V1 proof byte variants instead of silently returning empty Co-Authored-By: Claude Opus 4.6 --- .../fetch_compacted_nullifiers/v0/mod.rs | 8 +++++--- .../v0/mod.rs | 7 ++++++- .../v0/mod.rs | 5 ++++- .../v0/mod.rs | 5 ++++- .../v0/mod.rs | 17 ++++++++++++++--- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs index 93f1e6d54ba..2ade9789bda 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs @@ -135,8 +135,10 @@ impl Drive { &platform_version.drive, )?; - // Track the start_block from descending query to avoid duplicates - let desc_start_block = compacted_changes.first().map(|(start, _, _)| *start); + // Track the (start_block, end_block) from descending query to avoid duplicates + let desc_range_key = compacted_changes + .first() + .map(|(start, end, _)| (*start, *end)); for (key, element) in asc_results.to_key_elements() { // Check if we've reached the limit @@ -166,7 +168,7 @@ impl Drive { })?); // Skip if this is the same range we got from descending query - if Some(start_block) == desc_start_block { + if Some((start_block, end_block)) == desc_range_key { continue; } diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs index 80d80ed3e44..c6ccf552057 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs @@ -1,4 +1,5 @@ use crate::drive::Drive; +use crate::error::drive::DriveError; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; use crate::util::grove_operations::DirectQueryType; @@ -50,7 +51,11 @@ impl Drive { // Insert each item with a sequential key for (i, element) in items.into_iter().enumerate() { - let index = current_count + i as u64; + let index = current_count.checked_add(i as u64).ok_or( + Error::Drive(DriveError::CorruptedCodeExecution( + "auto-incremented count tree index overflow", + )), + )?; drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element( insert_path.clone(), index.to_be_bytes().to_vec(), diff --git a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs index e01870da58c..6a446d8f8d8 100644 --- a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs @@ -103,7 +103,10 @@ impl Drive { compacted_layer .map(|layer| match &layer.merk_proof { ProofBytes::Merk(bytes) => extract_kv_entries_from_merk_proof(bytes), - _ => Ok(vec![]), + other => Err(Error::Proof(ProofError::CorruptedProof(format!( + "unsupported V1 proof bytes variant for compacted address balances: {:?}", + std::mem::discriminant(other) + )))), }) .transpose()? .unwrap_or_default() diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index fcb154174f6..511d832fa0e 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -99,7 +99,10 @@ impl Drive { compacted_layer .map(|layer| match &layer.merk_proof { ProofBytes::Merk(bytes) => extract_kv_entries_from_merk_proof(bytes), - _ => Ok(vec![]), + other => Err(Error::Proof(ProofError::CorruptedProof(format!( + "unsupported V1 proof bytes variant for compacted nullifiers: {:?}", + std::mem::discriminant(other) + )))), }) .transpose()? .unwrap_or_default() diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index eb4250d7c2a..0dcbbe88885 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1274,7 +1274,12 @@ impl Drive { ))); } - let documents = BTreeMap::from([(document_id, maybe_doc)]); + let doc = maybe_doc.ok_or_else(|| { + Error::Proof(ProofError::CorruptedProof( + "shielded withdrawal was executed but withdrawal document is missing from proof".to_string(), + )) + })?; + let documents = BTreeMap::from([(document_id, Some(doc))]); Ok(( root_hash_nf, @@ -1333,10 +1338,16 @@ impl Drive { "expected an item element for asset lock outpoint".to_string(), ))); } - None => StoredAssetLockInfo::NotPresent, + None => { + return Err(Error::Proof(ProofError::CorruptedProof( + "shield from asset lock was executed but asset lock outpoint is absent from proof".to_string(), + ))); + } } } else { - StoredAssetLockInfo::NotPresent + return Err(Error::Proof(ProofError::CorruptedProof( + "shield from asset lock was executed but no proved key values returned".to_string(), + ))); }; Ok((root_hash, VerifiedAssetLockConsumed(info))) From 72217725e298877810637ae146ea4f3fb83faa5e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 8 Mar 2026 14:59:10 +0700 Subject: [PATCH 12/25] refactor(dpp): replace ProtocolError::Generic with specific error types Add AddressWitnessError and ShieldedBuildError variants to ProtocolError to replace Generic usage in address witness verification and shielded transaction building code. Also fix IdentifierError usage in wallet FFI. Co-Authored-By: Claude Opus 4.6 --- .../src/address_funds/platform_address.rs | 44 +++++++++---------- packages/rs-dpp/src/errors/protocol_error.rs | 6 +++ packages/rs-dpp/src/shielded/builder/mod.rs | 14 +++--- .../rs-dpp/src/shielded/builder/shield.rs | 2 +- .../builder/shield_from_asset_lock.rs | 2 +- .../src/shielded/builder/shielded_transfer.rs | 14 +++--- .../shielded/builder/shielded_withdrawal.rs | 8 ++-- .../rs-dpp/src/shielded/builder/unshield.rs | 8 ++-- packages/rs-platform-wallet-ffi/src/types.rs | 2 +- 9 files changed, 53 insertions(+), 47 deletions(-) diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 3775accefc9..1891c707368 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -307,7 +307,7 @@ impl PlatformAddress { pubkey_hash, ) .map_err(|e| { - ProtocolError::Generic(format!("P2PKH signature verification failed: {}", e)) + ProtocolError::AddressWitnessError(format!("P2PKH signature verification failed: {}", e)) })?; Ok(AddressWitnessVerificationOperations::for_p2pkh( @@ -325,7 +325,7 @@ impl PlatformAddress { let script = ScriptBuf::from_bytes(redeem_script.to_vec()); let computed_hash = script.script_hash(); if computed_hash.as_byte_array() != script_hash { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Script hash {} does not match address hash {}", hex::encode(computed_hash.as_byte_array()), hex::encode(script_hash) @@ -343,7 +343,7 @@ impl PlatformAddress { .collect(); if valid_signatures.len() < threshold { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Not enough signatures: got {}, need {}", valid_signatures.len(), threshold @@ -368,11 +368,11 @@ impl PlatformAddress { valid_signatures[sig_idx].as_slice(), ) .map_err(|e| { - ProtocolError::Generic(format!("Invalid signature format: {}", e)) + ProtocolError::AddressWitnessError(format!("Invalid signature format: {}", e)) })?; let pub_key = PublicKey::from_slice(&pubkeys[pubkey_idx]).map_err(|e| { - ProtocolError::Generic(format!("Invalid public key: {}", e)) + ProtocolError::AddressWitnessError(format!("Invalid public key: {}", e)) })?; if secp @@ -391,19 +391,19 @@ impl PlatformAddress { signable_bytes.len(), )) } else { - Err(ProtocolError::Generic(format!( + Err(ProtocolError::AddressWitnessError(format!( "Not enough valid signatures: verified {}, need {}", matched, threshold ))) } } (PlatformAddress::P2pkh(_), AddressWitness::P2sh { .. }) => { - Err(ProtocolError::Generic( + Err(ProtocolError::AddressWitnessError( "P2PKH address requires P2pkh witness, got P2sh".to_string(), )) } (PlatformAddress::P2sh(_), AddressWitness::P2pkh { .. }) => Err( - ProtocolError::Generic("P2SH address requires P2sh witness, got P2pkh".to_string()), + ProtocolError::AddressWitnessError("P2SH address requires P2sh witness, got P2pkh".to_string()), ), } } @@ -435,7 +435,7 @@ impl PlatformAddress { if byte >= OP_PUSHNUM_1.to_u8() && byte <= OP_PUSHNUM_16.to_u8() { (byte - OP_PUSHNUM_1.to_u8() + 1) as usize } else { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Unsupported P2SH script type: only standard multisig (OP_M ... OP_N OP_CHECKMULTISIG) is supported. \ First opcode was 0x{:02x}, expected OP_1 through OP_16", byte @@ -443,20 +443,20 @@ impl PlatformAddress { } } Some(Ok(dashcore::blockdata::script::Instruction::PushBytes(_))) => { - return Err(ProtocolError::Generic( + return Err(ProtocolError::AddressWitnessError( "Unsupported P2SH script type: only standard multisig is supported. \ Script starts with a data push instead of OP_M threshold." .to_string(), )) } Some(Err(e)) => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Error parsing P2SH script: {:?}", e ))) } None => { - return Err(ProtocolError::Generic( + return Err(ProtocolError::AddressWitnessError( "Empty P2SH redeem script".to_string(), )) } @@ -483,7 +483,7 @@ impl PlatformAddress { // This is OP_N, the total number of keys let n = (byte - OP_PUSHNUM_1.to_u8() + 1) as usize; if pubkeys.len() != n { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Multisig script declares {} keys but contains {}", n, pubkeys.len() @@ -492,24 +492,24 @@ impl PlatformAddress { break; } else if op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY { // Hit CHECKMULTISIG without seeing OP_N - malformed - return Err(ProtocolError::Generic( + return Err(ProtocolError::AddressWitnessError( "Malformed multisig script: OP_CHECKMULTISIG before OP_N".to_string(), )); } else { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Unsupported opcode 0x{:02x} in P2SH script. Only standard multisig is supported.", byte ))); } } Some(Err(e)) => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Error parsing multisig script: {:?}", e ))) } None => { - return Err(ProtocolError::Generic( + return Err(ProtocolError::AddressWitnessError( "Incomplete multisig script: unexpected end before OP_N".to_string(), )) } @@ -518,7 +518,7 @@ impl PlatformAddress { // Validate threshold if threshold > pubkeys.len() { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::AddressWitnessError(format!( "Invalid multisig: threshold {} exceeds number of keys {}", threshold, pubkeys.len() @@ -531,24 +531,24 @@ impl PlatformAddress { if op == OP_CHECKMULTISIG { // Standard multisig - verify script is complete if instructions.next().is_some() { - return Err(ProtocolError::Generic( + return Err(ProtocolError::AddressWitnessError( "Multisig script has extra data after OP_CHECKMULTISIG".to_string(), )); } Ok((threshold, pubkeys)) } else if op == OP_CHECKMULTISIGVERIFY { - Err(ProtocolError::Generic( + Err(ProtocolError::AddressWitnessError( "OP_CHECKMULTISIGVERIFY is not supported, only OP_CHECKMULTISIG" .to_string(), )) } else { - Err(ProtocolError::Generic(format!( + Err(ProtocolError::AddressWitnessError(format!( "Expected OP_CHECKMULTISIG, got opcode 0x{:02x}", op.to_u8() ))) } } - _ => Err(ProtocolError::Generic( + _ => Err(ProtocolError::AddressWitnessError( "Invalid multisig script: expected OP_CHECKMULTISIG after OP_N".to_string(), )), } diff --git a/packages/rs-dpp/src/errors/protocol_error.rs b/packages/rs-dpp/src/errors/protocol_error.rs index ec5f87ad88e..e0497c685b1 100644 --- a/packages/rs-dpp/src/errors/protocol_error.rs +++ b/packages/rs-dpp/src/errors/protocol_error.rs @@ -140,6 +140,12 @@ pub enum ProtocolError { #[error("Generic Error: {0}")] Generic(String), + #[error("Address witness verification error: {0}")] + AddressWitnessError(String), + + #[error("Shielded transaction build error: {0}")] + ShieldedBuildError(String), + #[error("Not supported Error: {0}")] NotSupported(String), diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs index 543c818ffaa..a8a25c2b45f 100644 --- a/packages/rs-dpp/src/shielded/builder/mod.rs +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -157,7 +157,7 @@ pub(crate) fn build_output_only_bundle( builder .add_output(None, payment_address, NoteValue::from_raw(amount), memo) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add output: {:?}", e)))?; prove_and_sign_bundle(builder, prover, &[], &[]) } @@ -185,7 +185,7 @@ pub(crate) fn build_spend_bundle( for spend in spends { builder .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)))?; } builder @@ -195,7 +195,7 @@ pub(crate) fn build_spend_bundle( NoteValue::from_raw(output_amount), memo, ) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add output: {:?}", e)))?; prove_and_sign_bundle( builder, @@ -217,19 +217,19 @@ pub(crate) fn prove_and_sign_bundle( let (unauthorized, _) = builder .build::(&mut rng) - .map_err(|e| ProtocolError::Generic(format!("failed to build bundle: {:?}", e)))? - .ok_or_else(|| ProtocolError::Generic("bundle was empty after build".to_string()))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to build bundle: {:?}", e)))? + .ok_or_else(|| ProtocolError::ShieldedBuildError("bundle was empty after build".to_string()))?; let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); let proven = unauthorized .create_proof(prover.proving_key(), &mut rng) - .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to create proof: {:?}", e)))?; proven .apply_signatures(rng, sighash, signing_keys) - .map_err(|e| ProtocolError::Generic(format!("failed to apply signatures: {:?}", e))) + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to apply signatures: {:?}", e))) } /// Shared test utilities for builder tests. diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs index 3f41b67ded3..91eac886619 100644 --- a/packages/rs-dpp/src/shielded/builder/shield.rs +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -41,7 +41,7 @@ pub fn build_shield_transition, P: OrchardProver>( platform_version: &PlatformVersion, ) -> Result { if fee_strategy.is_empty() { - return Err(ProtocolError::Generic( + return Err(ProtocolError::ShieldedBuildError( "fee_strategy must have at least one step".to_string(), )); } diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs index d91fa6d6b1c..09ce869b283 100644 --- a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -41,7 +41,7 @@ pub fn build_shield_from_asset_lock_transition( .checked_neg() .and_then(|v| u64::try_from(v).ok()) .ok_or_else(|| { - ProtocolError::Generic( + ProtocolError::ShieldedBuildError( "shield_from_asset_lock: bundle value_balance is not negative".to_string(), ) })?; diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs index 03217df0cef..0207a862088 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -55,13 +55,13 @@ pub fn build_shielded_transfer_transition( let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); let effective_fee = match fee { Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "fee {} is below minimum required fee {}", f, min_fee ))); } Some(f) if f > min_fee.saturating_mul(1000) => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "fee {} exceeds 1000x the minimum fee {}", f, min_fee ))); @@ -72,9 +72,9 @@ pub fn build_shielded_transfer_transition( let required = transfer_amount .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::Generic("fee + transfer_amount overflows u64".to_string()))?; + .ok_or_else(|| ProtocolError::ShieldedBuildError("fee + transfer_amount overflows u64".to_string()))?; if required > total_spent { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "transfer amount {} + fee {} = {} exceeds total spendable value {}", transfer_amount, effective_fee, required, total_spent ))); @@ -89,7 +89,7 @@ pub fn build_shielded_transfer_transition( for spend in spends { builder .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)))?; } // Primary output to recipient @@ -100,7 +100,7 @@ pub fn build_shielded_transfer_transition( NoteValue::from_raw(transfer_amount), memo, ) - .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add output: {:?}", e)))?; // Change output (if any) if change_amount > 0 { @@ -112,7 +112,7 @@ pub fn build_shielded_transfer_transition( NoteValue::from_raw(change_amount), [0u8; 36], ) - .map_err(|e| ProtocolError::Generic(format!("failed to add change output: {:?}", e)))?; + .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add change output: {:?}", e)))?; } // ShieldedTransfer has no extra_data in sighash diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs index 750b7428e03..306aa0cf93d 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -51,7 +51,7 @@ pub fn build_shielded_withdrawal_transition( platform_version: &PlatformVersion, ) -> Result { if withdrawal_amount > i64::MAX as u64 { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "withdrawal amount {} exceeds maximum allowed value {}", withdrawal_amount, i64::MAX as u64 @@ -65,7 +65,7 @@ pub fn build_shielded_withdrawal_transition( let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); let effective_fee = match fee { Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "fee {} is below minimum required fee {}", f, min_fee ))); @@ -77,10 +77,10 @@ pub fn build_shielded_withdrawal_transition( let required = withdrawal_amount .checked_add(effective_fee) .ok_or_else(|| { - ProtocolError::Generic("fee + withdrawal_amount overflows u64".to_string()) + ProtocolError::ShieldedBuildError("fee + withdrawal_amount overflows u64".to_string()) })?; if required > total_spent { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "withdrawal amount {} + fee {} = {} exceeds total spendable value {}", withdrawal_amount, effective_fee, required, total_spent ))); diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index d7589ad1720..12a1f8ed9cc 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -45,7 +45,7 @@ pub fn build_unshield_transition( platform_version: &PlatformVersion, ) -> Result { if unshield_amount > i64::MAX as u64 { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "unshield amount {} exceeds maximum allowed value {}", unshield_amount, i64::MAX as u64 @@ -59,7 +59,7 @@ pub fn build_unshield_transition( let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); let effective_fee = match fee { Some(f) if f < min_fee => { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "fee {} is below minimum required fee {}", f, min_fee ))); @@ -70,9 +70,9 @@ pub fn build_unshield_transition( let required = unshield_amount .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::Generic("fee + unshield_amount overflows u64".to_string()))?; + .ok_or_else(|| ProtocolError::ShieldedBuildError("fee + unshield_amount overflows u64".to_string()))?; if required > total_spent { - return Err(ProtocolError::Generic(format!( + return Err(ProtocolError::ShieldedBuildError(format!( "unshield amount {} + fee {} = {} exceeds total spendable value {}", unshield_amount, effective_fee, required, total_spent ))); diff --git a/packages/rs-platform-wallet-ffi/src/types.rs b/packages/rs-platform-wallet-ffi/src/types.rs index cc383d2a9f4..82740ca8fb4 100644 --- a/packages/rs-platform-wallet-ffi/src/types.rs +++ b/packages/rs-platform-wallet-ffi/src/types.rs @@ -22,7 +22,7 @@ impl IdentifierBytes { #[allow(clippy::result_large_err)] pub fn to_identifier(&self) -> Result { dpp::prelude::Identifier::from_bytes(&self.bytes) - .map_err(|_| dpp::ProtocolError::Generic("Invalid identifier bytes".to_string())) + .map_err(|_| dpp::ProtocolError::IdentifierError("Invalid identifier bytes".to_string())) } } From 7ca50a67e5300c9c3ed45c506c58fdc06aa3cca2 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 8 Mar 2026 17:07:12 +0700 Subject: [PATCH 13/25] build(drive): update grovedb to latest develop (99a2ab01) - Update grovedb to 99a2ab01 which splits InsertOnly into InsertWithKnownToNotAlreadyExist and InsertIfNotExists - Adapt API changes: empty_commitment_tree/empty_bulk_append_tree now return Result, prove_query_v1 renamed to prove_query - Use GROVE_V3 in drive version v7 for non-Merk tree proof support - Update fee regression tests for protocol v12 shielded pool tree costs (+51,300 processing credits due to larger Merk propagation) - Add 3 protocol v11 fee regression tests to verify pre-shielded fees Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 511 ++++++++++++++---- .../address_funds_transfer/tests.rs | 327 +++++++++-- packages/rs-drive/Cargo.toml | 12 +- .../src/drive/initialization/v3/mod.rs | 2 +- .../add_to_token_total_supply/v0/mod.rs | 2 +- packages/rs-drive/src/fees/op.rs | 10 +- .../shielded/mod.rs | 2 +- .../shielded/shielded_transfer_transition.rs | 2 +- .../shielded/unshield_transition.rs | 2 +- .../src/util/batch/drive_op_batch/shielded.rs | 12 +- .../src/util/batch/grovedb_op_batch/mod.rs | 7 +- .../v0/mod.rs | 8 +- .../grove_get_proved_path_query_v1/v0/mod.rs | 2 +- .../grove_insert_empty_tree/v0/mod.rs | 4 +- packages/rs-platform-version/Cargo.toml | 2 +- .../src/version/drive_versions/v7.rs | 4 +- 16 files changed, 727 insertions(+), 182 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66f0ba61607..de576327fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -245,6 +245,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.9" @@ -601,7 +623,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq 0.4.2", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -726,7 +748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -876,6 +898,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -905,7 +933,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -915,7 +954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1053,6 +1092,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -1068,6 +1116,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1206,6 +1264,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -1351,7 +1418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1915,7 +1982,7 @@ dependencies = [ "dpp", "env_logger 0.11.9", "getrandom 0.2.17", - "grovedb-commitment-tree", + "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", "hex", "indexmap 2.13.0", "integer-encoding", @@ -1966,7 +2033,7 @@ dependencies = [ "dpp", "enum-map", "grovedb", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-epoch-based-storage-flags", "grovedb-path", "grovedb-storage", @@ -2066,6 +2133,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -2575,20 +2648,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2653,15 +2727,15 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", "grovedb-bulk-append-tree", - "grovedb-commitment-tree", - "grovedb-costs", + "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", @@ -2678,7 +2752,7 @@ dependencies = [ "integer-encoding", "intmap", "itertools 0.14.0", - "reqwest", + "reqwest 0.13.2", "sha2", "tempfile", "thiserror 2.0.18", @@ -2691,11 +2765,11 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "blake3", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-merkle-mountain-range", "grovedb-query", @@ -2708,10 +2782,22 @@ dependencies = [ name = "grovedb-commitment-tree" version = "4.0.0" source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "blake3", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", + "incrementalmerkletree", + "orchard", + "thiserror 2.0.18", +] + +[[package]] +name = "grovedb-commitment-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "blake3", "grovedb-bulk-append-tree", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-storage", "incrementalmerkletree", "orchard", @@ -2728,14 +2814,24 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "grovedb-costs" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +dependencies = [ + "integer-encoding", + "intmap", + "thiserror 2.0.18", +] + [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "blake3", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-query", "grovedb-storage", "thiserror 2.0.18", @@ -2744,7 +2840,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "bincode_derive", @@ -2759,9 +2855,9 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "hex", "integer-encoding", "intmap", @@ -2771,7 +2867,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "bincode_derive", @@ -2779,7 +2875,7 @@ dependencies = [ "byteorder", "colored", "ed", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-element", "grovedb-path", "grovedb-query", @@ -2790,25 +2886,25 @@ dependencies = [ "indexmap 2.13.0", "integer-encoding", "num_cpus", - "rand 0.8.5", + "rand 0.10.0", "thiserror 2.0.18", ] [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "blake3", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-storage", ] [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "hex", ] @@ -2816,12 +2912,12 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "bincode", "byteorder", "ed", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-storage", "hex", "indexmap 2.13.0", @@ -2832,10 +2928,10 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "blake3", - "grovedb-costs", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", "grovedb-path", "grovedb-visualize", "hex", @@ -2843,7 +2939,7 @@ dependencies = [ "lazy_static", "num_cpus", "rocksdb", - "strum 0.27.2", + "strum 0.28.0", "tempfile", "thiserror 2.0.18", ] @@ -2851,7 +2947,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2860,7 +2956,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "hex", "itertools 0.14.0", @@ -2869,7 +2965,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" dependencies = [ "serde", "serde_with 3.17.0", @@ -3296,7 +3392,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -3529,9 +3625,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -3604,9 +3700,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e3d65f018c6ae946ab16e80944b97096ed73c35b221d1c478a6c81d8f57940" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -3617,15 +3713,37 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.21" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17c2b211d863c7fde02cbea8a3c1a439b98e109286554f2860bdded7ff83818" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -3727,7 +3845,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -3832,9 +3950,9 @@ checksum = "744a4c881f502e98c2241d2e5f50040ac73b30194d64452bb6260393b53f0dc9" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -4387,7 +4505,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -4641,18 +4759,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", @@ -4661,9 +4779,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -4830,7 +4948,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4932,11 +5050,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -5170,7 +5288,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.6.2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5183,6 +5301,7 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ + "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -5207,16 +5326,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -5227,6 +5346,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -5254,6 +5379,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -5292,6 +5428,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -5442,9 +5584,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", - "futures-channel", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -5480,6 +5620,46 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest-middleware" version = "0.4.2" @@ -5489,7 +5669,7 @@ dependencies = [ "anyhow", "async-trait", "http", - "reqwest", + "reqwest 0.12.28", "serde", "thiserror 1.0.69", "tower-service", @@ -5506,7 +5686,7 @@ dependencies = [ "getrandom 0.2.17", "http", "matchit 0.8.4", - "reqwest", + "reqwest 0.12.28", "reqwest-middleware", "tracing", ] @@ -5620,7 +5800,7 @@ dependencies = [ "once_cell", "prometheus", "quick_cache", - "reqwest", + "reqwest 0.12.28", "reqwest-middleware", "reqwest-tracing", "rmp-serde", @@ -5701,7 +5881,7 @@ dependencies = [ "log", "once_cell", "platform-wallet-ffi", - "reqwest", + "reqwest 0.12.28", "rs-sdk-trusted-context-provider", "serde", "serde_json", @@ -5722,7 +5902,7 @@ dependencies = [ "futures", "hex", "lru", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.18", @@ -5831,6 +6011,7 @@ version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -5862,12 +6043,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -6295,7 +6504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -6306,7 +6515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -6396,9 +6605,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "sketches-ddsketch" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" [[package]] name = "slab" @@ -6424,12 +6633,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6517,11 +6726,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "strum_macros 0.27.2", + "strum_macros 0.28.0", ] [[package]] @@ -6539,9 +6748,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -6646,7 +6855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -6892,9 +7101,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -6902,7 +7111,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -6910,9 +7119,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", @@ -7014,7 +7223,7 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -7035,6 +7244,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.19.15" @@ -7057,19 +7275,19 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -7078,7 +7296,7 @@ version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -7143,7 +7361,7 @@ dependencies = [ "percent-encoding", "pin-project", "rustls-native-certs", - "socket2 0.6.2", + "socket2 0.6.3", "sync_wrapper", "tokio", "tokio-rustls", @@ -7577,13 +7795,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", - "rand 0.9.2", + "rand 0.10.0", "wasm-bindgen", ] @@ -7986,6 +8204,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.6" @@ -8114,6 +8341,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -8159,6 +8395,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -8207,6 +8458,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -8225,6 +8482,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -8243,6 +8506,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -8273,6 +8542,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -8291,6 +8566,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -8309,6 +8590,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -8327,6 +8614,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -8356,9 +8649,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -8529,7 +8822,7 @@ name = "zcash_note_encryption" version = "0.4.1" source = "git+https://github.com/dashpay/zcash_note_encryption?rev=9f7e93d#9f7e93d42cef839d02b9d75918117941d453f8cb" dependencies = [ - "chacha20", + "chacha20 0.9.1", "chacha20poly1305", "cipher", "rand_core 0.6.4", @@ -8547,18 +8840,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -8724,9 +9017,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs index 034a7992d53..274481decb1 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs @@ -7489,9 +7489,11 @@ mod tests { ); // Assert exact values - UPDATE THESE if fees legitimately change + // Fee increased by 51,300 in protocol v12 due to shielded pool trees + // increasing Merk propagation costs in AddressBalances subtree. assert_eq!( - processing_fee, 457440, - "Processing fee changed! Was 457440, now {}", + processing_fee, 508740, + "Processing fee changed! Was 508740, now {}", processing_fee ); assert_eq!( @@ -7500,8 +7502,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6532440, - "Total fee changed! Was 6532440, now {}", + total_fee, 6583740, + "Total fee changed! Was 6583740, now {}", total_fee ); } @@ -7575,8 +7577,8 @@ mod tests { // Assert exact values assert_eq!( - processing_fee, 457440, - "Processing fee changed! Was 457440, now {}", + processing_fee, 508740, + "Processing fee changed! Was 508740, now {}", processing_fee ); assert_eq!( @@ -7585,8 +7587,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6532440, - "Total fee changed! Was 6532440, now {}", + total_fee, 6583740, + "Total fee changed! Was 6583740, now {}", total_fee ); } @@ -7662,8 +7664,8 @@ mod tests { // Assert exact values - 2 inputs should cost more processing than 1 input assert_eq!( - processing_fee, 587800, - "Processing fee changed! Was 587800, now {}", + processing_fee, 639100, + "Processing fee changed! Was 639100, now {}", processing_fee ); assert_eq!( @@ -7672,8 +7674,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6662800, - "Total fee changed! Was 6662800, now {}", + total_fee, 6714100, + "Total fee changed! Was 6714100, now {}", total_fee ); } @@ -7748,8 +7750,8 @@ mod tests { // Assert exact values - 2 outputs should cost more storage than 1 output assert_eq!( - processing_fee, 559820, - "Processing fee changed! Was 559820, now {}", + processing_fee, 611120, + "Processing fee changed! Was 611120, now {}", processing_fee ); assert_eq!( @@ -7758,8 +7760,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 12709820, - "Total fee changed! Was 12709820, now {}", + total_fee, 12761120, + "Total fee changed! Was 12761120, now {}", total_fee ); } @@ -7842,8 +7844,8 @@ mod tests { // Assert exact values - P2SH with 2 signatures assert_eq!( - processing_fee, 477440, - "Processing fee changed! Was 477440, now {}", + processing_fee, 528740, + "Processing fee changed! Was 528740, now {}", processing_fee ); assert_eq!( @@ -7852,8 +7854,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6552440, - "Total fee changed! Was 6552440, now {}", + total_fee, 6603740, + "Total fee changed! Was 6603740, now {}", total_fee ); } @@ -7936,8 +7938,8 @@ mod tests { // Assert exact values - 3-of-5 multisig assert_eq!( - processing_fee, 492440, - "Processing fee changed! Was 492440, now {}", + processing_fee, 543740, + "Processing fee changed! Was 543740, now {}", processing_fee ); assert_eq!( @@ -7946,8 +7948,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6567440, - "Total fee changed! Was 6567440, now {}", + total_fee, 6618740, + "Total fee changed! Was 6618740, now {}", total_fee ); } @@ -8021,17 +8023,17 @@ mod tests { processing_fee, storage_fee, total_fee ); - // Base processing fee is 457440, with user_fee_increase=100 it should be higher + // Base processing fee is 508740, with user_fee_increase=100 it should be higher // The exact formula depends on implementation assert!( - processing_fee > 457440, + processing_fee > 508740, "Processing fee with user_fee_increase should be higher than base" ); // Assert exact values assert_eq!( - processing_fee, 914880, - "Processing fee changed! Was 914880, now {}", + processing_fee, 1017480, + "Processing fee changed! Was 1017480, now {}", processing_fee ); assert_eq!( @@ -8040,8 +8042,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 6989880, - "Total fee changed! Was 6989880, now {}", + total_fee, 7092480, + "Total fee changed! Was 7092480, now {}", total_fee ); } @@ -8119,16 +8121,16 @@ mod tests { processing_fee, storage_fee, total_fee ); - // 16 inputs should have higher processing fee than 1 input (base is ~457K) + // 16 inputs should have higher processing fee than 1 input (base is ~508K) assert!( - processing_fee > 457440, + processing_fee > 508740, "16 inputs should have processing fee > single input" ); // Assert exact values assert_eq!( - processing_fee, 2958200, - "Processing fee changed! Was 2958200, now {}", + processing_fee, 3009500, + "Processing fee changed! Was 3009500, now {}", processing_fee ); assert_eq!( @@ -8137,8 +8139,8 @@ mod tests { storage_fee ); assert_eq!( - total_fee, 9033200, - "Total fee changed! Was 9033200, now {}", + total_fee, 9084500, + "Total fee changed! Was 9084500, now {}", total_fee ); } @@ -8273,18 +8275,263 @@ mod tests { // Assert exact values for new address assert_eq!( - total_fee_new, 6532440, - "Total fee to new address changed! Was 6532440, now {}", + total_fee_new, 6583740, + "Total fee to new address changed! Was 6583740, now {}", total_fee_new ); // Assert exact values for existing address (much cheaper - only updates balance) assert_eq!( - total_fee_existing, 445920, - "Total fee to existing address changed! Was 445920, now {}", + total_fee_existing, 497220, + "Total fee to existing address changed! Was 497220, now {}", total_fee_existing ); } + + // ========================================== + // PROTOCOL V11 FEE REGRESSION TESTS + // These ensure fees match pre-shielded-pool values when running + // under protocol v11 (which uses create_initial_state_structure v2, + // without the shielded pool trees that increase Merk propagation costs). + // ========================================== + + #[test] + fn test_fee_simple_p2pkh_1_input_1_output_deduct_from_input_in_protocol_v11() { + let platform_version = PlatformVersion::get(11).unwrap(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_initial_protocol_version(11) + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + let output_address = create_platform_address(99); + + let transfer_amount = dash_to_credits!(0.5); + let initial_balance = dash_to_credits!(1.0); + setup_address_with_balance(&mut platform, input_address, 0, initial_balance); + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, transfer_amount)); + let mut outputs = BTreeMap::new(); + outputs.insert(output_address, transfer_amount); + + let transition = AddressFundsTransferTransitionV0::try_from_inputs_with_signer( + inputs, + outputs, + vec![AddressFundsFeeStrategyStep::DeductFromInput(0)], + &signer, + 0, + platform_version, + ) + .expect("should create transition"); + + let transition_bytes = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![transition_bytes], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process"); + + let (processing_fee, storage_fee, total_fee) = + extract_fees(&processing_result.execution_results()[0]); + + // Protocol v11 does not have shielded pool trees, so fees are lower + assert_eq!( + processing_fee, 457440, + "Protocol v11 processing fee changed! Was 457440, now {}", + processing_fee + ); + assert_eq!( + storage_fee, 6075000, + "Protocol v11 storage fee changed! Was 6075000, now {}", + storage_fee + ); + assert_eq!( + total_fee, 6532440, + "Protocol v11 total fee changed! Was 6532440, now {}", + total_fee + ); + } + + #[test] + fn test_fee_simple_p2pkh_1_input_1_output_reduce_output_in_protocol_v11() { + let platform_version = PlatformVersion::get(11).unwrap(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_initial_protocol_version(11) + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + let output_address = create_platform_address(99); + + let transfer_amount = dash_to_credits!(0.5); + let initial_balance = dash_to_credits!(1.0); + setup_address_with_balance(&mut platform, input_address, 0, initial_balance); + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, transfer_amount)); + let mut outputs = BTreeMap::new(); + outputs.insert(output_address, transfer_amount); + + let transition = AddressFundsTransferTransitionV0::try_from_inputs_with_signer( + inputs, + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], + &signer, + 0, + platform_version, + ) + .expect("should create transition"); + + let transition_bytes = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![transition_bytes], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process"); + + let (processing_fee, storage_fee, total_fee) = + extract_fees(&processing_result.execution_results()[0]); + + // Protocol v11 does not have shielded pool trees, so fees are lower + assert_eq!( + processing_fee, 457440, + "Protocol v11 processing fee changed! Was 457440, now {}", + processing_fee + ); + assert_eq!( + storage_fee, 6075000, + "Protocol v11 storage fee changed! Was 6075000, now {}", + storage_fee + ); + assert_eq!( + total_fee, 6532440, + "Protocol v11 total fee changed! Was 6532440, now {}", + total_fee + ); + } + + #[test] + fn test_fee_with_user_fee_increase_in_protocol_v11() { + let platform_version = PlatformVersion::get(11).unwrap(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_initial_protocol_version(11) + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + let output_address = create_platform_address(99); + + let amount = dash_to_credits!(1.0); + setup_address_with_balance(&mut platform, input_address, 0, amount * 2); + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, amount)); + let mut outputs = BTreeMap::new(); + outputs.insert(output_address, amount); + + let user_fee_increase = 100; + + let transition = AddressFundsTransferTransitionV0::try_from_inputs_with_signer( + inputs, + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], + &signer, + user_fee_increase, + platform_version, + ) + .expect("should create transition"); + + let transition_bytes = transition.serialize_to_bytes().unwrap(); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![transition_bytes], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process"); + + let (processing_fee, storage_fee, total_fee) = + extract_fees(&processing_result.execution_results()[0]); + + // Protocol v11 does not have shielded pool trees + assert_eq!( + processing_fee, 914880, + "Protocol v11 processing fee changed! Was 914880, now {}", + processing_fee + ); + assert_eq!( + storage_fee, 6075000, + "Protocol v11 storage fee changed! Was 6075000, now {}", + storage_fee + ); + assert_eq!( + total_fee, 6989880, + "Protocol v11 total fee changed! Was 6989880, now {}", + total_fee + ); + } } mod security_edge_cases { diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 42f2c41c5aa..5c081c3dec8 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/initialization/v3/mod.rs b/packages/rs-drive/src/drive/initialization/v3/mod.rs index 68773d95eab..708115be569 100644 --- a/packages/rs-drive/src/drive/initialization/v3/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v3/mod.rs @@ -85,7 +85,7 @@ impl Drive { batch.add_insert( shielded_credit_pool_path_vec(), vec![SHIELDED_NOTES_KEY], - Element::empty_commitment_tree(SHIELDED_NOTES_CHUNK_POWER), + Element::empty_commitment_tree(SHIELDED_NOTES_CHUNK_POWER)?, ); // 3. Nullifiers tree (ProvableCountTree) diff --git a/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs index b6562116af8..842e6fdd89d 100644 --- a/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs @@ -147,7 +147,7 @@ impl Drive { "amount is over max allowed in Sum Item (i64::Max)", ))); } - let insert_op = QualifiedGroveDbOp::insert_only_op( + let insert_op = QualifiedGroveDbOp::insert_only_known_to_not_already_exist_op( path_holding_total_token_supply_vec, token_id.to_vec(), SumItem(amount as i64, None), diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 24893e075fa..35d311401d0 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -555,7 +555,7 @@ pub trait LowLevelDriveOperationTreeTypeConverter { path: Vec>, key: Vec, storage_flags: Option<&StorageFlags>, - ) -> LowLevelDriveOperation; + ) -> Result; } impl LowLevelDriveOperationTreeTypeConverter for TreeType { @@ -565,7 +565,7 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { path: Vec>, key: Vec, storage_flags: Option<&StorageFlags>, - ) -> LowLevelDriveOperation { + ) -> Result { let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags()); let element = match self { TreeType::NormalTree => Element::empty_tree_with_flags(element_flags), @@ -580,18 +580,18 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { Element::empty_provable_count_sum_tree_with_flags(element_flags) } TreeType::CommitmentTree(chunk_power) => { - Element::empty_commitment_tree_with_flags(*chunk_power, element_flags) + Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)? } TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags), TreeType::BulkAppendTree(chunk_power) => { - Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags) + Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)? } TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => { Element::empty_dense_tree_with_flags(*chunk_power, element_flags) } }; - LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element) + Ok(LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element)) } } diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs index a3ec63c304f..75fb95ebb2e 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs @@ -8,7 +8,7 @@ use crate::state_transition_action::shielded::ShieldedActionNote; use crate::util::batch::drive_op_batch::ShieldedPoolOperationType; use crate::util::batch::DriveOperation; -/// Insert each nullifier (InsertOnly to prevent double-spend). +/// Insert each nullifier (known to not exist after validation). pub(super) fn insert_nullifiers<'a>( ops: &mut Vec>, notes: &[ShieldedActionNote], diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs index 2c7f8eb9783..7455b22bc5f 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs @@ -24,7 +24,7 @@ impl DriveHighLevelOperationConverter for ShieldedTransferTransitionAction { ShieldedTransferTransitionAction::V0(v0) => { let mut ops: Vec> = Vec::new(); - // 1. Insert each nullifier (InsertOnly to prevent double-spend) + // 1. Insert each nullifier (known to not exist after validation) insert_nullifiers(&mut ops, &v0.notes); // 2. Insert notes into CommitmentTree diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs index a40a7cac0bf..093b0015ef1 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs @@ -25,7 +25,7 @@ impl DriveHighLevelOperationConverter for UnshieldTransitionAction { UnshieldTransitionAction::V0(v0) => { let mut ops: Vec> = Vec::new(); - // 1. Insert each nullifier (InsertOnly to prevent double-spend) + // 1. Insert each nullifier (known to not exist after validation) insert_nullifiers(&mut ops, &v0.notes); // 2. Credit the output address with the unshielded amount diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs index 647ee0e6ba6..3add4d5ee0b 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs @@ -78,11 +78,13 @@ impl DriveLowLevelOperationConverter for ShieldedPoolOperationType { } ShieldedPoolOperationType::InsertNullifier { nullifier } => { let nullifiers_path = shielded_credit_pool_nullifiers_path_vec(); - Ok(vec![GroveOperation(QualifiedGroveDbOp::insert_only_op( - nullifiers_path, - nullifier.to_vec(), - Element::new_item(vec![]), - ))]) + Ok(vec![GroveOperation( + QualifiedGroveDbOp::insert_only_known_to_not_already_exist_op( + nullifiers_path, + nullifier.to_vec(), + Element::new_item(vec![]), + ), + )]) } ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance } => { let pool_path = shielded_credit_pool_path_vec(); diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index be36450e88d..5c95b248bdd 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -341,7 +341,9 @@ impl fmt::Display for GroveDbOpBatch { writeln!(f, " Path: {}", path_string)?; writeln!(f, " Key: {}", key_string)?; match &op.op { - GroveOp::InsertOrReplace { element } | GroveOp::InsertOnly { element } => { + GroveOp::InsertOrReplace { element } + | GroveOp::InsertWithKnownToNotAlreadyExist { element } + | GroveOp::InsertIfNotExists { element, .. } => { let flags = element.get_flags(); let flag_info = match flags { None => "No Flags".to_string(), @@ -694,7 +696,8 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { let op = if matches!( op, &GroveOp::InsertOrReplace { .. } - | &GroveOp::InsertOnly { .. } + | &GroveOp::InsertWithKnownToNotAlreadyExist { .. } + | &GroveOp::InsertIfNotExists { .. } | &GroveOp::Replace { .. } | &GroveOp::Patch { .. } ) { diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs index aa84e0e2646..3f1bd9b0b8b 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs @@ -36,7 +36,7 @@ impl Drive { path.clone(), key.to_vec(), storage_flags, - ); + )?; // we only add the operation if it doesn't already exist in the current batch if let Some(existing_operations) = check_existing_operations { let mut i = 0; @@ -103,7 +103,7 @@ impl Drive { path.clone(), key.to_vec(), storage_flags, - ); + )?; // we only add the operation if it doesn't already exist in the current batch if let Some(existing_operations) = check_existing_operations { let mut i = 0; @@ -168,7 +168,7 @@ impl Drive { path_items, key.to_vec(), storage_flags, - ); + )?; // we only add the operation if it doesn't already exist in the current batch if let Some(existing_operations) = check_existing_operations { let mut i = 0; @@ -233,7 +233,7 @@ impl Drive { path_items, key.to_vec(), storage_flags, - ); + )?; // we only add the operation if it doesn't already exist in the current batch if let Some(existing_operations) = check_existing_operations { let mut i = 0; diff --git a/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs index d7f8a9d0f85..43cf7e0f3a8 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_get_proved_path_query_v1/v0/mod.rs @@ -18,7 +18,7 @@ impl Drive { ) -> Result, Error> { let CostContext { value, cost } = self.grove - .prove_query_v1(path_query, None, &drive_version.grove_version); + .prove_query(path_query, None, &drive_version.grove_version); drive_operations.push(CalculatedCostOperation(cost)); value.map_err(Error::from) } diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs index 2494d17c56c..37f7e6ead1b 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs @@ -28,9 +28,9 @@ impl Drive { TreeType::CountSumTree => Element::empty_count_sum_tree(), TreeType::ProvableCountTree => Element::empty_provable_count_tree(), TreeType::ProvableCountSumTree => Element::empty_provable_count_sum_tree(), - TreeType::CommitmentTree(chunk_power) => Element::empty_commitment_tree(chunk_power), + TreeType::CommitmentTree(chunk_power) => Element::empty_commitment_tree(chunk_power)?, TreeType::MmrTree => Element::empty_mmr_tree(), - TreeType::BulkAppendTree(chunk_power) => Element::empty_bulk_append_tree(chunk_power), + TreeType::BulkAppendTree(chunk_power) => Element::empty_bulk_append_tree(chunk_power)?, TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => { Element::empty_dense_tree(chunk_power) } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 3593be0ef15..324f6d47679 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } [features] mock-versions = [] diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs index f6a0abbc499..2671c39d372 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -20,7 +20,7 @@ use crate::version::drive_versions::{ DriveProveMethodVersions, DriveSavedBlockTransactionsMethodVersions, DriveSystemEstimationCostsMethodVersions, DriveVersion, }; -use grovedb_version::version::v2::GROVE_V2; +use grovedb_version::version::v3::GROVE_V3; /// This was introduced in protocol v12 for shielded transactions. pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { @@ -127,5 +127,5 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, - grove_version: GROVE_V2, + grove_version: GROVE_V3, }; From ea9e63c3f5477e145a8b332658ab4e090a3b884e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 8 Mar 2026 19:57:18 +0700 Subject: [PATCH 14/25] refactor(drive): move per-block nullifier paths to shielded credit pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-block nullifier storage (recent, compacted, expiration) was incorrectly under SavedBlockTransactions. Move paths and code to shielded/nullifiers/ under [AddressBalances, "s", ...] where the shielded pool lives. Also fix feature gates, cost estimation layer count (4→7), and stale doc comments. Co-Authored-By: Claude Opus 4.6 --- .../src/drive/initialization/v3/mod.rs | 27 +++---- .../src/drive/saved_block_transactions/mod.rs | 9 --- .../drive/saved_block_transactions/queries.rs | 64 ---------------- .../src/drive/shielded/estimated_costs.rs | 15 ++-- packages/rs-drive/src/drive/shielded/mod.rs | 4 + .../cleanup_expired_nullifiers/mod.rs | 0 .../cleanup_expired_nullifiers/v0/mod.rs | 9 ++- .../nullifiers}/compact_nullifiers/mod.rs | 0 .../nullifiers}/compact_nullifiers/v0/mod.rs | 16 ++-- .../fetch_compacted_nullifiers/mod.rs | 0 .../fetch_compacted_nullifiers/v0/mod.rs | 9 ++- .../nullifiers}/fetch_nullifiers/mod.rs | 0 .../nullifiers}/fetch_nullifiers/v0/mod.rs | 9 ++- .../src/drive/shielded/nullifiers/mod.rs | 16 ++++ .../src/drive/shielded/nullifiers/queries.rs | 74 +++++++++++++++++++ .../nullifiers}/store_nullifiers/mod.rs | 2 +- .../nullifiers}/store_nullifiers/v0/mod.rs | 21 +++--- .../v0/mod.rs | 24 +++--- .../verify_recent_nullifier_changes/v0/mod.rs | 9 +-- 19 files changed, 167 insertions(+), 141 deletions(-) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/cleanup_expired_nullifiers/mod.rs (100%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/cleanup_expired_nullifiers/v0/mod.rs (90%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/compact_nullifiers/mod.rs (100%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/compact_nullifiers/v0/mod.rs (93%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/fetch_compacted_nullifiers/mod.rs (100%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/fetch_compacted_nullifiers/v0/mod.rs (97%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/fetch_nullifiers/mod.rs (100%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/fetch_nullifiers/v0/mod.rs (91%) create mode 100644 packages/rs-drive/src/drive/shielded/nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/nullifiers/queries.rs rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/store_nullifiers/mod.rs (96%) rename packages/rs-drive/src/drive/{saved_block_transactions => shielded/nullifiers}/store_nullifiers/v0/mod.rs (89%) diff --git a/packages/rs-drive/src/drive/initialization/v3/mod.rs b/packages/rs-drive/src/drive/initialization/v3/mod.rs index 708115be569..06f4bf5727b 100644 --- a/packages/rs-drive/src/drive/initialization/v3/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v3/mod.rs @@ -1,8 +1,6 @@ //! Drive Initialization -use crate::drive::saved_block_transactions::{ - COMPACTED_NULLIFIERS_KEY_U8, NULLIFIERS_EXPIRATION_TIME_KEY_U8, NULLIFIERS_KEY_U8, -}; +use crate::drive::shielded::nullifiers::queries::*; use crate::drive::shielded::paths::*; use crate::drive::{Drive, RootTree}; use crate::error::Error; @@ -109,29 +107,26 @@ impl Drive { Element::empty_tree(), ); - // 6. Nullifiers CountSumTree under SavedBlockTransactions for storing - // per-block nullifier lists. Each item is an ItemWithSumItem - // (serialized Vec<[u8;32]> + nullifier count as sum). + // 6. Per-block nullifiers CountSumTree under shielded credit pool. + // Each item is an ItemWithSumItem (serialized Vec<[u8;32]> + nullifier count as sum). batch.add_insert( - Self::saved_block_transactions_path(), - vec![NULLIFIERS_KEY_U8], + shielded_credit_pool_path_vec(), + vec![SHIELDED_RECENT_NULLIFIERS_KEY_U8], Element::empty_count_sum_tree(), ); - // 7. Compacted nullifiers NormalTree under SavedBlockTransactions for storing - // compacted/aggregated nullifier lists spanning multiple blocks. + // 7. Compacted nullifiers NormalTree under shielded credit pool. // Key: (start_block, end_block) as 16 bytes, Value: serialized Vec<[u8;32]> batch.add_insert( - Self::saved_block_transactions_path(), - vec![COMPACTED_NULLIFIERS_KEY_U8], + shielded_credit_pool_path_vec(), + vec![SHIELDED_COMPACTED_NULLIFIERS_KEY_U8], Element::empty_tree(), ); - // 8. Nullifiers expiration time NormalTree under SavedBlockTransactions for storing - // expiration timestamps for compacted nullifier ranges. + // 8. Nullifiers expiration time NormalTree under shielded credit pool. batch.add_insert( - Self::saved_block_transactions_path(), - vec![NULLIFIERS_EXPIRATION_TIME_KEY_U8], + shielded_credit_pool_path_vec(), + vec![SHIELDED_NULLIFIERS_EXPIRATION_TIME_KEY_U8], Element::empty_tree(), ); diff --git a/packages/rs-drive/src/drive/saved_block_transactions/mod.rs b/packages/rs-drive/src/drive/saved_block_transactions/mod.rs index 0e82340f0af..5b1a1652331 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/mod.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/mod.rs @@ -5,15 +5,6 @@ mod fetch_compacted_address_balances; mod queries; mod store_address_balances; -mod cleanup_expired_nullifiers; -/// Compaction of per-block nullifier entries into range-keyed compacted entries -pub mod compact_nullifiers; -mod fetch_compacted_nullifiers; -mod fetch_nullifiers; -mod store_nullifiers; - pub use fetch_address_balances::AddressBalanceChangesPerBlock; pub use fetch_compacted_address_balances::CompactedAddressBalanceChanges; -pub use fetch_compacted_nullifiers::CompactedNullifierChanges; -pub use fetch_nullifiers::NullifierChangesPerBlock; pub use queries::*; diff --git a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs index 082056600fb..ee4eb07ab76 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs @@ -19,23 +19,6 @@ pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY: &[u8; 1] = b"e"; /// The subtree key for compacted addresses expiration time storage as u8 pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8: u8 = b'e'; -/// The subtree key for nullifiers storage (per-block nullifier lists) -pub const NULLIFIERS_KEY: &[u8; 1] = b"n"; - -/// The subtree key for nullifiers storage as u8 -pub const NULLIFIERS_KEY_U8: u8 = b'n'; - -/// The subtree key for compacted nullifiers storage -pub const COMPACTED_NULLIFIERS_KEY: &[u8; 1] = b"o"; - -/// The subtree key for compacted nullifiers storage as u8 -pub const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; - -/// The subtree key for nullifiers expiration time storage -pub const NULLIFIERS_EXPIRATION_TIME_KEY: &[u8; 1] = b"p"; - -/// The subtree key for nullifiers expiration time storage as u8 -pub const NULLIFIERS_EXPIRATION_TIME_KEY_U8: u8 = b'p'; impl Drive { /// Path to saved block transactions storage. @@ -91,51 +74,4 @@ impl Drive { ] } - /// Path to nullifiers under saved block transactions (Vec version). - pub fn saved_block_transactions_nullifiers_path_vec() -> Vec> { - vec![ - vec![RootTree::SavedBlockTransactions as u8], - vec![NULLIFIERS_KEY_U8], - ] - } - - /// Path to nullifiers under saved block transactions. - pub fn saved_block_transactions_nullifiers_path() -> [&'static [u8]; 2] { - [ - Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), - &[NULLIFIERS_KEY_U8], - ] - } - - /// Path to compacted nullifiers under saved block transactions (Vec version). - pub fn saved_compacted_block_transactions_nullifiers_path_vec() -> Vec> { - vec![ - vec![RootTree::SavedBlockTransactions as u8], - vec![COMPACTED_NULLIFIERS_KEY_U8], - ] - } - - /// Path to compacted nullifiers under saved block transactions. - pub fn saved_compacted_block_transactions_nullifiers_path() -> [&'static [u8]; 2] { - [ - Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), - &[COMPACTED_NULLIFIERS_KEY_U8], - ] - } - - /// Path to nullifiers expiration time under saved block transactions (Vec version). - pub fn saved_nullifiers_expiration_time_path_vec() -> Vec> { - vec![ - vec![RootTree::SavedBlockTransactions as u8], - vec![NULLIFIERS_EXPIRATION_TIME_KEY_U8], - ] - } - - /// Path to nullifiers expiration time under saved block transactions. - pub fn saved_nullifiers_expiration_time_path() -> [&'static [u8]; 2] { - [ - Into::<&[u8; 1]>::into(RootTree::SavedBlockTransactions), - &[NULLIFIERS_EXPIRATION_TIME_KEY_U8], - ] - } } diff --git a/packages/rs-drive/src/drive/shielded/estimated_costs.rs b/packages/rs-drive/src/drive/shielded/estimated_costs.rs index 2e3107063f3..a44133b8a9a 100644 --- a/packages/rs-drive/src/drive/shielded/estimated_costs.rs +++ b/packages/rs-drive/src/drive/shielded/estimated_costs.rs @@ -75,25 +75,26 @@ impl Drive { ); // Shielded credit pool: [AddressBalances, "s"] - // SumTree containing: notes (CommitmentTree), nullifiers (NormalTree), - // total balance (SumItem), anchors (NormalTree) + // SumTree containing: notes (CommitmentTree), permanent nullifiers (ProvableCountTree), + // total balance (SumItem), anchors (NormalTree), recent nullifiers (CountSumTree), + // compacted nullifiers (NormalTree), expiration time (NormalTree) estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(shielded_credit_pool_path()), EstimatedLayerInformation { tree_type: TreeType::SumTree, - estimated_layer_count: EstimatedLevel(4, false), + estimated_layer_count: EstimatedLevel(7, false), estimated_layer_sizes: Mix { subtrees_size: Some(( 1, SomeSumTrees { sum_trees_weight: 0, big_sum_trees_weight: 0, - count_trees_weight: 1, // nullifiers (ProvableCountTree) - count_sum_trees_weight: 0, - non_sum_trees_weight: 1, // anchors + count_trees_weight: 1, // permanent nullifiers (ProvableCountTree) + count_sum_trees_weight: 1, // recent nullifiers (CountSumTree) + non_sum_trees_weight: 3, // anchors, compacted nullifiers, expiration time }, None, - 3, // 3 subtrees: notes (CommitmentTree), nullifiers, anchors + 6, // 6 subtrees: notes, permanent nullifiers, anchors, recent nullifiers, compacted nullifiers, expiration time )), items_size: Some((1, 8, None, 1)), // 1 item: total balance references_size: None, diff --git a/packages/rs-drive/src/drive/shielded/mod.rs b/packages/rs-drive/src/drive/shielded/mod.rs index 1515f442dd6..f4306f2d185 100644 --- a/packages/rs-drive/src/drive/shielded/mod.rs +++ b/packages/rs-drive/src/drive/shielded/mod.rs @@ -9,3 +9,7 @@ pub(crate) mod estimated_costs; /// Prove methods for shielded pool queries #[cfg(feature = "server")] pub mod prove; + +/// Per-block nullifier storage for catch-up sync +#[cfg(any(feature = "server", feature = "verify"))] +pub mod nullifiers; diff --git a/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/mod.rs similarity index 100% rename from packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/mod.rs diff --git a/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs similarity index 90% rename from packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs index e9a48f2e051..3d521a655d2 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/cleanup_expired_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs @@ -1,3 +1,6 @@ +use crate::drive::shielded::nullifiers::queries::{ + shielded_compacted_nullifiers_path_vec, shielded_nullifiers_expiration_time_path_vec, +}; use crate::drive::Drive; use crate::error::Error; use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; @@ -12,13 +15,13 @@ impl Drive { /// /// Queries for all expiration entries with time <= current_block_time_ms, /// then deletes the corresponding compacted entries and the expiration entries. - pub(super) fn cleanup_expired_nullifiers_v0( + pub(in crate::drive) fn cleanup_expired_nullifiers_v0( &self, current_block_time_ms: u64, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let expiration_path = Self::saved_nullifiers_expiration_time_path_vec(); + let expiration_path = shielded_nullifiers_expiration_time_path_vec(); // Query all entries with expiration time <= current_block_time_ms let mut query = Query::new(); @@ -51,7 +54,7 @@ impl Drive { let mut batch = GroveDbOpBatch::new(); let mut total_cleaned = 0usize; - let compacted_path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + let compacted_path = shielded_compacted_nullifiers_path_vec(); for (expiration_key, element) in key_elements { // Get the vec of block ranges from the element diff --git a/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/mod.rs similarity index 100% rename from packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/mod.rs diff --git a/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs similarity index 93% rename from packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs index 849c6d7d037..0be363fbef0 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/compact_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs @@ -1,4 +1,8 @@ -use crate::drive::saved_block_transactions::compact_nullifiers::ONE_WEEK_IN_MS; +use super::ONE_WEEK_IN_MS; +use crate::drive::shielded::nullifiers::queries::{ + shielded_compacted_nullifiers_path_vec, shielded_nullifiers_expiration_time_path, + shielded_nullifiers_expiration_time_path_vec, shielded_recent_nullifiers_path_vec, +}; use crate::drive::Drive; use crate::error::Error; use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; @@ -22,7 +26,7 @@ impl Drive { /// nullifiers expiration time tree with the same (start_block, end_block) key. /// /// Returns the range of blocks that were compacted (start_block, end_block). - pub(super) fn compact_nullifiers_with_current_block_v0( + pub(in crate::drive) fn compact_nullifiers_with_current_block_v0( &self, current_nullifiers: &[[u8; 32]], current_block_height: u64, @@ -30,7 +34,7 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result<(u64, u64), Error> { - let path = Self::saved_block_transactions_nullifiers_path_vec(); + let path = shielded_recent_nullifiers_path_vec(); // Query all entries from the nullifiers tree let mut query = Query::new(); @@ -129,7 +133,7 @@ impl Drive { // Insert the compacted entry as a plain Item (not ItemWithSumItem) batch.add_insert( - Self::saved_compacted_block_transactions_nullifiers_path_vec(), + shielded_compacted_nullifiers_path_vec(), compacted_key.clone(), Element::new_item(serialized), ); @@ -140,7 +144,7 @@ impl Drive { // Check if an entry with this expiration time already exists // If so, we need to append to the existing vec of block ranges - let expiration_path = Self::saved_nullifiers_expiration_time_path(); + let expiration_path = shielded_nullifiers_expiration_time_path(); let mut drive_operations = vec![]; let existing_ranges = self.grove_get_raw_optional( @@ -181,7 +185,7 @@ impl Drive { // Store in the expiration tree: key = expiration_time, value = vec of (start_block, end_block) batch.add_insert( - Self::saved_nullifiers_expiration_time_path_vec(), + shielded_nullifiers_expiration_time_path_vec(), expiration_key, Element::new_item(expiration_value), ); diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs similarity index 100% rename from packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs similarity index 97% rename from packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs index 2ade9789bda..65b1ae47520 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs @@ -1,3 +1,4 @@ +use crate::drive::shielded::nullifiers::queries::shielded_compacted_nullifiers_path_vec; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -17,14 +18,14 @@ impl Drive { /// from block 505) as well as ranges that start after `start_block_height`. /// /// Returns a vector of (start_block, end_block, nullifiers) tuples. - pub(super) fn fetch_compacted_nullifier_changes_v0( + pub(in crate::drive) fn fetch_compacted_nullifier_changes_v0( &self, start_block_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + let path = shielded_compacted_nullifiers_path_vec(); // Keys are 16 bytes: (start_block, end_block), both big-endian. // We want ranges where end_block >= start_block_height, which includes: @@ -201,14 +202,14 @@ impl Drive { /// 2. Second query (proving): ascending from the found start_block or start_block_height /// /// This ensures the proof covers all relevant ranges efficiently. - pub(super) fn prove_compacted_nullifier_changes_v0( + pub(in crate::drive) fn prove_compacted_nullifier_changes_v0( &self, start_block_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { - let path = Self::saved_compacted_block_transactions_nullifiers_path_vec(); + let path = shielded_compacted_nullifiers_path_vec(); // Step 1: Non-proving descending query to find any range containing start_block_height let mut desc_end_key = Vec::with_capacity(16); diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs similarity index 100% rename from packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs diff --git a/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs similarity index 91% rename from packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs index c44662fa8a3..81d7514374e 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/fetch_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs @@ -1,3 +1,4 @@ +use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -13,14 +14,14 @@ impl Drive { /// /// Retrieves all nullifier change records from `start_height` onwards. /// Returns a vector of (block_height, nullifiers) tuples. - pub(super) fn fetch_recent_nullifier_changes_v0( + pub(in crate::drive) fn fetch_recent_nullifier_changes_v0( &self, start_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let path = Self::saved_block_transactions_nullifiers_path_vec(); + let path = shielded_recent_nullifiers_path_vec(); // Create a range query starting from the specified height let mut query = Query::new(); @@ -76,14 +77,14 @@ impl Drive { } /// Version 0 implementation for proving nullifier changes from a start height. - pub(super) fn prove_recent_nullifier_changes_v0( + pub(in crate::drive) fn prove_recent_nullifier_changes_v0( &self, start_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { - let path = Self::saved_block_transactions_nullifiers_path_vec(); + let path = shielded_recent_nullifiers_path_vec(); // Create a range query starting from the specified height let mut query = Query::new(); diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs new file mode 100644 index 00000000000..e147e2fb424 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs @@ -0,0 +1,16 @@ +/// Fetching compacted nullifier changes +#[cfg(feature = "server")] +pub mod fetch_compacted_nullifiers; +/// Fetching recent per-block nullifier changes +#[cfg(feature = "server")] +pub mod fetch_nullifiers; +#[cfg(feature = "server")] +mod store_nullifiers; +/// Compaction of per-block nullifier entries into range-keyed compacted entries +#[cfg(feature = "server")] +pub mod compact_nullifiers; +#[cfg(feature = "server")] +mod cleanup_expired_nullifiers; +/// Path definitions and constants for per-block nullifier storage +pub mod queries; +pub use queries::*; diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/queries.rs b/packages/rs-drive/src/drive/shielded/nullifiers/queries.rs new file mode 100644 index 00000000000..951d9214e2a --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/nullifiers/queries.rs @@ -0,0 +1,74 @@ +use crate::drive::shielded::paths::SHIELDED_CREDIT_POOL_KEY; +use crate::drive::RootTree; + +/// The subtree key for per-block nullifiers storage (CountSumTree) +pub const SHIELDED_RECENT_NULLIFIERS_KEY: &[u8; 1] = b"n"; + +/// The subtree key for per-block nullifiers storage as u8 +pub const SHIELDED_RECENT_NULLIFIERS_KEY_U8: u8 = b'n'; + +/// The subtree key for compacted nullifiers storage +pub const SHIELDED_COMPACTED_NULLIFIERS_KEY: &[u8; 1] = b"o"; + +/// The subtree key for compacted nullifiers storage as u8 +pub const SHIELDED_COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; + +/// The subtree key for nullifiers expiration time storage +pub const SHIELDED_NULLIFIERS_EXPIRATION_TIME_KEY: &[u8; 1] = b"p"; + +/// The subtree key for nullifiers expiration time storage as u8 +pub const SHIELDED_NULLIFIERS_EXPIRATION_TIME_KEY_U8: u8 = b'p'; + +/// Path to per-block nullifiers: [AddressBalances, "s", "n"] +pub fn shielded_recent_nullifiers_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + SHIELDED_RECENT_NULLIFIERS_KEY, + ] +} + +/// Path to per-block nullifiers as vec: [AddressBalances, "s", "n"] +pub fn shielded_recent_nullifiers_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_RECENT_NULLIFIERS_KEY_U8], + ] +} + +/// Path to compacted nullifiers: [AddressBalances, "s", "o"] +pub fn shielded_compacted_nullifiers_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + SHIELDED_COMPACTED_NULLIFIERS_KEY, + ] +} + +/// Path to compacted nullifiers as vec: [AddressBalances, "s", "o"] +pub fn shielded_compacted_nullifiers_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_COMPACTED_NULLIFIERS_KEY_U8], + ] +} + +/// Path to nullifiers expiration time: [AddressBalances, "s", "p"] +pub fn shielded_nullifiers_expiration_time_path() -> [&'static [u8]; 3] { + [ + Into::<&[u8; 1]>::into(RootTree::AddressBalances), + SHIELDED_CREDIT_POOL_KEY, + SHIELDED_NULLIFIERS_EXPIRATION_TIME_KEY, + ] +} + +/// Path to nullifiers expiration time as vec: [AddressBalances, "s", "p"] +pub fn shielded_nullifiers_expiration_time_path_vec() -> Vec> { + vec![ + vec![RootTree::AddressBalances as u8], + SHIELDED_CREDIT_POOL_KEY.to_vec(), + vec![SHIELDED_NULLIFIERS_EXPIRATION_TIME_KEY_U8], + ] +} diff --git a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/mod.rs similarity index 96% rename from packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/mod.rs index ece1654af51..83cd007b612 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/mod.rs @@ -7,7 +7,7 @@ use grovedb::TransactionArg; use platform_version::version::PlatformVersion; impl Drive { - /// Stores nullifiers for a block in the SavedBlockTransactions tree. + /// Stores nullifiers for a block in the shielded credit pool. /// /// This method serializes the nullifiers using bincode and stores /// them keyed by block height. If compaction thresholds are exceeded, it will diff --git a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs similarity index 89% rename from packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs index 34569f69203..63cf771a133 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/store_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs @@ -1,18 +1,21 @@ -use crate::drive::saved_block_transactions::NULLIFIERS_KEY_U8; +use crate::drive::shielded::nullifiers::queries::{ + shielded_recent_nullifiers_path, SHIELDED_RECENT_NULLIFIERS_KEY_U8, +}; +use crate::drive::shielded::paths::shielded_credit_pool_path; use crate::drive::Drive; use crate::error::Error; use crate::util::grove_operations::DirectQueryType; use dpp::ProtocolError; use grovedb::Element; use grovedb::TransactionArg; -use grovedb_path::SubtreePath; + use platform_version::version::PlatformVersion; impl Drive { /// Version 0 implementation of storing nullifiers for a block. /// /// Serializes the nullifier list using bincode and stores it in the - /// SavedBlockTransactions/Nullifiers count sum tree keyed by block height. + /// shielded credit pool per-block nullifiers count sum tree keyed by block height. /// Each entry is an ItemWithSumItem where: /// - The item contains the serialized nullifiers /// - The sum value is the number of nullifiers @@ -20,7 +23,7 @@ impl Drive { /// Before storing, checks if compaction thresholds are exceeded and triggers /// compaction if necessary. If compaction occurs, the current block's nullifiers /// are included in the compaction rather than stored separately. - pub(super) fn store_nullifiers_for_block_v0( + pub(in crate::drive) fn store_nullifiers_for_block_v0( &self, nullifiers: &[[u8; 32]], block_height: u64, @@ -66,8 +69,8 @@ impl Drive { )) })?; - // Store in the SavedBlockTransactions/Nullifiers count sum tree with block height as key - let path: [&[u8]; 2] = Drive::saved_block_transactions_nullifiers_path(); + // Store in the shielded pool per-block nullifiers count sum tree with block height as key + let path = shielded_recent_nullifiers_path(); // Use block height as the key (big-endian for proper ordering) let key = block_height.to_be_bytes(); @@ -110,13 +113,13 @@ impl Drive { transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result { - let saved_block_tx_path = Self::saved_block_transactions_path(); + let pool_path = shielded_credit_pool_path(); // Get the count sum tree element to check current count and sum let mut drive_operations = vec![]; let tree_element = self.grove_get_raw( - SubtreePath::from(saved_block_tx_path.as_slice()), - &[NULLIFIERS_KEY_U8], + (&pool_path).into(), + &[SHIELDED_RECENT_NULLIFIERS_KEY_U8], DirectQueryType::StatefulDirectQuery, transaction, &mut drive_operations, diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index 511d832fa0e..4ceef91696d 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -1,5 +1,7 @@ -/// The subtree key for compacted nullifiers storage as u8 (must match saved_block_transactions::COMPACTED_NULLIFIERS_KEY_U8) -const COMPACTED_NULLIFIERS_KEY_U8: u8 = b'o'; +use crate::drive::shielded::nullifiers::queries::{ + shielded_compacted_nullifiers_path_vec, SHIELDED_COMPACTED_NULLIFIERS_KEY_U8, +}; +use crate::drive::shielded::paths::SHIELDED_CREDIT_POOL_KEY_U8; use crate::drive::Drive; use crate::drive::RootTree; use crate::error::proof::ProofError; @@ -70,9 +72,10 @@ impl Drive { })?; // Navigate to the compacted nullifiers layer - // Path: SavedBlockTransactions ('$' = 0x24) -> CompactedNullifiers ('o' = 0x6f) - let saved_block_key = vec![RootTree::SavedBlockTransactions as u8]; - let compacted_key = vec![COMPACTED_NULLIFIERS_KEY_U8]; + // Path: AddressBalances -> SHIELDED_CREDIT_POOL_KEY -> CompactedNullifiers ('o') + let address_balances_key = vec![RootTree::AddressBalances as u8]; + let pool_key = vec![SHIELDED_CREDIT_POOL_KEY_U8]; + let compacted_key = vec![SHIELDED_COMPACTED_NULLIFIERS_KEY_U8]; // Extract KV entries from the compacted layer's merk proof to find // if there's a containing range for start_block_height. @@ -83,7 +86,8 @@ impl Drive { let compacted_layer = v0 .root_layer .lower_layers - .get(&saved_block_key) + .get(&address_balances_key) + .and_then(|layer| layer.lower_layers.get(&pool_key)) .and_then(|layer| layer.lower_layers.get(&compacted_key)); compacted_layer .map(|layer| extract_kv_entries_from_merk_proof(&layer.merk_proof)) @@ -94,7 +98,8 @@ impl Drive { let compacted_layer = v1 .root_layer .lower_layers - .get(&saved_block_key) + .get(&address_balances_key) + .and_then(|layer| layer.lower_layers.get(&pool_key)) .and_then(|layer| layer.lower_layers.get(&compacted_key)); compacted_layer .map(|layer| match &layer.merk_proof { @@ -139,10 +144,7 @@ impl Drive { }); // Verify the proof and get results using subset query - let path = vec![ - vec![RootTree::SavedBlockTransactions as u8], - vec![COMPACTED_NULLIFIERS_KEY_U8], - ]; + let path = shielded_compacted_nullifiers_path_vec(); let mut query = Query::new(); query.insert_range_from(start_key..); diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs index 2f71970a239..9a6c9764fed 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -1,7 +1,5 @@ -/// The subtree key for nullifiers storage as u8 (must match saved_block_transactions::NULLIFIERS_KEY_U8) -const NULLIFIERS_KEY_U8: u8 = b'n'; +use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; use crate::drive::Drive; -use crate::drive::RootTree; use crate::error::proof::ProofError; use crate::error::Error; use crate::verify::RootHash; @@ -22,10 +20,7 @@ impl Drive { verify_subset_of_proof: bool, platform_version: &PlatformVersion, ) -> Result<(RootHash, VerifiedNullifierChangesPerBlock), Error> { - let path = vec![ - vec![RootTree::SavedBlockTransactions as u8], - vec![NULLIFIERS_KEY_U8], - ]; + let path = shielded_recent_nullifiers_path_vec(); let config = bincode::config::standard() .with_big_endian() From f2b5338712b5c6579ba7bf7a21b09f30836b068c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 11:05:18 +0700 Subject: [PATCH 15/25] refactor(drive): extract shielded pool ops into versioned functions and improve naming - Rename cleanup_expired_nullifiers to cleanup_expired_nullifier_compactions (the function purges expired sync data, not the permanent nullifiers) - Extract inline grove ops (InsertNote, InsertNullifier, UpdateTotalBalance) into versioned Drive methods: insert_note_op, insert_nullifiers, update_total_balance_op - Merge InsertNullifier + StoreNullifiersForBlock into single InsertNullifiers operation that handles both permanent tree insertion and per-block sync storage Co-Authored-By: Claude Opus 4.6 --- .../src/address_funds/platform_address.rs | 18 ++-- packages/rs-dpp/src/shielded/builder/mod.rs | 16 +++- .../src/shielded/builder/shielded_transfer.rs | 14 ++-- .../rs-dpp/src/shielded/builder/unshield.rs | 6 +- .../drive/saved_block_transactions/queries.rs | 2 - .../src/drive/shielded/insert_note/mod.rs | 36 ++++++++ .../src/drive/shielded/insert_note/v0/mod.rs | 29 +++++++ .../drive/shielded/insert_nullifiers/mod.rs | 46 ++++++++++ .../shielded/insert_nullifiers/v0/mod.rs | 52 ++++++++++++ packages/rs-drive/src/drive/shielded/mod.rs | 12 +++ .../mod.rs | 8 +- .../v0/mod.rs | 2 +- .../src/drive/shielded/nullifiers/mod.rs | 14 ++-- .../shielded/update_total_balance/mod.rs | 29 +++++++ .../shielded/update_total_balance/v0/mod.rs | 32 +++++++ packages/rs-drive/src/fees/op.rs | 4 +- .../shielded/mod.rs | 23 ++--- .../shielded/shielded_transfer_transition.rs | 5 +- .../shielded_withdrawal_transition.rs | 5 +- .../shielded/unshield_transition.rs | 5 +- .../system/penalize_shielded_pool.rs | 6 +- .../src/util/batch/drive_op_batch/shielded.rs | 83 ++++--------------- .../v0/mod.rs | 8 +- .../v0/mod.rs | 3 +- .../drive_group_method_versions/mod.rs | 3 + .../src/version/drive_versions/mod.rs | 2 +- .../src/version/drive_versions/v1.rs | 5 +- .../src/version/drive_versions/v2.rs | 5 +- .../src/version/drive_versions/v3.rs | 5 +- .../src/version/drive_versions/v4.rs | 5 +- .../src/version/drive_versions/v5.rs | 5 +- .../src/version/drive_versions/v6.rs | 5 +- .../src/version/drive_versions/v7.rs | 5 +- .../src/version/mocks/v2_test.rs | 5 +- packages/rs-platform-wallet-ffi/src/types.rs | 5 +- 35 files changed, 359 insertions(+), 149 deletions(-) create mode 100644 packages/rs-drive/src/drive/shielded/insert_note/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/insert_note/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/insert_nullifiers/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/insert_nullifiers/v0/mod.rs rename packages/rs-drive/src/drive/shielded/nullifiers/{cleanup_expired_nullifiers => cleanup_expired_nullifier_compactions}/mod.rs (85%) rename packages/rs-drive/src/drive/shielded/nullifiers/{cleanup_expired_nullifiers => cleanup_expired_nullifier_compactions}/v0/mod.rs (98%) create mode 100644 packages/rs-drive/src/drive/shielded/update_total_balance/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/update_total_balance/v0/mod.rs diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 1891c707368..066e30c3639 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -307,7 +307,10 @@ impl PlatformAddress { pubkey_hash, ) .map_err(|e| { - ProtocolError::AddressWitnessError(format!("P2PKH signature verification failed: {}", e)) + ProtocolError::AddressWitnessError(format!( + "P2PKH signature verification failed: {}", + e + )) })?; Ok(AddressWitnessVerificationOperations::for_p2pkh( @@ -368,7 +371,10 @@ impl PlatformAddress { valid_signatures[sig_idx].as_slice(), ) .map_err(|e| { - ProtocolError::AddressWitnessError(format!("Invalid signature format: {}", e)) + ProtocolError::AddressWitnessError(format!( + "Invalid signature format: {}", + e + )) })?; let pub_key = PublicKey::from_slice(&pubkeys[pubkey_idx]).map_err(|e| { @@ -402,9 +408,11 @@ impl PlatformAddress { "P2PKH address requires P2pkh witness, got P2sh".to_string(), )) } - (PlatformAddress::P2sh(_), AddressWitness::P2pkh { .. }) => Err( - ProtocolError::AddressWitnessError("P2SH address requires P2sh witness, got P2pkh".to_string()), - ), + (PlatformAddress::P2sh(_), AddressWitness::P2pkh { .. }) => { + Err(ProtocolError::AddressWitnessError( + "P2SH address requires P2sh witness, got P2pkh".to_string(), + )) + } } } diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs index a8a25c2b45f..dd43be3bbe7 100644 --- a/packages/rs-dpp/src/shielded/builder/mod.rs +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -185,7 +185,9 @@ pub(crate) fn build_spend_bundle( for spend in spends { builder .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)))?; + .map_err(|e| { + ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)) + })?; } builder @@ -218,18 +220,24 @@ pub(crate) fn prove_and_sign_bundle( let (unauthorized, _) = builder .build::(&mut rng) .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to build bundle: {:?}", e)))? - .ok_or_else(|| ProtocolError::ShieldedBuildError("bundle was empty after build".to_string()))?; + .ok_or_else(|| { + ProtocolError::ShieldedBuildError("bundle was empty after build".to_string()) + })?; let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); let proven = unauthorized .create_proof(prover.proving_key(), &mut rng) - .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to create proof: {:?}", e)))?; + .map_err(|e| { + ProtocolError::ShieldedBuildError(format!("failed to create proof: {:?}", e)) + })?; proven .apply_signatures(rng, sighash, signing_keys) - .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to apply signatures: {:?}", e))) + .map_err(|e| { + ProtocolError::ShieldedBuildError(format!("failed to apply signatures: {:?}", e)) + }) } /// Shared test utilities for builder tests. diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs index 0207a862088..40ecd870635 100644 --- a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -70,9 +70,9 @@ pub fn build_shielded_transfer_transition( None => min_fee, }; - let required = transfer_amount - .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::ShieldedBuildError("fee + transfer_amount overflows u64".to_string()))?; + let required = transfer_amount.checked_add(effective_fee).ok_or_else(|| { + ProtocolError::ShieldedBuildError("fee + transfer_amount overflows u64".to_string()) + })?; if required > total_spent { return Err(ProtocolError::ShieldedBuildError(format!( "transfer amount {} + fee {} = {} exceeds total spendable value {}", @@ -89,7 +89,9 @@ pub fn build_shielded_transfer_transition( for spend in spends { builder .add_spend(fvk.clone(), spend.note, spend.merkle_path) - .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)))?; + .map_err(|e| { + ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e)) + })?; } // Primary output to recipient @@ -112,7 +114,9 @@ pub fn build_shielded_transfer_transition( NoteValue::from_raw(change_amount), [0u8; 36], ) - .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add change output: {:?}", e)))?; + .map_err(|e| { + ProtocolError::ShieldedBuildError(format!("failed to add change output: {:?}", e)) + })?; } // ShieldedTransfer has no extra_data in sighash diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs index 12a1f8ed9cc..82cf86ebd56 100644 --- a/packages/rs-dpp/src/shielded/builder/unshield.rs +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -68,9 +68,9 @@ pub fn build_unshield_transition( None => min_fee, }; - let required = unshield_amount - .checked_add(effective_fee) - .ok_or_else(|| ProtocolError::ShieldedBuildError("fee + unshield_amount overflows u64".to_string()))?; + let required = unshield_amount.checked_add(effective_fee).ok_or_else(|| { + ProtocolError::ShieldedBuildError("fee + unshield_amount overflows u64".to_string()) + })?; if required > total_spent { return Err(ProtocolError::ShieldedBuildError(format!( "unshield amount {} + fee {} = {} exceeds total spendable value {}", diff --git a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs index ee4eb07ab76..2b8556805d8 100644 --- a/packages/rs-drive/src/drive/saved_block_transactions/queries.rs +++ b/packages/rs-drive/src/drive/saved_block_transactions/queries.rs @@ -19,7 +19,6 @@ pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY: &[u8; 1] = b"e"; /// The subtree key for compacted addresses expiration time storage as u8 pub const COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8: u8 = b'e'; - impl Drive { /// Path to saved block transactions storage. pub fn saved_block_transactions_path() -> Vec> { @@ -73,5 +72,4 @@ impl Drive { &[COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8], ] } - } diff --git a/packages/rs-drive/src/drive/shielded/insert_note/mod.rs b/packages/rs-drive/src/drive/shielded/insert_note/mod.rs new file mode 100644 index 00000000000..f3d154e7b24 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/insert_note/mod.rs @@ -0,0 +1,36 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::version::PlatformVersion; + +impl Drive { + /// Constructs the low-level drive operations to insert a note into the + /// shielded pool commitment tree. + /// + /// This appends the note commitment (cmx) to the commitment tree frontier + /// and stores cmx || nullifier || encrypted_note as the item value. + /// + /// # Parameters + /// - `nullifier`: The 32-byte nullifier (rho) of the spent note in this action + /// - `cmx`: The 32-byte note commitment + /// - `encrypted_note`: The encrypted note payload (216 bytes) + /// - `platform_version`: The platform version for dispatch + pub fn insert_note_op( + nullifier: [u8; 32], + cmx: [u8; 32], + encrypted_note: Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version.drive.methods.shielded.insert_note { + 0 => Self::insert_note_op_v0(nullifier, cmx, encrypted_note), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "insert_note_op".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/insert_note/v0/mod.rs b/packages/rs-drive/src/drive/shielded/insert_note/v0/mod.rs new file mode 100644 index 00000000000..ebdc1663adf --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/insert_note/v0/mod.rs @@ -0,0 +1,29 @@ +use crate::drive::shielded::paths::shielded_credit_pool_notes_path_vec; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::GroveOperation; +use grovedb::batch::QualifiedGroveDbOp; + +impl Drive { + /// Version 0 implementation of constructing a note insertion operation. + /// + /// Creates a `commitment_tree_insert_op` that appends the note commitment (cmx) + /// to the commitment tree frontier and stores cmx || nullifier || encrypted_note + /// as the item value. + pub(in crate::drive) fn insert_note_op_v0( + nullifier: [u8; 32], + cmx: [u8; 32], + encrypted_note: Vec, + ) -> Result, Error> { + let notes_path = shielded_credit_pool_notes_path_vec(); + Ok(vec![GroveOperation( + QualifiedGroveDbOp::commitment_tree_insert_op( + notes_path, + cmx, + nullifier, + encrypted_note, + ), + )]) + } +} diff --git a/packages/rs-drive/src/drive/shielded/insert_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/insert_nullifiers/mod.rs new file mode 100644 index 00000000000..c5cfabe89bd --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/insert_nullifiers/mod.rs @@ -0,0 +1,46 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Inserts nullifiers into the shielded pool. + /// + /// This performs two operations: + /// 1. Inserts each nullifier into the permanent nullifiers tree (double-spend prevention) + /// 2. Stores all nullifiers into per-block sync storage (for catch-up RPCs) + /// + /// # Parameters + /// - `nullifiers`: The 32-byte nullifiers to insert + /// - `block_height`: The current block height (for sync storage) + /// - `block_time_ms`: The current block time in milliseconds (for sync storage expiration) + /// - `transaction`: The database transaction + /// - `platform_version`: The platform version for dispatch + pub fn insert_nullifiers( + &self, + nullifiers: &[[u8; 32]], + block_height: u64, + block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version.drive.methods.shielded.insert_nullifiers { + 0 => self.insert_nullifiers_v0( + nullifiers, + block_height, + block_time_ms, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "insert_nullifiers".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/insert_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/insert_nullifiers/v0/mod.rs new file mode 100644 index 00000000000..371f3d1434b --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/insert_nullifiers/v0/mod.rs @@ -0,0 +1,52 @@ +use crate::drive::shielded::paths::shielded_credit_pool_nullifiers_path_vec; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::GroveOperation; +use grovedb::batch::QualifiedGroveDbOp; +use grovedb::{Element, TransactionArg}; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation of inserting nullifiers. + /// + /// For each nullifier: + /// 1. Inserts into the permanent nullifiers tree to prevent double-spend + /// + /// Then stores all nullifiers into per-block sync storage for catch-up RPCs. + pub(in crate::drive) fn insert_nullifiers_v0( + &self, + nullifiers: &[[u8; 32]], + block_height: u64, + block_time_ms: u64, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let nullifiers_path = shielded_credit_pool_nullifiers_path_vec(); + + // Build permanent nullifier tree ops + let ops = nullifiers + .iter() + .map(|nullifier| { + GroveOperation( + QualifiedGroveDbOp::insert_only_known_to_not_already_exist_op( + nullifiers_path.clone(), + nullifier.to_vec(), + Element::new_item(vec![]), + ), + ) + }) + .collect(); + + // Store to per-block sync storage for catch-up RPCs + self.store_nullifiers_for_block( + nullifiers, + block_height, + block_time_ms, + transaction, + platform_version, + )?; + + Ok(ops) + } +} diff --git a/packages/rs-drive/src/drive/shielded/mod.rs b/packages/rs-drive/src/drive/shielded/mod.rs index f4306f2d185..a9cd1d6d086 100644 --- a/packages/rs-drive/src/drive/shielded/mod.rs +++ b/packages/rs-drive/src/drive/shielded/mod.rs @@ -6,6 +6,18 @@ pub mod paths; #[cfg(feature = "server")] pub(crate) mod estimated_costs; +/// Insert a note into the shielded pool commitment tree +#[cfg(feature = "server")] +mod insert_note; + +/// Insert nullifiers into the permanent tree and per-block sync storage +#[cfg(feature = "server")] +mod insert_nullifiers; + +/// Update the shielded pool total balance +#[cfg(feature = "server")] +mod update_total_balance; + /// Prove methods for shielded pool queries #[cfg(feature = "server")] pub mod prove; diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/mod.rs similarity index 85% rename from packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/mod.rs index a3288990f1b..3d5bcb96af0 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/mod.rs @@ -22,7 +22,7 @@ impl Drive { /// # Returns /// * `Ok(usize)` - The number of compacted entries that were cleaned up /// * `Err` - An error occurred - pub fn cleanup_expired_nullifiers( + pub fn cleanup_expired_nullifier_compactions( &self, current_block_time_ms: u64, transaction: TransactionArg, @@ -32,15 +32,15 @@ impl Drive { .drive .methods .saved_block_transactions - .cleanup_expired_nullifiers + .cleanup_expired_nullifier_compactions { - 0 => self.cleanup_expired_nullifiers_v0( + 0 => self.cleanup_expired_nullifier_compactions_v0( current_block_time_ms, transaction, platform_version, ), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "cleanup_expired_nullifiers".to_string(), + method: "cleanup_expired_nullifier_compactions".to_string(), known_versions: vec![0], received: version, })), diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs similarity index 98% rename from packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs rename to packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs index 3d521a655d2..108c70a239a 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs @@ -15,7 +15,7 @@ impl Drive { /// /// Queries for all expiration entries with time <= current_block_time_ms, /// then deletes the corresponding compacted entries and the expiration entries. - pub(in crate::drive) fn cleanup_expired_nullifiers_v0( + pub(in crate::drive) fn cleanup_expired_nullifier_compactions_v0( &self, current_block_time_ms: u64, transaction: TransactionArg, diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs index e147e2fb424..36a6732b64b 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs @@ -1,16 +1,16 @@ +#[cfg(feature = "server")] +mod cleanup_expired_nullifier_compactions; +/// Compaction of per-block nullifier entries into range-keyed compacted entries +#[cfg(feature = "server")] +pub mod compact_nullifiers; /// Fetching compacted nullifier changes #[cfg(feature = "server")] pub mod fetch_compacted_nullifiers; /// Fetching recent per-block nullifier changes #[cfg(feature = "server")] pub mod fetch_nullifiers; -#[cfg(feature = "server")] -mod store_nullifiers; -/// Compaction of per-block nullifier entries into range-keyed compacted entries -#[cfg(feature = "server")] -pub mod compact_nullifiers; -#[cfg(feature = "server")] -mod cleanup_expired_nullifiers; /// Path definitions and constants for per-block nullifier storage pub mod queries; +#[cfg(feature = "server")] +mod store_nullifiers; pub use queries::*; diff --git a/packages/rs-drive/src/drive/shielded/update_total_balance/mod.rs b/packages/rs-drive/src/drive/shielded/update_total_balance/mod.rs new file mode 100644 index 00000000000..a6083ae4e96 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/update_total_balance/mod.rs @@ -0,0 +1,29 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::version::PlatformVersion; + +impl Drive { + /// Constructs the low-level drive operations to update the shielded pool's + /// total balance. + /// + /// # Parameters + /// - `new_total_balance`: The new total balance value + /// - `platform_version`: The platform version for dispatch + pub fn update_total_balance_op( + new_total_balance: u64, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version.drive.methods.shielded.update_total_balance { + 0 => Self::update_total_balance_op_v0(new_total_balance), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "update_total_balance_op".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/update_total_balance/v0/mod.rs b/packages/rs-drive/src/drive/shielded/update_total_balance/v0/mod.rs new file mode 100644 index 00000000000..ae55907268e --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/update_total_balance/v0/mod.rs @@ -0,0 +1,32 @@ +use crate::drive::shielded::paths::{shielded_credit_pool_path_vec, SHIELDED_TOTAL_BALANCE_KEY}; +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::fees::op::LowLevelDriveOperation::GroveOperation; +use grovedb::batch::QualifiedGroveDbOp; +use grovedb::Element; + +impl Drive { + /// Version 0 implementation of constructing a total balance update operation. + /// + /// Creates an `insert_or_replace_op` that sets the shielded pool's total + /// balance sum item to the new value. + pub(in crate::drive) fn update_total_balance_op_v0( + new_total_balance: u64, + ) -> Result, Error> { + let pool_path = shielded_credit_pool_path_vec(); + let balance_i64 = i64::try_from(new_total_balance).map_err(|_| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance exceeds i64::MAX".to_string(), + )) + })?; + Ok(vec![GroveOperation( + QualifiedGroveDbOp::insert_or_replace_op( + pool_path, + vec![SHIELDED_TOTAL_BALANCE_KEY], + Element::new_sum_item(balance_i64), + ), + )]) + } +} diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 35d311401d0..58fdc18606e 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -591,7 +591,9 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { } }; - Ok(LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element)) + Ok(LowLevelDriveOperation::insert_for_known_path_key_element( + path, key, element, + )) } } diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs index 75fb95ebb2e..51e0976c1f8 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rs @@ -8,15 +8,16 @@ use crate::state_transition_action::shielded::ShieldedActionNote; use crate::util::batch::drive_op_batch::ShieldedPoolOperationType; use crate::util::batch::DriveOperation; -/// Insert each nullifier (known to not exist after validation). +/// Insert nullifiers into the permanent tree (double-spend prevention) and +/// per-block sync storage (catch-up RPCs). pub(super) fn insert_nullifiers<'a>( ops: &mut Vec>, notes: &[ShieldedActionNote], ) { - for note in notes { + if !notes.is_empty() { ops.push(DriveOperation::ShieldedPoolOperation( - ShieldedPoolOperationType::InsertNullifier { - nullifier: note.nullifier, + ShieldedPoolOperationType::InsertNullifiers { + nullifiers: notes.iter().map(|n| n.nullifier).collect(), }, )); } @@ -44,17 +45,3 @@ pub(super) fn update_balance<'a>(ops: &mut Vec>, new_total_ba ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance }, )); } - -/// Store nullifiers to recent block storage for catch-up sync RPCs. -pub(super) fn store_nullifiers_for_block<'a>( - ops: &mut Vec>, - notes: &[ShieldedActionNote], -) { - if !notes.is_empty() { - ops.push(DriveOperation::ShieldedPoolOperation( - ShieldedPoolOperationType::StoreNullifiersForBlock { - nullifiers: notes.iter().map(|n| n.nullifier).collect(), - }, - )); - } -} diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs index 7455b22bc5f..9d84b9693de 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_transfer_transition.rs @@ -1,4 +1,4 @@ -use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use super::{insert_notes, insert_nullifiers, update_balance}; use crate::error::drive::DriveError; use crate::error::Error; use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; @@ -42,9 +42,6 @@ impl DriveHighLevelOperationConverter for ShieldedTransferTransitionAction { })?; update_balance(&mut ops, new_total_balance); - // 4. Store nullifiers to recent block storage for catch-up sync - store_nullifiers_for_block(&mut ops, &v0.notes); - Ok(ops) } }, diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs index 1c3860384a0..f5353ceb70b 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/shielded_withdrawal_transition.rs @@ -1,4 +1,4 @@ -use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use super::{insert_notes, insert_nullifiers, update_balance}; use crate::error::drive::DriveError; use crate::error::Error; use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; @@ -69,9 +69,6 @@ impl DriveHighLevelOperationConverter for ShieldedWithdrawalTransitionAction { SystemOperationType::RemoveFromSystemCredits { amount: v0.amount }, )); - // 6. Store nullifiers to recent block storage for catch-up sync - store_nullifiers_for_block(&mut ops, &v0.notes); - Ok(ops) } }, diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs index 093b0015ef1..c8b6f04449f 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs @@ -1,4 +1,4 @@ -use super::{insert_notes, insert_nullifiers, store_nullifiers_for_block, update_balance}; +use super::{insert_notes, insert_nullifiers, update_balance}; use crate::error::drive::DriveError; use crate::error::Error; use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; @@ -58,9 +58,6 @@ impl DriveHighLevelOperationConverter for UnshieldTransitionAction { })?; update_balance(&mut ops, new_total_balance); - // 5. Store nullifiers to recent block storage for catch-up sync - store_nullifiers_for_block(&mut ops, &v0.notes); - Ok(ops) } }, diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs index a62d0b8a2aa..d9d39fd9933 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs @@ -17,10 +17,10 @@ impl DriveHighLevelOperationConverter for PenalizeShieldedPoolAction { let mut ops: Vec> = Vec::new(); // 1. Record nullifiers as spent (prevents replaying the same invalid proof) - for nullifier in v0.nullifiers.iter() { + if !v0.nullifiers.is_empty() { ops.push(DriveOperation::ShieldedPoolOperation( - ShieldedPoolOperationType::InsertNullifier { - nullifier: *nullifier, + ShieldedPoolOperationType::InsertNullifiers { + nullifiers: v0.nullifiers, }, )); } diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs index 3add4d5ee0b..eca8be6c9fb 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/shielded.rs @@ -1,17 +1,11 @@ -use crate::drive::shielded::paths::{ - shielded_credit_pool_notes_path_vec, shielded_credit_pool_nullifiers_path_vec, - shielded_credit_pool_path_vec, SHIELDED_TOTAL_BALANCE_KEY, -}; use crate::drive::Drive; -use crate::error::drive::DriveError; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; -use crate::fees::op::LowLevelDriveOperation::GroveOperation; use crate::util::batch::drive_op_batch::DriveLowLevelOperationConverter; use dpp::block::block_info::BlockInfo; use dpp::version::PlatformVersion; -use grovedb::batch::{KeyInfoPath, QualifiedGroveDbOp}; -use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; use std::collections::HashMap; /// Operations on the Shielded Pool @@ -27,22 +21,17 @@ pub enum ShieldedPoolOperationType { /// The encrypted note payload (216 bytes) encrypted_note: Vec, }, - /// Insert a nullifier to prevent double-spend - InsertNullifier { - /// The 32-byte nullifier - nullifier: [u8; 32], + /// Insert nullifiers into the permanent tree (double-spend prevention) and + /// per-block sync storage (catch-up RPCs). + InsertNullifiers { + /// The 32-byte nullifiers to insert + nullifiers: Vec<[u8; 32]>, }, /// Update the shielded pool total balance UpdateTotalBalance { /// The new total balance value new_total_balance: u64, }, - /// Store nullifiers to recent block storage for catch-up sync RPCs. - /// Block height and time are taken from BlockInfo during low-level conversion. - StoreNullifiersForBlock { - /// The nullifiers to store for this block - nullifiers: Vec<[u8; 32]>, - }, } impl DriveLowLevelOperationConverter for ShieldedPoolOperationType { @@ -65,56 +54,16 @@ impl DriveLowLevelOperationConverter for ShieldedPoolOperationType { nullifier, cmx, encrypted_note, - } => { - let notes_path = shielded_credit_pool_notes_path_vec(); - Ok(vec![GroveOperation( - QualifiedGroveDbOp::commitment_tree_insert_op( - notes_path, - cmx, - nullifier, - encrypted_note, - ), - )]) - } - ShieldedPoolOperationType::InsertNullifier { nullifier } => { - let nullifiers_path = shielded_credit_pool_nullifiers_path_vec(); - Ok(vec![GroveOperation( - QualifiedGroveDbOp::insert_only_known_to_not_already_exist_op( - nullifiers_path, - nullifier.to_vec(), - Element::new_item(vec![]), - ), - )]) - } + } => Drive::insert_note_op(nullifier, cmx, encrypted_note, platform_version), + ShieldedPoolOperationType::InsertNullifiers { nullifiers } => drive.insert_nullifiers( + &nullifiers, + block_info.height, + block_info.time_ms, + transaction, + platform_version, + ), ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance } => { - let pool_path = shielded_credit_pool_path_vec(); - let balance_i64 = i64::try_from(new_total_balance).map_err(|_| { - Error::Drive(DriveError::CorruptedDriveState( - "shielded pool total balance exceeds i64::MAX".to_string(), - )) - })?; - Ok(vec![GroveOperation( - QualifiedGroveDbOp::insert_or_replace_op( - pool_path, - vec![SHIELDED_TOTAL_BALANCE_KEY], - Element::new_sum_item(balance_i64), - ), - )]) - } - ShieldedPoolOperationType::StoreNullifiersForBlock { nullifiers } => { - // Store nullifiers to recent block storage for catch-up sync RPCs. - // This is a side-effect operation — it doesn't produce low-level grove ops - // but instead calls store_nullifiers_for_block directly. - if !nullifiers.is_empty() { - drive.store_nullifiers_for_block( - &nullifiers, - block_info.height, - block_info.time_ms, - transaction, - platform_version, - )?; - } - Ok(vec![]) + Drive::update_total_balance_op(new_total_balance, platform_version) } } } diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs index c6ccf552057..b762e93ee7c 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs @@ -51,11 +51,9 @@ impl Drive { // Insert each item with a sequential key for (i, element) in items.into_iter().enumerate() { - let index = current_count.checked_add(i as u64).ok_or( - Error::Drive(DriveError::CorruptedCodeExecution( - "auto-incremented count tree index overflow", - )), - )?; + let index = current_count.checked_add(i as u64).ok_or(Error::Drive( + DriveError::CorruptedCodeExecution("auto-incremented count tree index overflow"), + ))?; drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element( insert_path.clone(), index.to_be_bytes().to_vec(), diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index 0dcbbe88885..e92f57ab44d 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1346,7 +1346,8 @@ impl Drive { } } else { return Err(Error::Proof(ProofError::CorruptedProof( - "shield from asset lock was executed but no proved key values returned".to_string(), + "shield from asset lock was executed but no proved key values returned" + .to_string(), ))); }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs index 0d7a67b58f7..b21a46b743f 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs @@ -66,6 +66,9 @@ pub struct DriveAddressFundsCostEstimationMethodVersions { #[derive(Clone, Debug, Default)] pub struct DriveShieldedMethodVersions { + pub insert_note: FeatureVersion, + pub insert_nullifiers: FeatureVersion, + pub update_total_balance: FeatureVersion, pub prove_nullifiers_trunk_query: FeatureVersion, pub prove_nullifiers_branch_query: FeatureVersion, pub nullifiers_query_min_depth: u8, diff --git a/packages/rs-platform-version/src/version/drive_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/mod.rs index 2f547c3c391..8ca5a2e18ea 100644 --- a/packages/rs-platform-version/src/version/drive_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/mod.rs @@ -91,7 +91,7 @@ pub struct DriveSavedBlockTransactionsMethodVersions { pub store_nullifiers: FeatureVersion, pub fetch_nullifiers: FeatureVersion, pub compact_nullifiers: FeatureVersion, - pub cleanup_expired_nullifiers: FeatureVersion, + pub cleanup_expired_nullifier_compactions: FeatureVersion, /// Maximum number of blocks to store before nullifier compaction is triggered pub max_blocks_before_nullifier_compaction: u16, /// Maximum number of nullifier entries before compaction is triggered diff --git a/packages/rs-platform-version/src/version/drive_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/v1.rs index 227c6dbb5ca..72610e1441b 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v1.rs @@ -105,6 +105,9 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -120,7 +123,7 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/v2.rs index 203f1afa59f..2378f4b410e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v2.rs @@ -105,6 +105,9 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -120,7 +123,7 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v3.rs b/packages/rs-platform-version/src/version/drive_versions/v3.rs index 408a49cab6e..598509389ad 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v3.rs @@ -105,6 +105,9 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -120,7 +123,7 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v4.rs b/packages/rs-platform-version/src/version/drive_versions/v4.rs index 4ad9c1ee18c..6399cf5d8ff 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v4.rs @@ -105,6 +105,9 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -120,7 +123,7 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v5.rs b/packages/rs-platform-version/src/version/drive_versions/v5.rs index 85808b0f126..06deea97c6a 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v5.rs @@ -107,6 +107,9 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -122,7 +125,7 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v6.rs b/packages/rs-platform-version/src/version/drive_versions/v6.rs index 768caeb5580..7fb34d75aa7 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v6.rs @@ -109,6 +109,9 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -124,7 +127,7 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs index 2671c39d372..ea6bcde83ee 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -106,6 +106,9 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, @@ -121,7 +124,7 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, - cleanup_expired_nullifiers: 0, + cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048, }, diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index e622afc4e0c..40292137b97 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -143,12 +143,15 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, shielded: DriveShieldedMethodVersions { + insert_note: 0, + insert_nullifiers: 0, + update_total_balance: 0, prove_nullifiers_trunk_query: 0, prove_nullifiers_branch_query: 0, nullifiers_query_min_depth: 6, nullifiers_query_max_depth: 10, }, - saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, cleanup_expired_nullifiers: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048 }, + saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, cleanup_expired_nullifier_compactions: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048 }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, grove_version: GROVE_V1, diff --git a/packages/rs-platform-wallet-ffi/src/types.rs b/packages/rs-platform-wallet-ffi/src/types.rs index 82740ca8fb4..9cd45395242 100644 --- a/packages/rs-platform-wallet-ffi/src/types.rs +++ b/packages/rs-platform-wallet-ffi/src/types.rs @@ -21,8 +21,9 @@ impl IdentifierBytes { #[allow(clippy::result_large_err)] pub fn to_identifier(&self) -> Result { - dpp::prelude::Identifier::from_bytes(&self.bytes) - .map_err(|_| dpp::ProtocolError::IdentifierError("Invalid identifier bytes".to_string())) + dpp::prelude::Identifier::from_bytes(&self.bytes).map_err(|_| { + dpp::ProtocolError::IdentifierError("Invalid identifier bytes".to_string()) + }) } } From 0b82b6b02a5db9fefe6155b48772b679b4cd16d6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 11:15:35 +0700 Subject: [PATCH 16/25] refactor(drive): derive notes and anchor from transition in try_from_transition Remove redundant `notes` and `anchor` parameters from all five shielded `try_from_transition` methods. These values are already available on the transition structs (`value.actions` and `value.anchor`), so passing them separately was unnecessary indirection. Add `From<&SerializedAction> for ShieldedActionNote` conversion to cleanly map transition actions to action notes. Co-Authored-By: Claude Opus 4.6 --- .../src/state_transition_action/shielded/mod.rs | 12 ++++++++++++ .../shielded/shield/transformer.rs | 3 --- .../shielded/shield/v0/transformer.rs | 4 +++- .../shielded/shield_from_asset_lock/transformer.rs | 3 --- .../shield_from_asset_lock/v0/transformer.rs | 6 ++++-- .../shielded/shielded_transfer/transformer.rs | 5 ----- .../shielded/shielded_transfer/v0/transformer.rs | 9 +++++---- .../shielded/shielded_withdrawal/transformer.rs | 5 ----- .../shielded/shielded_withdrawal/v0/transformer.rs | 7 ++++--- .../shielded/unshield/transformer.rs | 11 ++--------- .../shielded/unshield/v0/transformer.rs | 7 ++++--- 11 files changed, 34 insertions(+), 38 deletions(-) diff --git a/packages/rs-drive/src/state_transition_action/shielded/mod.rs b/packages/rs-drive/src/state_transition_action/shielded/mod.rs index 3c4be746467..5bf608b5c07 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/mod.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/mod.rs @@ -9,6 +9,8 @@ pub mod shielded_withdrawal; /// Unshield transition action pub mod unshield; +use dpp::shielded::SerializedAction; + /// One note from an Orchard action: the three per-action fields that travel together. #[derive(Debug, Clone)] pub struct ShieldedActionNote { @@ -19,3 +21,13 @@ pub struct ShieldedActionNote { /// Encrypted note ciphertext pub encrypted_note: Vec, } + +impl From<&SerializedAction> for ShieldedActionNote { + fn from(action: &SerializedAction) -> Self { + ShieldedActionNote { + nullifier: action.nullifier, + cmx: action.cmx, + encrypted_note: action.encrypted_note.clone(), + } + } +} diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs index be833da00d7..ca619df9af9 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/transformer.rs @@ -1,6 +1,5 @@ use crate::state_transition_action::shielded::shield::v0::ShieldTransitionActionV0; use crate::state_transition_action::shielded::shield::ShieldTransitionAction; -use crate::state_transition_action::shielded::ShieldedActionNote; use dpp::address_funds::PlatformAddress; use dpp::fee::Credits; use dpp::prelude::{AddressNonce, ConsensusValidationResult}; @@ -13,7 +12,6 @@ impl ShieldTransitionAction { value: &ShieldTransition, inputs_with_remaining_balance: BTreeMap, shield_amount: Credits, - notes: Vec, current_total_balance: Credits, ) -> ConsensusValidationResult { match value { @@ -22,7 +20,6 @@ impl ShieldTransitionAction { v0, inputs_with_remaining_balance, shield_amount, - notes, current_total_balance, ); result.map(|action| action.into()) diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs index 2a9f5bbab2d..68629ed312a 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shield/v0/transformer.rs @@ -12,9 +12,11 @@ impl ShieldTransitionActionV0 { value: &ShieldTransitionV0, inputs_with_remaining_balance: BTreeMap, shield_amount: Credits, - notes: Vec, current_total_balance: Credits, ) -> ConsensusValidationResult { + let notes: Vec = + value.actions.iter().map(ShieldedActionNote::from).collect(); + ConsensusValidationResult::new_with_data(ShieldTransitionActionV0 { inputs_with_remaining_balance, shield_amount, diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs index 0cec6e308c1..c05ec853283 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/transformer.rs @@ -1,6 +1,5 @@ use crate::state_transition_action::shielded::shield_from_asset_lock::v0::ShieldFromAssetLockTransitionActionV0; use crate::state_transition_action::shielded::shield_from_asset_lock::ShieldFromAssetLockTransitionAction; -use crate::state_transition_action::shielded::ShieldedActionNote; use dpp::fee::Credits; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; @@ -13,7 +12,6 @@ impl ShieldFromAssetLockTransitionAction { asset_lock_value_to_be_consumed: Credits, signable_bytes_hasher: [u8; 32], shield_amount: Credits, - notes: Vec, current_total_balance: Credits, ) -> ConsensusValidationResult { match value { @@ -24,7 +22,6 @@ impl ShieldFromAssetLockTransitionAction { asset_lock_value_to_be_consumed, signable_bytes_hasher, shield_amount, - notes, current_total_balance, ); result.map(|action| action.into()) diff --git a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs index 56f5c4969d3..d5cff6adf65 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shield_from_asset_lock/v0/transformer.rs @@ -7,14 +7,16 @@ use dpp::state_transition::state_transitions::shielded::shield_from_asset_lock_t impl ShieldFromAssetLockTransitionActionV0 { /// Transforms the shield from asset lock transition into an action pub fn try_from_transition( - _value: &ShieldFromAssetLockTransitionV0, + value: &ShieldFromAssetLockTransitionV0, asset_lock_outpoint: [u8; 36], asset_lock_value_to_be_consumed: Credits, signable_bytes_hasher: [u8; 32], shield_amount: Credits, - notes: Vec, current_total_balance: Credits, ) -> ConsensusValidationResult { + let notes: Vec = + value.actions.iter().map(ShieldedActionNote::from).collect(); + ConsensusValidationResult::new_with_data(ShieldFromAssetLockTransitionActionV0 { asset_lock_outpoint, asset_lock_value_to_be_consumed, diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs index b1e329e233c..c9324940402 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/transformer.rs @@ -1,6 +1,5 @@ use crate::state_transition_action::shielded::shielded_transfer::v0::ShieldedTransferTransitionActionV0; use crate::state_transition_action::shielded::shielded_transfer::ShieldedTransferTransitionAction; -use crate::state_transition_action::shielded::ShieldedActionNote; use dpp::fee::Credits; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::shielded_transfer_transition::ShieldedTransferTransition; @@ -9,8 +8,6 @@ impl ShieldedTransferTransitionAction { /// Transforms the state transition into an action pub fn try_from_transition( value: &ShieldedTransferTransition, - notes: Vec, - anchor: [u8; 32], fee_amount: Credits, current_total_balance: Credits, ) -> ConsensusValidationResult { @@ -18,8 +15,6 @@ impl ShieldedTransferTransitionAction { ShieldedTransferTransition::V0(v0) => { let result = ShieldedTransferTransitionActionV0::try_from_transition( v0, - notes, - anchor, fee_amount, current_total_balance, ); diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs index b4c356d8bec..c741b7ed59a 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_transfer/v0/transformer.rs @@ -7,15 +7,16 @@ use dpp::state_transition::state_transitions::shielded::shielded_transfer_transi impl ShieldedTransferTransitionActionV0 { /// Transforms the shielded transfer transition into an action pub fn try_from_transition( - _value: &ShieldedTransferTransitionV0, - notes: Vec, - anchor: [u8; 32], + value: &ShieldedTransferTransitionV0, fee_amount: Credits, current_total_balance: Credits, ) -> ConsensusValidationResult { + let notes: Vec = + value.actions.iter().map(ShieldedActionNote::from).collect(); + ConsensusValidationResult::new_with_data(ShieldedTransferTransitionActionV0 { notes, - anchor, + anchor: value.anchor, fee_amount, current_total_balance, }) diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs index 7048f97ebda..caaa5b3a747 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/transformer.rs @@ -1,6 +1,5 @@ use crate::state_transition_action::shielded::shielded_withdrawal::v0::ShieldedWithdrawalTransitionActionV0; use crate::state_transition_action::shielded::shielded_withdrawal::ShieldedWithdrawalTransitionAction; -use crate::state_transition_action::shielded::ShieldedActionNote; use dpp::fee::Credits; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; @@ -9,8 +8,6 @@ impl ShieldedWithdrawalTransitionAction { /// Transforms the state transition into an action pub fn try_from_transition( value: &ShieldedWithdrawalTransition, - notes: Vec, - anchor: [u8; 32], current_total_balance: Credits, creation_time_ms: u64, ) -> ConsensusValidationResult { @@ -18,8 +15,6 @@ impl ShieldedWithdrawalTransitionAction { ShieldedWithdrawalTransition::V0(v0) => { let result = ShieldedWithdrawalTransitionActionV0::try_from_transition( v0, - notes, - anchor, current_total_balance, creation_time_ms, ); diff --git a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs index 7e86164b84d..ccf78a23df5 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/shielded_withdrawal/v0/transformer.rs @@ -12,11 +12,12 @@ impl ShieldedWithdrawalTransitionActionV0 { /// Transforms the shielded withdrawal transition into an action pub fn try_from_transition( value: &ShieldedWithdrawalTransitionV0, - notes: Vec, - anchor: [u8; 32], current_total_balance: Credits, creation_time_ms: u64, ) -> ConsensusValidationResult { + let notes: Vec = + value.actions.iter().map(ShieldedActionNote::from).collect(); + // Generate entropy from first nullifier + output_script for document ID let mut entropy = Vec::new(); if let Some(first_note) = notes.first() { @@ -65,7 +66,7 @@ impl ShieldedWithdrawalTransitionActionV0 { ConsensusValidationResult::new_with_data(ShieldedWithdrawalTransitionActionV0 { amount: value.unshielding_amount, notes, - anchor, + anchor: value.anchor, core_fee_per_byte: value.core_fee_per_byte, pooling: value.pooling, output_script: value.output_script.clone(), diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs index 5984e3216d1..45ad2fe2b10 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/transformer.rs @@ -1,6 +1,5 @@ use crate::state_transition_action::shielded::unshield::v0::UnshieldTransitionActionV0; use crate::state_transition_action::shielded::unshield::UnshieldTransitionAction; -use crate::state_transition_action::shielded::ShieldedActionNote; use dpp::fee::Credits; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::unshield_transition::UnshieldTransition; @@ -9,18 +8,12 @@ impl UnshieldTransitionAction { /// Transforms the state transition into an action pub fn try_from_transition( value: &UnshieldTransition, - notes: Vec, - anchor: [u8; 32], current_total_balance: Credits, ) -> ConsensusValidationResult { match value { UnshieldTransition::V0(v0) => { - let result = UnshieldTransitionActionV0::try_from_transition( - v0, - notes, - anchor, - current_total_balance, - ); + let result = + UnshieldTransitionActionV0::try_from_transition(v0, current_total_balance); result.map(|action| action.into()) } } diff --git a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs index f86f5b298ab..6324b8e6d99 100644 --- a/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/shielded/unshield/v0/transformer.rs @@ -8,15 +8,16 @@ impl UnshieldTransitionActionV0 { /// Transforms the unshield transition into an action pub fn try_from_transition( value: &UnshieldTransitionV0, - notes: Vec, - anchor: [u8; 32], current_total_balance: Credits, ) -> ConsensusValidationResult { + let notes: Vec = + value.actions.iter().map(ShieldedActionNote::from).collect(); + ConsensusValidationResult::new_with_data(UnshieldTransitionActionV0 { output_address: value.output_address, amount: value.unshielding_amount, notes, - anchor, + anchor: value.anchor, fee_amount: 0, // TODO: fee calculation for unshield current_total_balance, }) From 295f777d85c388bfa571b6562fade39a2c905d6c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 12:54:21 +0700 Subject: [PATCH 17/25] fix(drive): harden bincode decoding and fix limit==0 edge case in shielded nullifiers - Replace with_no_limit() with with_limit() in verify_recent and verify_compacted nullifier verifiers (128 KiB for nullifier lists, 4 MiB for GroveDBProof) - Capture bytes_read and reject trailing bytes after decode - Short-circuit fetch_compacted_nullifiers when limit == Some(0) - Return CorruptedSerialization error on malformed probe key in prove_compacted_nullifier_changes instead of silently synthesizing a default key Co-Authored-By: Claude Opus 4.6 --- .../fetch_compacted_nullifiers/v0/mod.rs | 14 ++++-- .../v0/mod.rs | 47 +++++++++++++------ .../verify_recent_nullifier_changes/v0/mod.rs | 12 ++++- 3 files changed, 53 insertions(+), 20 deletions(-) diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs index 65b1ae47520..83e46c06252 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs @@ -45,6 +45,11 @@ impl Drive { let mut compacted_changes = Vec::new(); let limit_usize = limit.map(|l| l as usize); + // Short-circuit: if limit is zero, no results should be returned + if limit_usize == Some(0) { + return Ok(compacted_changes); + } + // Query 1: Find if there's a range containing start_block_height // Query descending from (start_block_height, u64::MAX) with limit 1 let mut desc_end_key = Vec::with_capacity(16); @@ -251,10 +256,11 @@ impl Drive { key } } else { - let mut key = Vec::with_capacity(16); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "invalid compacted block key length, expected 16 bytes".to_string(), + ), + ))); } } else { let mut key = Vec::with_capacity(16); diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index 4ceef91696d..59a2d2fe082 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -57,19 +57,27 @@ impl Drive { limit: Option, platform_version: &PlatformVersion, ) -> Result<(RootHash, VerifiedCompactedNullifierChanges), Error> { - let bincode_config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - // Decode the GroveDBProof to navigate its structure - let grovedb_proof: GroveDBProof = bincode::decode_from_slice(proof, bincode_config) - .map(|(p, _)| p) - .map_err(|e| { - Error::Proof(ProofError::CorruptedProof(format!( - "cannot decode GroveDBProof: {}", - e - ))) - })?; + let grovedb_proof: GroveDBProof = { + let config = bincode::config::standard() + .with_big_endian() + .with_limit::<{ 4 * 1024 * 1024 }>(); + let (proof_data, bytes_read): (GroveDBProof, usize) = + bincode::decode_from_slice(proof, config).map_err(|e| { + Error::Proof(ProofError::CorruptedProof(format!( + "cannot decode GroveDBProof: {}", + e + ))) + })?; + if bytes_read != proof.len() { + return Err(Error::Proof(ProofError::CorruptedProof(format!( + "trailing bytes after GroveDBProof decode: read {} of {}", + bytes_read, + proof.len() + )))); + } + proof_data + }; // Navigate to the compacted nullifiers layer // Path: AddressBalances -> SHIELDED_CREDIT_POOL_KEY -> CompactedNullifiers ('o') @@ -190,14 +198,25 @@ impl Drive { }; // Deserialize the nullifier list - let (nullifiers, _): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, bincode_config).map_err(|e| { + let nullifier_config = bincode::config::standard() + .with_big_endian() + .with_limit::<{ 128 * 1024 }>(); + let (nullifiers, bytes_read): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(&serialized_data, nullifier_config).map_err(|e| { Error::Proof(ProofError::CorruptedProof(format!( "cannot decode compacted nullifiers: {}", e ))) })?; + if bytes_read != serialized_data.len() { + return Err(Error::Proof(ProofError::CorruptedProof(format!( + "trailing bytes after compacted nullifier decode: read {} of {}", + bytes_read, + serialized_data.len() + )))); + } + compacted_changes.push((range_start, range_end, nullifiers)); } diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs index 9a6c9764fed..9b93fb21c21 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -24,7 +24,7 @@ impl Drive { let config = bincode::config::standard() .with_big_endian() - .with_no_limit(); + .with_limit::<{ 128 * 1024 }>(); // Create the same range query as the prove function let mut query = Query::new(); @@ -61,7 +61,7 @@ impl Drive { }; // Deserialize the nullifier list - let (nullifiers, _): (Vec<[u8; 32]>, usize) = + let (nullifiers, bytes_read): (Vec<[u8; 32]>, usize) = bincode::decode_from_slice(&serialized_data, config).map_err(|e| { Error::Proof(ProofError::CorruptedProof(format!( "cannot decode nullifiers: {}", @@ -69,6 +69,14 @@ impl Drive { ))) })?; + if bytes_read != serialized_data.len() { + return Err(Error::Proof(ProofError::CorruptedProof(format!( + "trailing bytes after nullifier decode: read {} of {}", + bytes_read, + serialized_data.len() + )))); + } + nullifier_changes.push((block_height, nullifiers)); } From 3fb7801fc303d10628bf35df0a80606a323d42e9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 16:56:40 +0700 Subject: [PATCH 18/25] refactor(drive): address review feedback on shielded nullifiers - Centralize bincode serialization into CompactedNullifiers and NullifierExpirationRanges wrapper structs in nullifiers/types.rs - Move prove functions under shielded/prove/ for consistent organization - Add test proving unbounded cleanup query handles 5000 entries safely - Document TPS analysis justifying unbounded query as code comment Co-Authored-By: Claude Opus 4.6 --- .../v0/mod.rs | 125 ++++++++++++++++-- .../nullifiers/compact_nullifiers/v0/mod.rs | 45 +------ .../fetch_compacted_nullifiers/mod.rs | 37 ------ .../fetch_compacted_nullifiers/v0/mod.rs | 104 +-------------- .../nullifiers/fetch_nullifiers/mod.rs | 37 ------ .../nullifiers/fetch_nullifiers/v0/mod.rs | 36 +---- .../src/drive/shielded/nullifiers/mod.rs | 3 + .../nullifiers/store_nullifiers/v0/mod.rs | 15 +-- .../src/drive/shielded/nullifiers/types.rs | 122 +++++++++++++++++ .../rs-drive/src/drive/shielded/prove/mod.rs | 6 + .../prove_compacted_nullifier_changes/mod.rs | 48 +++++++ .../v0/mod.rs | 93 +++++++++++++ .../prove_recent_nullifier_changes/mod.rs | 48 +++++++ .../prove_recent_nullifier_changes/v0/mod.rs | 31 +++++ 14 files changed, 479 insertions(+), 271 deletions(-) create mode 100644 packages/rs-drive/src/drive/shielded/nullifiers/types.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/v0/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/mod.rs create mode 100644 packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/v0/mod.rs diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs index 108c70a239a..10cb0bd3d82 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs @@ -1,6 +1,7 @@ use crate::drive::shielded::nullifiers::queries::{ shielded_compacted_nullifiers_path_vec, shielded_nullifiers_expiration_time_path_vec, }; +use crate::drive::shielded::nullifiers::types::NullifierExpirationRanges; use crate::drive::Drive; use crate::error::Error; use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; @@ -15,6 +16,13 @@ impl Drive { /// /// Queries for all expiration entries with time <= current_block_time_ms, /// then deletes the corresponding compacted entries and the expiration entries. + /// + /// The query is unbounded (no limit) because the data model naturally caps + /// the number of expiration entries. Compaction triggers every 64 blocks or + /// 2048 nullifiers. With 3s blocks (201,600 blocks/week) and 1-week expiry: + /// - Normal load (~1 shielded TPS): ~3,150 entries/week + /// - Extreme load (683 shielded TPS, compaction every block): ~201,600 entries/week + /// Both are well within GroveDB query capacity. pub(in crate::drive) fn cleanup_expired_nullifier_compactions_v0( &self, current_block_time_ms: u64, @@ -47,10 +55,6 @@ impl Drive { return Ok(0); } - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - let mut batch = GroveDbOpBatch::new(); let mut total_cleaned = 0usize; @@ -67,16 +71,10 @@ impl Drive { }; // Deserialize the vec of block ranges - let (ranges, _): (Vec<(u64, u64)>, usize) = - bincode::decode_from_slice(&serialized_ranges, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode expiration block ranges: {}", - e - )))) - })?; + let ranges = NullifierExpirationRanges::decode(&serialized_ranges)?; // Delete each compacted nullifier entry - for (start_block, end_block) in &ranges { + for (start_block, end_block) in ranges.iter() { let mut compacted_key = Vec::with_capacity(16); compacted_key.extend_from_slice(&start_block.to_be_bytes()); compacted_key.extend_from_slice(&end_block.to_be_bytes()); @@ -96,3 +94,106 @@ impl Drive { Ok(total_cleaned) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::drive::shielded::nullifiers::queries::{ + shielded_compacted_nullifiers_path, shielded_nullifiers_expiration_time_path, + }; + use crate::drive::shielded::nullifiers::types::CompactedNullifiers; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use grovedb::Element; + + /// Calculation: can the unbounded query in cleanup be a problem? + /// + /// Compaction triggers every 64 blocks or 2048 nullifiers. + /// Platform blocks can happen every ~3s. In one week (604,800s): + /// - 201,600 blocks + /// - At most 201,600 / 64 = 3,150 compaction events (block-triggered) + /// - Each compaction = 1 expiration entry (unique timestamp key) + /// + /// To get more compactions, nullifier-triggered compaction must dominate: + /// - 2048 nullifiers/block → compaction every block → 201,600 entries/week + /// - That requires 2048 shielded tx/block ÷ 3s = ~683 shielded TPS sustained + /// + /// Even at 683 TPS for a full week, only ~201,600 expiration entries accumulate. + /// At normal load (<1 shielded TPS), it's ~3,150 entries. + /// + /// This test creates 5,000 expiration entries (exceeding the normal worst-case week + /// at <1 TPS) and verifies cleanup handles them fine. + #[test] + fn test_cleanup_handles_5000_expired_entries() { + let drive = setup_drive_with_initial_state_structure(None); + let transaction = drive.grove.start_transaction(); + let platform_version = PlatformVersion::latest(); + + let expiration_path = shielded_nullifiers_expiration_time_path(); + let compacted_path = shielded_compacted_nullifiers_path(); + + let num_entries: u64 = 5000; + + // Insert 5000 expiration entries and their corresponding compacted entries. + // Each expiration entry has a unique timestamp key and references one block range. + for i in 0..num_entries { + let expiration_time_ms = (i + 1) * 1000; // 1s, 2s, ... 5000s + let expiration_key = expiration_time_ms.to_be_bytes().to_vec(); + + let start_block = i * 64; + let end_block = start_block + 63; + + // Serialize the block ranges for the expiration entry + let ranges = NullifierExpirationRanges::new(vec![(start_block, end_block)]); + let serialized_ranges = ranges.encode().expect("encode ranges"); + + // Insert expiration entry + drive + .grove + .insert( + expiration_path.as_ref(), + &expiration_key, + Element::new_item(serialized_ranges), + None, + Some(&transaction), + &platform_version.drive.grove_version, + ) + .unwrap() + .expect("insert expiration entry"); + + // Insert corresponding compacted nullifier entry + let mut compacted_key = Vec::with_capacity(16); + compacted_key.extend_from_slice(&start_block.to_be_bytes()); + compacted_key.extend_from_slice(&end_block.to_be_bytes()); + + // Just a small dummy payload + let dummy_nullifiers = CompactedNullifiers::new(vec![[0u8; 32]]); + let serialized_nullifiers = dummy_nullifiers.encode().expect("encode nullifiers"); + + drive + .grove + .insert( + compacted_path.as_ref(), + &compacted_key, + Element::new_item(serialized_nullifiers), + None, + Some(&transaction), + &platform_version.drive.grove_version, + ) + .unwrap() + .expect("insert compacted entry"); + } + + // Now cleanup all entries (current_block_time >= all expiration times) + let current_time_ms = (num_entries + 1) * 1000; + + let cleaned = drive + .cleanup_expired_nullifier_compactions( + current_time_ms, + Some(&transaction), + platform_version, + ) + .expect("cleanup should succeed with 5000 entries"); + + assert_eq!(cleaned, num_entries as usize); + } +} diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs index 0be363fbef0..4c67b00df6c 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/compact_nullifiers/v0/mod.rs @@ -3,6 +3,7 @@ use crate::drive::shielded::nullifiers::queries::{ shielded_compacted_nullifiers_path_vec, shielded_nullifiers_expiration_time_path, shielded_nullifiers_expiration_time_path_vec, shielded_recent_nullifiers_path_vec, }; +use crate::drive::shielded::nullifiers::types::{CompactedNullifiers, NullifierExpirationRanges}; use crate::drive::Drive; use crate::error::Error; use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods; @@ -52,10 +53,6 @@ impl Drive { let key_elements = results.to_key_elements(); - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - // Track block range - start with current block let mut start_block: u64 = current_block_height; let mut end_block: u64 = current_block_height; @@ -93,16 +90,10 @@ impl Drive { }; // Deserialize the nullifier list - let (block_nullifiers, _): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode nullifiers: {}", - e - )))) - })?; + let block_nullifiers = CompactedNullifiers::decode(&serialized_data)?; // Simply concatenate - no merge semantics needed for nullifiers - combined_nullifiers.extend(block_nullifiers); + combined_nullifiers.extend(block_nullifiers.iter()); keys_to_delete.push(key); } @@ -111,12 +102,7 @@ impl Drive { combined_nullifiers.extend_from_slice(current_nullifiers); // Serialize the combined nullifiers - let serialized = bincode::encode_to_vec(&combined_nullifiers, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot encode compacted nullifiers: {}", - e - )))) - })?; + let serialized = CompactedNullifiers::new(combined_nullifiers).encode()?; // Create the compacted key: (start_block, end_block) as 16 bytes let mut compacted_key = Vec::with_capacity(16); @@ -158,29 +144,12 @@ impl Drive { let expiration_value = if let Some(Element::Item(existing_data, _)) = existing_ranges { // Deserialize existing vec of block ranges and append the new one - let (mut ranges, _): (Vec<(u64, u64)>, usize) = - bincode::decode_from_slice(&existing_data, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode expiration block ranges: {}", - e - )))) - })?; + let mut ranges = NullifierExpirationRanges::decode(&existing_data)?.into_inner(); ranges.push((start_block, end_block)); - bincode::encode_to_vec(&ranges, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot encode expiration block ranges: {}", - e - )))) - })? + NullifierExpirationRanges::new(ranges).encode()? } else { // No existing entry, create new vec with single range - let ranges: Vec<(u64, u64)> = vec![(start_block, end_block)]; - bincode::encode_to_vec(&ranges, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot encode expiration block ranges: {}", - e - )))) - })? + NullifierExpirationRanges::new(vec![(start_block, end_block)]).encode()? }; // Store in the expiration tree: key = expiration_time, value = vec of (start_block, end_block) diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs index 49892a3f085..3cddb7cde33 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs @@ -45,41 +45,4 @@ impl Drive { })), } } - - /// Proves compacted nullifier changes starting from a given block height. - /// - /// # Arguments - /// * `start_block_height` - The block height to start from - /// * `limit` - Optional maximum number of compacted entries to prove - /// * `transaction` - Optional database transaction - /// * `platform_version` - The platform version - /// - /// # Returns - /// A grovedb proof - pub fn prove_compacted_nullifier_changes( - &self, - start_block_height: u64, - limit: Option, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result, Error> { - match platform_version - .drive - .methods - .saved_block_transactions - .fetch_nullifiers - { - 0 => self.prove_compacted_nullifier_changes_v0( - start_block_height, - limit, - transaction, - platform_version, - ), - version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "prove_compacted_nullifier_changes".to_string(), - known_versions: vec![0], - received: version, - })), - } - } } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs index 83e46c06252..99900d2ccf9 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs @@ -1,4 +1,5 @@ use crate::drive::shielded::nullifiers::queries::shielded_compacted_nullifiers_path_vec; +use crate::drive::shielded::nullifiers::types::CompactedNullifiers; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -38,10 +39,6 @@ impl Drive { // 2. Second query: ascending from (start_block_height, 0) to get ranges // that start at or after start_block_height - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - let mut compacted_changes = Vec::new(); let limit_usize = limit.map(|l| l as usize); @@ -101,13 +98,7 @@ impl Drive { ))); }; - let (nullifiers, _): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode compacted nullifiers: {}", - e - )))) - })?; + let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); compacted_changes.push((start_block, end_block, nullifiers)); } @@ -186,13 +177,7 @@ impl Drive { ))); }; - let (nullifiers, _): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode compacted nullifiers: {}", - e - )))) - })?; + let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); compacted_changes.push((start_block, end_block, nullifiers)); } @@ -200,87 +185,4 @@ impl Drive { Ok(compacted_changes) } - /// Version 0 implementation for proving compacted nullifier changes. - /// - /// Uses a two-step approach: - /// 1. First query (non-proving): descending to find any range containing start_block_height - /// 2. Second query (proving): ascending from the found start_block or start_block_height - /// - /// This ensures the proof covers all relevant ranges efficiently. - pub(in crate::drive) fn prove_compacted_nullifier_changes_v0( - &self, - start_block_height: u64, - limit: Option, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result, Error> { - let path = shielded_compacted_nullifiers_path_vec(); - - // Step 1: Non-proving descending query to find any range containing start_block_height - let mut desc_end_key = Vec::with_capacity(16); - desc_end_key.extend_from_slice(&start_block_height.to_be_bytes()); - desc_end_key.extend_from_slice(&u64::MAX.to_be_bytes()); - - let mut desc_query = Query::new_with_direction(false); // descending - desc_query.insert_range_to_inclusive(..=desc_end_key); - - let desc_path_query = - PathQuery::new(path.clone(), SizedQuery::new(desc_query, Some(1), None)); - - let (desc_results, _) = self.grove_get_path_query( - &desc_path_query, - transaction, - QueryResultType::QueryKeyElementPairResultType, - &mut vec![], - &platform_version.drive, - )?; - - // Determine the actual start key for the proved query - // If we found a containing range, use its exact key - // Otherwise use (start_block_height, start_block_height) since end_block >= start_block always - let start_key = if let Some((key, _)) = desc_results.to_key_elements().into_iter().next() { - if key.len() == 16 { - let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?); - // If this range contains start_block_height, use its exact key - if end_block >= start_block_height { - key - } else { - // No containing range, use (start_block_height, start_block_height) - let mut key = Vec::with_capacity(16); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key - } - } else { - return Err(Error::Protocol(Box::new( - ProtocolError::CorruptedSerialization( - "invalid compacted block key length, expected 16 bytes".to_string(), - ), - ))); - } - } else { - let mut key = Vec::with_capacity(16); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key.extend_from_slice(&start_block_height.to_be_bytes()); - key - }; - - // Step 2: Proved ascending query from start_key - - let mut query = Query::new(); - query.insert_range_from(start_key..); - - let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); - - self.grove_get_proved_path_query( - &path_query, - transaction, - &mut vec![], - &platform_version.drive, - ) - } } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs index fb7ed9694ae..fa661b40d87 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs @@ -45,41 +45,4 @@ impl Drive { })), } } - - /// Proves recent nullifier changes starting from a given block height. - /// - /// # Arguments - /// * `start_height` - The block height to start from - /// * `limit` - Optional maximum number of blocks to prove - /// * `transaction` - Optional database transaction - /// * `platform_version` - The platform version - /// - /// # Returns - /// A grovedb proof - pub fn prove_recent_nullifier_changes( - &self, - start_height: u64, - limit: Option, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result, Error> { - match platform_version - .drive - .methods - .saved_block_transactions - .fetch_nullifiers - { - 0 => self.prove_recent_nullifier_changes_v0( - start_height, - limit, - transaction, - platform_version, - ), - version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "prove_recent_nullifier_changes".to_string(), - known_versions: vec![0], - received: version, - })), - } - } } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs index 81d7514374e..5ecdd78445a 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs @@ -1,4 +1,5 @@ use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; +use crate::drive::shielded::nullifiers::types::CompactedNullifiers; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -37,10 +38,6 @@ impl Drive { &platform_version.drive, )?; - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - let mut nullifier_changes = Vec::new(); for (key, element) in results.to_key_elements() { @@ -62,13 +59,7 @@ impl Drive { }; // Deserialize the nullifier list - let (nullifiers, _): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot decode nullifiers: {}", - e - )))) - })?; + let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); nullifier_changes.push((block_height, nullifiers)); } @@ -76,27 +67,4 @@ impl Drive { Ok(nullifier_changes) } - /// Version 0 implementation for proving nullifier changes from a start height. - pub(in crate::drive) fn prove_recent_nullifier_changes_v0( - &self, - start_height: u64, - limit: Option, - transaction: TransactionArg, - platform_version: &PlatformVersion, - ) -> Result, Error> { - let path = shielded_recent_nullifiers_path_vec(); - - // Create a range query starting from the specified height - let mut query = Query::new(); - query.insert_range_from(start_height.to_be_bytes().to_vec()..); - - let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); - - self.grove_get_proved_path_query( - &path_query, - transaction, - &mut vec![], - &platform_version.drive, - ) - } } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs index 36a6732b64b..610cf0e1efa 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs @@ -13,4 +13,7 @@ pub mod fetch_nullifiers; pub mod queries; #[cfg(feature = "server")] mod store_nullifiers; +/// Wrapper types for serialized nullifier data +#[cfg(feature = "server")] +pub mod types; pub use queries::*; diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs index 63cf771a133..3b9192d17e0 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/store_nullifiers/v0/mod.rs @@ -1,11 +1,11 @@ use crate::drive::shielded::nullifiers::queries::{ shielded_recent_nullifiers_path, SHIELDED_RECENT_NULLIFIERS_KEY_U8, }; +use crate::drive::shielded::nullifiers::types::CompactedNullifiers; use crate::drive::shielded::paths::shielded_credit_pool_path; use crate::drive::Drive; use crate::error::Error; use crate::util::grove_operations::DirectQueryType; -use dpp::ProtocolError; use grovedb::Element; use grovedb::TransactionArg; @@ -50,17 +50,8 @@ impl Drive { return Ok(()); } - // Serialize the nullifiers using bincode - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - - let serialized = bincode::encode_to_vec(nullifiers, config).map_err(|e| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( - "cannot encode nullifiers: {}", - e - )))) - })?; + // Serialize the nullifiers using CompactedNullifiers wrapper + let serialized = CompactedNullifiers::new(nullifiers.to_vec()).encode()?; // The sum value is the number of nullifiers let entry_count = i64::try_from(nullifiers.len()).map_err(|_| { diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/types.rs b/packages/rs-drive/src/drive/shielded/nullifiers/types.rs new file mode 100644 index 00000000000..2359b921816 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/nullifiers/types.rs @@ -0,0 +1,122 @@ +use crate::error::Error; +use dpp::ProtocolError; +use std::ops::Deref; + +/// Wrapper around `Vec<[u8; 32]>` for serialized nullifier lists. +/// +/// Encapsulates the bincode configuration used for encoding and decoding +/// compacted nullifier data, eliminating duplicated serialization boilerplate. +pub struct CompactedNullifiers(Vec<[u8; 32]>); + +impl CompactedNullifiers { + /// Creates a new `CompactedNullifiers` from the given nullifier list. + pub fn new(inner: Vec<[u8; 32]>) -> Self { + Self(inner) + } + + /// Returns the bincode configuration used for nullifier serialization. + fn bincode_config() -> bincode::config::Configuration< + bincode::config::BigEndian, + bincode::config::Varint, + bincode::config::NoLimit, + > { + bincode::config::standard() + .with_big_endian() + .with_no_limit() + } + + /// Encodes the nullifier list into bytes using bincode. + pub fn encode(&self) -> Result, Error> { + bincode::encode_to_vec(&self.0, Self::bincode_config()).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode compacted nullifiers: {}", + e + )))) + }) + } + + /// Decodes a nullifier list from bytes using bincode. + pub fn decode(bytes: &[u8]) -> Result { + let (inner, _): (Vec<[u8; 32]>, usize) = + bincode::decode_from_slice(bytes, Self::bincode_config()).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode compacted nullifiers: {}", + e + )))) + })?; + Ok(Self(inner)) + } + + /// Consumes the wrapper and returns the inner `Vec<[u8; 32]>`. + pub fn into_inner(self) -> Vec<[u8; 32]> { + self.0 + } +} + +impl Deref for CompactedNullifiers { + type Target = Vec<[u8; 32]>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Wrapper around `Vec<(u64, u64)>` for serialized nullifier expiration range lists. +/// +/// Each `(u64, u64)` pair represents a `(start_block, end_block)` range of compacted +/// nullifiers that share the same expiration timestamp. Encapsulates the bincode +/// configuration used for encoding and decoding these ranges. +pub struct NullifierExpirationRanges(Vec<(u64, u64)>); + +impl NullifierExpirationRanges { + /// Creates a new `NullifierExpirationRanges` from the given range list. + pub fn new(inner: Vec<(u64, u64)>) -> Self { + Self(inner) + } + + /// Returns the bincode configuration used for expiration range serialization. + fn bincode_config() -> bincode::config::Configuration< + bincode::config::BigEndian, + bincode::config::Varint, + bincode::config::NoLimit, + > { + bincode::config::standard() + .with_big_endian() + .with_no_limit() + } + + /// Encodes the expiration range list into bytes using bincode. + pub fn encode(&self) -> Result, Error> { + bincode::encode_to_vec(&self.0, Self::bincode_config()).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot encode nullifier expiration ranges: {}", + e + )))) + }) + } + + /// Decodes an expiration range list from bytes using bincode. + pub fn decode(bytes: &[u8]) -> Result { + let (inner, _): (Vec<(u64, u64)>, usize) = + bincode::decode_from_slice(bytes, Self::bincode_config()).map_err(|e| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization(format!( + "cannot decode nullifier expiration ranges: {}", + e + )))) + })?; + Ok(Self(inner)) + } + + /// Consumes the wrapper and returns the inner `Vec<(u64, u64)>`. + pub fn into_inner(self) -> Vec<(u64, u64)> { + self.0 + } +} + +impl Deref for NullifierExpirationRanges { + type Target = Vec<(u64, u64)>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/mod.rs b/packages/rs-drive/src/drive/shielded/prove/mod.rs index c979a8d57c5..46c9fc05d26 100644 --- a/packages/rs-drive/src/drive/shielded/prove/mod.rs +++ b/packages/rs-drive/src/drive/shielded/prove/mod.rs @@ -3,3 +3,9 @@ pub mod prove_nullifiers_trunk_query; /// Proves nullifiers using a branch chunk query. pub mod prove_nullifiers_branch_query; + +/// Proves compacted nullifier changes starting from a given block height. +pub mod prove_compacted_nullifier_changes; + +/// Proves recent nullifier changes starting from a given block height. +pub mod prove_recent_nullifier_changes; diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/mod.rs new file mode 100644 index 00000000000..11f1df609f7 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/mod.rs @@ -0,0 +1,48 @@ +//! Proves compacted nullifier changes starting from a given block height. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Proves compacted nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_block_height` - The block height to start from + /// * `limit` - Optional maximum number of compacted entries to prove + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A grovedb proof + pub fn prove_compacted_nullifier_changes( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.prove_compacted_nullifier_changes_v0( + start_block_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_compacted_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/v0/mod.rs new file mode 100644 index 00000000000..5b3fd7de66f --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_compacted_nullifier_changes/v0/mod.rs @@ -0,0 +1,93 @@ +use crate::drive::shielded::nullifiers::queries::shielded_compacted_nullifiers_path_vec; +use crate::drive::Drive; +use crate::error::Error; +use dpp::ProtocolError; +use grovedb::query_result_type::QueryResultType; +use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation for proving compacted nullifier changes. + /// + /// Uses a two-step approach: + /// 1. First query (non-proving): descending to find any range containing start_block_height + /// 2. Second query (proving): ascending from the found start_block or start_block_height + /// + /// This ensures the proof covers all relevant ranges efficiently. + pub(super) fn prove_compacted_nullifier_changes_v0( + &self, + start_block_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path = shielded_compacted_nullifiers_path_vec(); + + // Step 1: Non-proving descending query to find any range containing start_block_height + let mut desc_end_key = Vec::with_capacity(16); + desc_end_key.extend_from_slice(&start_block_height.to_be_bytes()); + desc_end_key.extend_from_slice(&u64::MAX.to_be_bytes()); + + let mut desc_query = Query::new_with_direction(false); // descending + desc_query.insert_range_to_inclusive(..=desc_end_key); + + let desc_path_query = + PathQuery::new(path.clone(), SizedQuery::new(desc_query, Some(1), None)); + + let (desc_results, _) = self.grove_get_path_query( + &desc_path_query, + transaction, + QueryResultType::QueryKeyElementPairResultType, + &mut vec![], + &platform_version.drive, + )?; + + // Determine the actual start key for the proved query + // If we found a containing range, use its exact key + // Otherwise use (start_block_height, start_block_height) since end_block >= start_block always + let start_key = if let Some((key, _)) = desc_results.to_key_elements().into_iter().next() { + if key.len() == 16 { + let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); + // If this range contains start_block_height, use its exact key + if end_block >= start_block_height { + key + } else { + // No containing range, use (start_block_height, start_block_height) + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + } + } else { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "invalid compacted block key length, expected 16 bytes".to_string(), + ), + ))); + } + } else { + let mut key = Vec::with_capacity(16); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key.extend_from_slice(&start_block_height.to_be_bytes()); + key + }; + + // Step 2: Proved ascending query from start_key + + let mut query = Query::new(); + query.insert_range_from(start_key..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + self.grove_get_proved_path_query( + &path_query, + transaction, + &mut vec![], + &platform_version.drive, + ) + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/mod.rs new file mode 100644 index 00000000000..1b91ef3f9aa --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/mod.rs @@ -0,0 +1,48 @@ +//! Proves recent nullifier changes starting from a given block height. + +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Proves recent nullifier changes starting from a given block height. + /// + /// # Arguments + /// * `start_height` - The block height to start from + /// * `limit` - Optional maximum number of blocks to prove + /// * `transaction` - Optional database transaction + /// * `platform_version` - The platform version + /// + /// # Returns + /// A grovedb proof + pub fn prove_recent_nullifier_changes( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .saved_block_transactions + .fetch_nullifiers + { + 0 => self.prove_recent_nullifier_changes_v0( + start_height, + limit, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_recent_nullifier_changes".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/v0/mod.rs new file mode 100644 index 00000000000..750e1452668 --- /dev/null +++ b/packages/rs-drive/src/drive/shielded/prove/prove_recent_nullifier_changes/v0/mod.rs @@ -0,0 +1,31 @@ +use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; +use crate::drive::Drive; +use crate::error::Error; +use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use platform_version::version::PlatformVersion; + +impl Drive { + /// Version 0 implementation for proving nullifier changes from a start height. + pub(super) fn prove_recent_nullifier_changes_v0( + &self, + start_height: u64, + limit: Option, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let path = shielded_recent_nullifiers_path_vec(); + + // Create a range query starting from the specified height + let mut query = Query::new(); + query.insert_range_from(start_height.to_be_bytes().to_vec()..); + + let path_query = PathQuery::new(path, SizedQuery::new(query, limit, None)); + + self.grove_get_proved_path_query( + &path_query, + transaction, + &mut vec![], + &platform_version.drive, + ) + } +} From a8de751e8d3d8e297490e5ece7f7e243373cd0a7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 17:40:01 +0700 Subject: [PATCH 19/25] fix(drive): adapt proof verification for shielded pool tree structure changes Shielded pool subtrees under AddressBalances changed root-level Merk hashes, causing proof size increases (+1 byte) and succinctness check failures. Use verify_subset_query/verify_subset_of_proof where proofs contain extra lower layers for sibling subtrees. Co-Authored-By: Claude Opus 4.6 --- .../verify_state_transitions.rs | 8 +++-- .../src/drive/initialization/v0/mod.rs | 32 +++++++++---------- .../v0/mod.rs | 1 + .../fetch_compacted_nullifiers/v0/mod.rs | 1 - .../nullifiers/fetch_nullifiers/v0/mod.rs | 1 - .../v0/mod.rs | 8 +++-- .../verify_finalized_epoch_infos/v0/mod.rs | 9 ++++-- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs index 09be30ab6c5..ac7d10068a1 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs @@ -797,10 +797,12 @@ pub(crate) fn verify_state_transitions_were_or_were_not_executed( } StateTransitionAction::IdentityTopUpAction(identity_top_up_transition) => { // we expect to get an identity that matches the state transition + // Use verify_subset_of_proof=true because the response proof is a merged + // proof covering both revision (Identities tree) and balance (Balances tree) let (root_hash, balance) = Drive::verify_identity_balance_for_identity_id( &response_proof.grovedb_proof, identity_top_up_transition.identity_id().into_buffer(), - false, + true, platform_version, ) .expect("expected to verify balance identity for top up"); @@ -828,12 +830,14 @@ pub(crate) fn verify_state_transitions_were_or_were_not_executed( ) => { // todo: we should also verify the document // we expect to get an identity that matches the state transition + // Use verify_subset_of_proof=true because GroveDB proofs may include + // lower layers for sibling subtrees at the root level let (root_hash, balance) = Drive::verify_identity_balance_for_identity_id( &response_proof.grovedb_proof, identity_credit_withdrawal_transition .identity_id() .into_buffer(), - false, + true, platform_version, ) .expect("expected to verify balance identity for withdrawal"); diff --git a/packages/rs-drive/src/drive/initialization/v0/mod.rs b/packages/rs-drive/src/drive/initialization/v0/mod.rs index f502bc2a843..5a0b9e04b87 100644 --- a/packages/rs-drive/src/drive/initialization/v0/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v0/mod.rs @@ -943,7 +943,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 112); //it + left + right + assert_eq!(proof.len(), 113); //it + left + right // Merk Level 1 let mut query = Query::new(); @@ -965,7 +965,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 180); //it + left + right + parent + parent other + assert_eq!(proof.len(), 181); //it + left + right + parent + parent other let mut query = Query::new(); query.insert_key(vec![RootTree::Balances as u8]); @@ -986,7 +986,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 181); //it + left + right + parent + parent other + assert_eq!(proof.len(), 182); //it + left + right + parent + parent other // Merk Level 2 let mut query = Query::new(); @@ -1008,7 +1008,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::Pools as u8]); @@ -1029,7 +1029,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 252); //it + left + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 253); //it + left + parent + sibling + parent sibling + grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::WithdrawalTransactions as u8]); @@ -1050,7 +1050,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::Votes as u8]); @@ -1071,7 +1071,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent // Merk Level 3 @@ -1094,7 +1094,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 249); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![ @@ -1117,7 +1117,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 249); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::PreFundedSpecializedBalances as u8]); @@ -1138,7 +1138,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 287); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 288); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::AddressBalances as u8]); @@ -1159,7 +1159,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 252); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::SpentAssetLockTransactions as u8]); @@ -1180,7 +1180,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 249); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::GroupActions as u8]); @@ -1201,7 +1201,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 249); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::Misc as u8]); @@ -1222,7 +1222,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 250); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent let mut query = Query::new(); query.insert_key(vec![RootTree::Versions as u8]); @@ -1243,7 +1243,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 250); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent // Merk Level 4 @@ -1266,6 +1266,6 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 286); //it + parent + parent sibling + grandparent + grandparent sibling + great-grandparent + great-grandparent sibling + great-great-grandparent + assert_eq!(proof.len(), 287); //it + parent + parent sibling + grandparent + grandparent sibling + great-grandparent + great-grandparent sibling + great-great-grandparent } } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs index 10cb0bd3d82..8cee05484d6 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/cleanup_expired_nullifier_compactions/v0/mod.rs @@ -22,6 +22,7 @@ impl Drive { /// 2048 nullifiers. With 3s blocks (201,600 blocks/week) and 1-week expiry: /// - Normal load (~1 shielded TPS): ~3,150 entries/week /// - Extreme load (683 shielded TPS, compaction every block): ~201,600 entries/week + /// /// Both are well within GroveDB query capacity. pub(in crate::drive) fn cleanup_expired_nullifier_compactions_v0( &self, diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs index 99900d2ccf9..3fb864ccea0 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs @@ -184,5 +184,4 @@ impl Drive { Ok(compacted_changes) } - } diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs index 5ecdd78445a..1229e4df4ef 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs @@ -66,5 +66,4 @@ impl Drive { Ok(nullifier_changes) } - } diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index e92f57ab44d..94f12fd853c 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -960,12 +960,14 @@ impl Drive { } StateTransition::IdentityCreditTransferToAddresses(st) => { let identity_id = st.identity_id(); + // verify_subset_of_proof=true because we verify identity balance/revision + // and address balances as separate subsets of the same merged proof let (root_hash_identity, Some((balance, revision)), address_balances) = Drive::verify_identity_balance_revision_and_addresses_from_inputs( proof, identity_id.to_buffer(), st.recipient_addresses().keys(), - false, + true, platform_version, )? else { @@ -1039,12 +1041,14 @@ impl Drive { .inputs() .keys() .chain(st.output().into_iter().map(|(address, _)| address)); + // verify_subset_of_proof=true because we verify identity balance/revision + // and address balances as separate subsets of the same merged proof let (root_hash_identity, Some((balance, revision)), address_balances) = Drive::verify_identity_balance_revision_and_addresses_from_inputs( proof, identity_id.to_buffer(), addresses_to_check, - false, + true, platform_version, )? else { diff --git a/packages/rs-drive/src/verify/system/verify_finalized_epoch_infos/v0/mod.rs b/packages/rs-drive/src/verify/system/verify_finalized_epoch_infos/v0/mod.rs index 8a1f24f70d2..2c74c561690 100644 --- a/packages/rs-drive/src/verify/system/verify_finalized_epoch_infos/v0/mod.rs +++ b/packages/rs-drive/src/verify/system/verify_finalized_epoch_infos/v0/mod.rs @@ -57,8 +57,13 @@ impl Drive { ))); }; - let (root_hash, elements) = - GroveDb::verify_query(proof, &path_query, &platform_version.drive.grove_version)?; + // Use verify_subset_query because the proof may contain extra lower layers + // for sibling subtrees at the root level (e.g., shielded pool subtrees) + let (root_hash, elements) = GroveDb::verify_subset_query( + proof, + &path_query, + &platform_version.drive.grove_version, + )?; let results = elements.into_iter().fold( BTreeMap::<_, BTreeMap<_, _>>::new(), From 4d1339702e8e52b961dd1f80efcdb2121eb20411 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 18:55:21 +0700 Subject: [PATCH 20/25] refactor(drive): upgrade grovedb, fix estimated costs, and clean up dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upgrade grovedb to a7b878f72279a4893a7e9dc0ab2973ca0b09ce69 - Add protocol version 11 proof size test - Fix shielded pool estimated costs: EstimatedLevel(7→3) for fixed 7-element tree, include CommitmentTree in non_sum_trees_weight - Use checked_sub instead of saturating_sub in penalize_shielded_pool to fail on balance underflow instead of silently clamping - Remove unused batch_insert_auto_incremented_items_in_count_tree - Remove unused transaction param from DriveOperationFinalizeTask::execute - Add doc comments to drive version v7 Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 52 +-- packages/rs-drive/Cargo.toml | 12 +- .../src/drive/initialization/v0/mod.rs | 352 ++++++++++++++++++ .../src/drive/shielded/estimated_costs.rs | 5 +- .../system/penalize_shielded_pool.rs | 10 +- .../apply_drive_operations/v0/mod.rs | 2 +- .../batch/drive_op_batch/finalize_task.rs | 2 - .../mod.rs | 44 --- .../v0/mod.rs | 66 ---- .../rs-drive/src/util/grove_operations/mod.rs | 2 - packages/rs-platform-version/Cargo.toml | 2 +- .../drive_grove_method_versions/mod.rs | 1 - .../drive_grove_method_versions/v1.rs | 1 - .../src/version/drive_versions/v7.rs | 3 +- 14 files changed, 400 insertions(+), 154 deletions(-) delete mode 100644 packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs delete mode 100644 packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs diff --git a/Cargo.lock b/Cargo.lock index de576327fc6..2d1533fd6a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2033,7 +2033,7 @@ dependencies = [ "dpp", "enum-map", "grovedb", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-epoch-based-storage-flags", "grovedb-path", "grovedb-storage", @@ -2727,15 +2727,15 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", "grovedb-bulk-append-tree", - "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", @@ -2765,11 +2765,11 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-merkle-mountain-range", "grovedb-query", @@ -2793,11 +2793,11 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "blake3", "grovedb-bulk-append-tree", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-storage", "incrementalmerkletree", "orchard", @@ -2817,7 +2817,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "integer-encoding", "intmap", @@ -2827,11 +2827,11 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-query", "grovedb-storage", "thiserror 2.0.18", @@ -2840,7 +2840,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "bincode_derive", @@ -2855,9 +2855,9 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "hex", "integer-encoding", "intmap", @@ -2867,7 +2867,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "bincode_derive", @@ -2875,7 +2875,7 @@ dependencies = [ "byteorder", "colored", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-element", "grovedb-path", "grovedb-query", @@ -2893,18 +2893,18 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-storage", ] [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "hex", ] @@ -2912,12 +2912,12 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "bincode", "byteorder", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-storage", "hex", "indexmap 2.13.0", @@ -2928,10 +2928,10 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", "grovedb-path", "grovedb-visualize", "hex", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2956,7 +2956,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "hex", "itertools 0.14.0", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=99a2ab0187b6a5f05c5bed7b4882646bf652f05f#99a2ab0187b6a5f05c5bed7b4882646bf652f05f" +source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" dependencies = [ "serde", "serde_with 3.17.0", diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 5c081c3dec8..2e7d036d953 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/initialization/v0/mod.rs b/packages/rs-drive/src/drive/initialization/v0/mod.rs index 5a0b9e04b87..7b4243ace3b 100644 --- a/packages/rs-drive/src/drive/initialization/v0/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v0/mod.rs @@ -916,6 +916,358 @@ mod tests { assert_eq!(proof.len(), 250); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent } + #[test] + fn test_initial_state_structure_proper_heights_in_protocol_version_11() { + let platform_version = PlatformVersion::get(11).unwrap(); + let drive = setup_drive_with_initial_state_structure(Some(platform_version)); + let drive_version = &platform_version.drive; + + // Merk Level 0 + let mut query = Query::new(); + query.insert_key(vec![RootTree::DataContractDocuments as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 112); //it + left + right + + // Merk Level 1 + let mut query = Query::new(); + query.insert_key(vec![RootTree::Identities as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 180); //it + left + right + parent + parent other + + let mut query = Query::new(); + query.insert_key(vec![RootTree::Balances as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 181); //it + left + right + parent + parent other + + // Merk Level 2 + let mut query = Query::new(); + query.insert_key(vec![RootTree::Tokens as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::Pools as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 252); //it + left + parent + sibling + parent sibling + grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::WithdrawalTransactions as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::Votes as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 250); //it + left + right + parent + sibling + parent sibling + grandparent + + // Merk Level 3 + + let mut query = Query::new(); + query.insert_key(vec![RootTree::UniquePublicKeyHashesToIdentities as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![ + RootTree::NonUniquePublicKeyKeyHashesToIdentities as u8, + ]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::PreFundedSpecializedBalances as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 287); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::AddressBalances as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::SpentAssetLockTransactions as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::GroupActions as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 248); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::Misc as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 250); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + let mut query = Query::new(); + query.insert_key(vec![RootTree::Versions as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 250); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + + // Merk Level 4 + + let mut query = Query::new(); + query.insert_key(vec![RootTree::SavedBlockTransactions as u8]); + let root_path_query = PathQuery::new( + vec![], + SizedQuery { + query, + limit: None, + offset: None, + }, + ); + let mut drive_operations = vec![]; + let proof = drive + .grove_get_proved_path_query( + &root_path_query, + None, + &mut drive_operations, + drive_version, + ) + .expect("expected to get root elements"); + assert_eq!(proof.len(), 286); //it + parent + parent sibling + grandparent + grandparent sibling + great-grandparent + great-grandparent sibling + great-great-grandparent + } + #[test] fn test_initial_state_structure_proper_heights_in_latest_protocol_version() { let drive = setup_drive_with_initial_state_structure(None); diff --git a/packages/rs-drive/src/drive/shielded/estimated_costs.rs b/packages/rs-drive/src/drive/shielded/estimated_costs.rs index a44133b8a9a..87de335bfd1 100644 --- a/packages/rs-drive/src/drive/shielded/estimated_costs.rs +++ b/packages/rs-drive/src/drive/shielded/estimated_costs.rs @@ -78,11 +78,12 @@ impl Drive { // SumTree containing: notes (CommitmentTree), permanent nullifiers (ProvableCountTree), // total balance (SumItem), anchors (NormalTree), recent nullifiers (CountSumTree), // compacted nullifiers (NormalTree), expiration time (NormalTree) + // 7 elements total (6 subtrees + 1 item) → balanced Merk depth = ceil(log2(8)) = 3 estimated_costs_only_with_layer_info.insert( KeyInfoPath::from_known_path(shielded_credit_pool_path()), EstimatedLayerInformation { tree_type: TreeType::SumTree, - estimated_layer_count: EstimatedLevel(7, false), + estimated_layer_count: EstimatedLevel(3, false), estimated_layer_sizes: Mix { subtrees_size: Some(( 1, @@ -91,7 +92,7 @@ impl Drive { big_sum_trees_weight: 0, count_trees_weight: 1, // permanent nullifiers (ProvableCountTree) count_sum_trees_weight: 1, // recent nullifiers (CountSumTree) - non_sum_trees_weight: 3, // anchors, compacted nullifiers, expiration time + non_sum_trees_weight: 4, // notes (CommitmentTree), anchors, compacted nullifiers, expiration time }, None, 6, // 6 subtrees: notes, permanent nullifiers, anchors, recent nullifiers, compacted nullifiers, expiration time diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs index d9d39fd9933..fee9484df0f 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs @@ -1,3 +1,4 @@ +use crate::error::drive::DriveError; use crate::error::Error; use crate::state_transition_action::action_convert_to_operations::DriveHighLevelOperationConverter; use crate::state_transition_action::system::penalize_shielded_pool_action::PenalizeShieldedPoolAction; @@ -26,7 +27,14 @@ impl DriveHighLevelOperationConverter for PenalizeShieldedPoolAction { } // 2. Deduct penalty from pool total balance - let new_total_balance = v0.current_total_balance.saturating_sub(v0.penalty_amount); + let new_total_balance = + v0.current_total_balance + .checked_sub(v0.penalty_amount) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance underflow when subtracting penalty_amount".to_string(), + )) + })?; ops.push(DriveOperation::ShieldedPoolOperation( ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance }, )); diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs b/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs index e67d93e0532..e8a0ccc7206 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/drive_methods/apply_drive_operations/v0/mod.rs @@ -96,7 +96,7 @@ impl Drive { // Execute drive operation callbacks after updating state for task in finalize_tasks { - task.execute(self, transaction, platform_version)?; + task.execute(self, platform_version)?; } Drive::calculate_fee( diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs index c98b00a1a6f..b6d1e5d3d5e 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs @@ -2,7 +2,6 @@ use crate::drive::Drive; use crate::error::Error; use dpp::prelude::Identifier; use dpp::version::PlatformVersion; -use grovedb::TransactionArg; #[derive(Clone, Debug)] pub enum DriveOperationFinalizeTask { @@ -22,7 +21,6 @@ impl DriveOperationFinalizeTask { pub fn execute( self, drive: &Drive, - _transaction: TransactionArg, _platform_version: &PlatformVersion, ) -> Result<(), Error> { match self { diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs deleted file mode 100644 index 588876aeb4c..00000000000 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -mod v0; - -use crate::drive::Drive; -use crate::error::drive::DriveError; -use crate::error::Error; -use crate::fees::op::LowLevelDriveOperation; -use dpp::version::drive_versions::DriveVersion; -use grovedb::{Element, TransactionArg}; - -impl Drive { - /// Inserts multiple items into a count tree with auto-incremented u64 big-endian keys. - /// - /// Reads the current count from the count tree, then generates sequential insert - /// operations starting from that count. - pub fn batch_insert_auto_incremented_items_in_count_tree( - &self, - parent_path: Vec>, - count_tree_key: &[u8], - items: Vec, - transaction: TransactionArg, - drive_operations: &mut Vec, - drive_version: &DriveVersion, - ) -> Result<(), Error> { - match drive_version - .grove_methods - .batch - .batch_insert_auto_incremented_items_in_count_tree - { - 0 => self.batch_insert_auto_incremented_items_in_count_tree_v0( - parent_path, - count_tree_key, - items, - transaction, - drive_operations, - drive_version, - ), - version => Err(Error::Drive(DriveError::UnknownVersionMismatch { - method: "batch_insert_auto_incremented_items_in_count_tree".to_string(), - known_versions: vec![0], - received: version, - })), - } - } -} diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs deleted file mode 100644 index b762e93ee7c..00000000000 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_auto_incremented_items_in_count_tree/v0/mod.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::drive::Drive; -use crate::error::drive::DriveError; -use crate::error::Error; -use crate::fees::op::LowLevelDriveOperation; -use crate::util::grove_operations::DirectQueryType; -use dpp::version::drive_versions::DriveVersion; -use grovedb::{Element, TransactionArg}; - -impl Drive { - /// Inserts multiple items into a count tree with auto-incremented u64 big-endian keys. - /// - /// Reads the current count from the count tree, then generates sequential insert - /// operations starting from that count. - /// - /// # Parameters - /// * `parent_path` - Path to the parent tree containing the count tree - /// * `count_tree_key` - Key of the count tree under the parent path - /// * `items` - Items (Elements) to insert with auto-incremented keys - /// * `transaction` - GroveDB transaction - /// * `drive_operations` - Accumulator for low-level drive operations - /// * `drive_version` - Drive version for method dispatch - pub(crate) fn batch_insert_auto_incremented_items_in_count_tree_v0( - &self, - parent_path: Vec>, - count_tree_key: &[u8], - items: Vec, - transaction: TransactionArg, - drive_operations: &mut Vec, - drive_version: &DriveVersion, - ) -> Result<(), Error> { - if items.is_empty() { - return Ok(()); - } - - // Read the current count from the count tree element - let current_count = self - .grove_get_raw_optional( - parent_path.as_slice().into(), - count_tree_key, - DirectQueryType::StatefulDirectQuery, - transaction, - drive_operations, - drive_version, - )? - .map(|element| element.count_value_or_default()) - .unwrap_or(0); - - // Build the full path to the count tree (parent_path + count_tree_key) - let mut insert_path = parent_path; - insert_path.push(count_tree_key.to_vec()); - - // Insert each item with a sequential key - for (i, element) in items.into_iter().enumerate() { - let index = current_count.checked_add(i as u64).ok_or(Error::Drive( - DriveError::CorruptedCodeExecution("auto-incremented count tree index overflow"), - ))?; - drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element( - insert_path.clone(), - index.to_be_bytes().to_vec(), - element, - )); - } - - Ok(()) - } -} diff --git a/packages/rs-drive/src/util/grove_operations/mod.rs b/packages/rs-drive/src/util/grove_operations/mod.rs index 80dd3830ca4..dfbdeec4d96 100644 --- a/packages/rs-drive/src/util/grove_operations/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/mod.rs @@ -146,8 +146,6 @@ pub mod batch_insert_sum_item_if_not_exists; /// Moved items that are found in a path query to a new path. pub mod batch_move_items_in_path_query; -/// Batch inserts items into a count tree with auto-incremented keys -pub mod batch_insert_auto_incremented_items_in_count_tree; /// Batch inserts item with sum item if not already existing pub mod batch_insert_item_with_sum_item_if_not_exists; /// Keeps the item, but inserts or adds to the sum item if it already exists diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 324f6d47679..8a1aacfa909 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "99a2ab0187b6a5f05c5bed7b4882646bf652f05f" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } [features] mock-versions = [] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs index 724456b8d14..6ee5751e84a 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs @@ -62,7 +62,6 @@ pub struct DriveGroveBatchMethodVersions { pub batch_insert_empty_sum_tree: FeatureVersion, pub batch_move: FeatureVersion, pub batch_insert_item_with_sum_item_if_not_exists: FeatureVersion, - pub batch_insert_auto_incremented_items_in_count_tree: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs index 9567908e458..324bcf0a6bf 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs @@ -53,7 +53,6 @@ pub const DRIVE_GROVE_METHOD_VERSIONS_V1: DriveGroveMethodVersions = DriveGroveM batch_insert_empty_sum_tree: 0, batch_move: 0, batch_insert_item_with_sum_item_if_not_exists: 0, - batch_insert_auto_incremented_items_in_count_tree: 0, }, apply: DriveGroveApplyMethodVersions { grove_apply_operation: 0, diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs index 35d832ea801..d27fcfa4f22 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -22,6 +22,7 @@ use crate::version::drive_versions::{ }; use grovedb_version::version::v3::GROVE_V3; +/// Drive version 7. /// This was introduced in protocol v12 for shielded transactions. pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { structure: DRIVE_STRUCTURE_V1, @@ -133,5 +134,5 @@ pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, - grove_version: GROVE_V3, + grove_version: GROVE_V3, // changed in v7: upgraded for shielded transaction support }; From 0f5acd5ad340b60a7cccb3bdc84f74fd21b994b5 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 9 Mar 2026 19:19:23 +0700 Subject: [PATCH 21/25] refactor(drive): replace nullifier tuple type aliases with structs Replace raw tuple type aliases (CompactedNullifierChanges, NullifierChangesPerBlock, etc.) with proper structs (CompactedNullifierChange, NullifierChangePerBlock) that wrap CompactedNullifiers instead of Vec<[u8; 32]>. Centralizes the duplicated 16-byte key parsing logic into CompactedNullifierChange::parse_key() and uses CompactedNullifiers::decode() in verify modules instead of inline bincode deserialization. Co-Authored-By: Claude Opus 4.6 --- .../fetch_compacted_nullifiers/mod.rs | 13 +--- .../fetch_compacted_nullifiers/v0/mod.rs | 64 +++++-------------- .../nullifiers/fetch_nullifiers/mod.rs | 13 +--- .../nullifiers/fetch_nullifiers/v0/mod.rs | 14 ++-- .../src/drive/shielded/nullifiers/mod.rs | 1 - .../src/drive/shielded/nullifiers/types.rs | 44 +++++++++++++ .../verify_compacted_nullifier_changes/mod.rs | 22 +------ .../v0/mod.rs | 48 +++----------- .../verify_recent_nullifier_changes/mod.rs | 21 +----- .../verify_recent_nullifier_changes/v0/mod.rs | 30 ++------- 10 files changed, 92 insertions(+), 178 deletions(-) diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs index 3cddb7cde33..5b31db5e56e 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/mod.rs @@ -6,26 +6,17 @@ use crate::error::Error; use grovedb::TransactionArg; use platform_version::version::PlatformVersion; -pub use v0::CompactedNullifierChanges; +use crate::drive::shielded::nullifiers::types::CompactedNullifierChange; impl Drive { /// Fetches compacted nullifier changes starting from a given block height. - /// - /// # Arguments - /// * `start_block_height` - The block height to start fetching from - /// * `limit` - Optional maximum number of compacted entries to return - /// * `transaction` - Optional database transaction - /// * `platform_version` - The platform version - /// - /// # Returns - /// A vector of (start_block, end_block, nullifiers) tuples pub fn fetch_compacted_nullifier_changes( &self, start_block_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, - ) -> Result { + ) -> Result, Error> { match platform_version .drive .methods diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs index 3fb864ccea0..c3a40612afd 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_compacted_nullifiers/v0/mod.rs @@ -1,5 +1,5 @@ use crate::drive::shielded::nullifiers::queries::shielded_compacted_nullifiers_path_vec; -use crate::drive::shielded::nullifiers::types::CompactedNullifiers; +use crate::drive::shielded::nullifiers::types::{CompactedNullifierChange, CompactedNullifiers}; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -7,10 +7,6 @@ use grovedb::query_result_type::QueryResultType; use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; use platform_version::version::PlatformVersion; -/// Result type for fetched compacted nullifier changes -/// Each entry is (start_block, end_block, nullifiers) -pub type CompactedNullifierChanges = Vec<(u64, u64, Vec<[u8; 32]>)>; - impl Drive { /// Version 0 implementation of fetching compacted nullifier changes. /// @@ -25,7 +21,7 @@ impl Drive { limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, - ) -> Result { + ) -> Result, Error> { let path = shielded_compacted_nullifiers_path_vec(); // Keys are 16 bytes: (start_block, end_block), both big-endian. @@ -69,24 +65,7 @@ impl Drive { // Check if we found a range that contains start_block_height if let Some((key, element)) = desc_results.to_key_elements().into_iter().next() { - if key.len() != 16 { - return Err(Error::Protocol(Box::new( - ProtocolError::CorruptedSerialization( - "invalid compacted block key length, expected 16 bytes".to_string(), - ), - ))); - } - - let start_block = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?); - let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?); + let (start_block, end_block) = CompactedNullifierChange::parse_key(&key)?; // Only include if end_block >= start_block_height (range contains our block) if end_block >= start_block_height { @@ -98,9 +77,13 @@ impl Drive { ))); }; - let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); + let nullifiers = CompactedNullifiers::decode(&serialized_data)?; - compacted_changes.push((start_block, end_block, nullifiers)); + compacted_changes.push(CompactedNullifierChange { + start_block, + end_block, + nullifiers, + }); } } @@ -135,7 +118,7 @@ impl Drive { // Track the (start_block, end_block) from descending query to avoid duplicates let desc_range_key = compacted_changes .first() - .map(|(start, end, _)| (*start, *end)); + .map(|c| (c.start_block, c.end_block)); for (key, element) in asc_results.to_key_elements() { // Check if we've reached the limit @@ -145,24 +128,7 @@ impl Drive { } } - if key.len() != 16 { - return Err(Error::Protocol(Box::new( - ProtocolError::CorruptedSerialization( - "invalid compacted block key length, expected 16 bytes".to_string(), - ), - ))); - } - - let start_block = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?); - let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { - Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( - "invalid compacted key slice".to_string(), - ))) - })?); + let (start_block, end_block) = CompactedNullifierChange::parse_key(&key)?; // Skip if this is the same range we got from descending query if Some((start_block, end_block)) == desc_range_key { @@ -177,9 +143,13 @@ impl Drive { ))); }; - let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); + let nullifiers = CompactedNullifiers::decode(&serialized_data)?; - compacted_changes.push((start_block, end_block, nullifiers)); + compacted_changes.push(CompactedNullifierChange { + start_block, + end_block, + nullifiers, + }); } Ok(compacted_changes) diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs index fa661b40d87..edc0b04b4ce 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/mod.rs @@ -6,26 +6,17 @@ use crate::error::Error; use grovedb::TransactionArg; use platform_version::version::PlatformVersion; -pub use v0::NullifierChangesPerBlock; +use crate::drive::shielded::nullifiers::types::NullifierChangePerBlock; impl Drive { /// Fetches recent nullifier changes starting from a given block height. - /// - /// # Arguments - /// * `start_height` - The block height to start fetching from - /// * `limit` - Optional maximum number of blocks to return - /// * `transaction` - Optional database transaction - /// * `platform_version` - The platform version - /// - /// # Returns - /// A vector of (block_height, nullifiers) tuples pub fn fetch_recent_nullifier_changes( &self, start_height: u64, limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, - ) -> Result { + ) -> Result, Error> { match platform_version .drive .methods diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs index 1229e4df4ef..d96d797e0f1 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/fetch_nullifiers/v0/mod.rs @@ -1,5 +1,5 @@ use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; -use crate::drive::shielded::nullifiers::types::CompactedNullifiers; +use crate::drive::shielded::nullifiers::types::{CompactedNullifiers, NullifierChangePerBlock}; use crate::drive::Drive; use crate::error::Error; use dpp::ProtocolError; @@ -7,9 +7,6 @@ use grovedb::query_result_type::QueryResultType; use grovedb::{Element, PathQuery, Query, SizedQuery, TransactionArg}; use platform_version::version::PlatformVersion; -/// Result type for fetched nullifier changes per block -pub type NullifierChangesPerBlock = Vec<(u64, Vec<[u8; 32]>)>; - impl Drive { /// Version 0 implementation of fetching nullifier changes from a start height. /// @@ -21,7 +18,7 @@ impl Drive { limit: Option, transaction: TransactionArg, platform_version: &PlatformVersion, - ) -> Result { + ) -> Result, Error> { let path = shielded_recent_nullifiers_path_vec(); // Create a range query starting from the specified height @@ -59,9 +56,12 @@ impl Drive { }; // Deserialize the nullifier list - let nullifiers = CompactedNullifiers::decode(&serialized_data)?.into_inner(); + let nullifiers = CompactedNullifiers::decode(&serialized_data)?; - nullifier_changes.push((block_height, nullifiers)); + nullifier_changes.push(NullifierChangePerBlock { + block_height, + nullifiers, + }); } Ok(nullifier_changes) diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs index 610cf0e1efa..e104d53f113 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/mod.rs @@ -14,6 +14,5 @@ pub mod queries; #[cfg(feature = "server")] mod store_nullifiers; /// Wrapper types for serialized nullifier data -#[cfg(feature = "server")] pub mod types; pub use queries::*; diff --git a/packages/rs-drive/src/drive/shielded/nullifiers/types.rs b/packages/rs-drive/src/drive/shielded/nullifiers/types.rs index 2359b921816..dc2318e12b8 100644 --- a/packages/rs-drive/src/drive/shielded/nullifiers/types.rs +++ b/packages/rs-drive/src/drive/shielded/nullifiers/types.rs @@ -120,3 +120,47 @@ impl Deref for NullifierExpirationRanges { &self.0 } } + +/// A single compacted nullifier change entry, representing nullifiers from a range of blocks. +/// +/// The key in GroveDB is 16 bytes: `(start_block, end_block)` in big-endian. +pub struct CompactedNullifierChange { + /// First block height in the compacted range. + pub start_block: u64, + /// Last block height in the compacted range. + pub end_block: u64, + /// The nullifiers from this range of blocks. + pub nullifiers: CompactedNullifiers, +} + +impl CompactedNullifierChange { + /// Parses start_block and end_block from a 16-byte big-endian key. + pub fn parse_key(key: &[u8]) -> Result<(u64, u64), Error> { + if key.len() != 16 { + return Err(Error::Protocol(Box::new( + ProtocolError::CorruptedSerialization( + "invalid compacted block key length, expected 16 bytes".to_string(), + ), + ))); + } + let start_block = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); + let end_block = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { + Error::Protocol(Box::new(ProtocolError::CorruptedSerialization( + "invalid compacted key slice".to_string(), + ))) + })?); + Ok((start_block, end_block)) + } +} + +/// A single nullifier change entry for a specific block height. +pub struct NullifierChangePerBlock { + /// The block height this entry belongs to. + pub block_height: u64, + /// The nullifiers added in this block. + pub nullifiers: CompactedNullifiers, +} diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs index 01f5baa98a7..d10383c0cab 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/mod.rs @@ -1,38 +1,20 @@ mod v0; +use crate::drive::shielded::nullifiers::types::CompactedNullifierChange; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use crate::verify::RootHash; use dpp::version::PlatformVersion; -/// Result type for verified compacted nullifier changes -/// Each entry is (start_block, end_block, nullifiers) -pub type VerifiedCompactedNullifierChanges = Vec<(u64, u64, Vec<[u8; 32]>)>; - impl Drive { /// Verifies the proof of compacted nullifier changes starting from a given block height. - /// - /// This method validates and extracts compacted nullifier changes from the provided proof. - /// Compacted entries represent concatenated data from multiple blocks. - /// - /// # Arguments - /// - `proof`: A byte slice containing the cryptographic proof for the compacted nullifier changes. - /// - `start_block_height`: The block height to start verifying from. - /// - `limit`: Optional maximum number of compacted entries to verify. - /// - `platform_version`: A reference to the platform version. - /// - /// # Returns - /// - `Ok((RootHash, VerifiedCompactedNullifierChanges))`: On success, returns: - /// - `RootHash`: The root hash of the Merkle tree. - /// - `VerifiedCompactedNullifierChanges`: Vector of (start_block, end_block, nullifiers) tuples. - /// - `Err(Error)`: If verification fails. pub fn verify_compacted_nullifier_changes( proof: &[u8], start_block_height: u64, limit: Option, platform_version: &PlatformVersion, - ) -> Result<(RootHash, VerifiedCompactedNullifierChanges), Error> { + ) -> Result<(RootHash, Vec), Error> { match platform_version .drive .methods diff --git a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs index 59a2d2fe082..2f089300fe5 100644 --- a/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_compacted_nullifier_changes/v0/mod.rs @@ -1,6 +1,7 @@ use crate::drive::shielded::nullifiers::queries::{ shielded_compacted_nullifiers_path_vec, SHIELDED_COMPACTED_NULLIFIERS_KEY_U8, }; +use crate::drive::shielded::nullifiers::types::{CompactedNullifierChange, CompactedNullifiers}; use crate::drive::shielded::paths::SHIELDED_CREDIT_POOL_KEY_U8; use crate::drive::Drive; use crate::drive::RootTree; @@ -13,8 +14,6 @@ use grovedb::{ }; use platform_version::version::PlatformVersion; -use super::VerifiedCompactedNullifierChanges; - /// Extract KV entries from merk proof bytes using the proper decoder. #[allow(clippy::type_complexity)] fn extract_kv_entries_from_merk_proof(merk_proof: &[u8]) -> Result, Vec)>, Error> { @@ -56,7 +55,7 @@ impl Drive { start_block_height: u64, limit: Option, platform_version: &PlatformVersion, - ) -> Result<(RootHash, VerifiedCompactedNullifierChanges), Error> { + ) -> Result<(RootHash, Vec), Error> { // Decode the GroveDBProof to navigate its structure let grovedb_proof: GroveDBProof = { let config = bincode::config::standard() @@ -173,22 +172,7 @@ impl Drive { continue; }; - if key.len() != 16 { - return Err(Error::Proof(ProofError::CorruptedProof( - "invalid compacted block key length, expected 16 bytes".to_string(), - ))); - } - - let range_start = u64::from_be_bytes(key[0..8].try_into().map_err(|_| { - Error::Proof(ProofError::CorruptedProof( - "invalid key slice length for block height".to_string(), - )) - })?); - let range_end = u64::from_be_bytes(key[8..16].try_into().map_err(|_| { - Error::Proof(ProofError::CorruptedProof( - "invalid key slice length for block height".to_string(), - )) - })?); + let (start_block, end_block) = CompactedNullifierChange::parse_key(&key)?; // Get the serialized data from the Item element let grovedb::Element::Item(serialized_data, _) = element else { @@ -197,27 +181,13 @@ impl Drive { ))); }; - // Deserialize the nullifier list - let nullifier_config = bincode::config::standard() - .with_big_endian() - .with_limit::<{ 128 * 1024 }>(); - let (nullifiers, bytes_read): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, nullifier_config).map_err(|e| { - Error::Proof(ProofError::CorruptedProof(format!( - "cannot decode compacted nullifiers: {}", - e - ))) - })?; - - if bytes_read != serialized_data.len() { - return Err(Error::Proof(ProofError::CorruptedProof(format!( - "trailing bytes after compacted nullifier decode: read {} of {}", - bytes_read, - serialized_data.len() - )))); - } + let nullifiers = CompactedNullifiers::decode(&serialized_data)?; - compacted_changes.push((range_start, range_end, nullifiers)); + compacted_changes.push(CompactedNullifierChange { + start_block, + end_block, + nullifiers, + }); } Ok((root_hash, compacted_changes)) diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs index 72ca1383e87..19033de1489 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/mod.rs @@ -1,38 +1,21 @@ mod v0; +use crate::drive::shielded::nullifiers::types::NullifierChangePerBlock; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; use crate::verify::RootHash; use dpp::version::PlatformVersion; -/// Result type for verified nullifier changes per block -pub type VerifiedNullifierChangesPerBlock = Vec<(u64, Vec<[u8; 32]>)>; - impl Drive { /// Verifies the proof of recent nullifier changes starting from a given block height. - /// - /// This method validates and extracts nullifier changes from the provided proof. - /// - /// # Arguments - /// - `proof`: A byte slice containing the cryptographic proof for the nullifier changes. - /// - `start_block_height`: The block height to start verifying from. - /// - `limit`: Optional maximum number of blocks to verify. - /// - `verify_subset_of_proof`: A boolean flag indicating whether to verify only a subset of the proof. - /// - `platform_version`: A reference to the platform version. - /// - /// # Returns - /// - `Ok((RootHash, VerifiedNullifierChangesPerBlock))`: On success, returns: - /// - `RootHash`: The root hash of the Merkle tree. - /// - `VerifiedNullifierChangesPerBlock`: Vector of (block_height, nullifiers) tuples. - /// - `Err(Error)`: If verification fails. pub fn verify_recent_nullifier_changes( proof: &[u8], start_block_height: u64, limit: Option, verify_subset_of_proof: bool, platform_version: &PlatformVersion, - ) -> Result<(RootHash, VerifiedNullifierChangesPerBlock), Error> { + ) -> Result<(RootHash, Vec), Error> { match platform_version .drive .methods diff --git a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs index 9b93fb21c21..8c9d137c596 100644 --- a/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/shielded/verify_recent_nullifier_changes/v0/mod.rs @@ -1,4 +1,5 @@ use crate::drive::shielded::nullifiers::queries::shielded_recent_nullifiers_path_vec; +use crate::drive::shielded::nullifiers::types::{CompactedNullifiers, NullifierChangePerBlock}; use crate::drive::Drive; use crate::error::proof::ProofError; use crate::error::Error; @@ -6,8 +7,6 @@ use crate::verify::RootHash; use grovedb::{Element, GroveDb, PathQuery, Query, SizedQuery}; use platform_version::version::PlatformVersion; -use super::VerifiedNullifierChangesPerBlock; - impl Drive { /// Verifies recent nullifier changes proof. /// @@ -19,13 +18,9 @@ impl Drive { limit: Option, verify_subset_of_proof: bool, platform_version: &PlatformVersion, - ) -> Result<(RootHash, VerifiedNullifierChangesPerBlock), Error> { + ) -> Result<(RootHash, Vec), Error> { let path = shielded_recent_nullifiers_path_vec(); - let config = bincode::config::standard() - .with_big_endian() - .with_limit::<{ 128 * 1024 }>(); - // Create the same range query as the prove function let mut query = Query::new(); query.insert_range_from(start_block_height.to_be_bytes().to_vec()..); @@ -61,23 +56,12 @@ impl Drive { }; // Deserialize the nullifier list - let (nullifiers, bytes_read): (Vec<[u8; 32]>, usize) = - bincode::decode_from_slice(&serialized_data, config).map_err(|e| { - Error::Proof(ProofError::CorruptedProof(format!( - "cannot decode nullifiers: {}", - e - ))) - })?; - - if bytes_read != serialized_data.len() { - return Err(Error::Proof(ProofError::CorruptedProof(format!( - "trailing bytes after nullifier decode: read {} of {}", - bytes_read, - serialized_data.len() - )))); - } + let nullifiers = CompactedNullifiers::decode(&serialized_data)?; - nullifier_changes.push((block_height, nullifiers)); + nullifier_changes.push(NullifierChangePerBlock { + block_height, + nullifiers, + }); } Ok((root_hash, nullifier_changes)) From 04c537e6ee1708581b5656b5a00a8310e502a0f8 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Tue, 10 Mar 2026 02:05:40 +0700 Subject: [PATCH 22/25] feat(drive): adapt to grovedb SubelementsDeletionBehavior per-op enum (#3217) Co-authored-by: Claude Opus 4.6 Co-authored-by: github-actions[bot] --- .github/grpc-queries-cache.json | 2 +- Cargo.lock | 64 +++++++++---------- packages/rs-drive/Cargo.toml | 12 ++-- .../credit_pools/epochs/operations_factory.rs | 3 +- .../src/drive/initialization/v0/mod.rs | 18 +++--- .../system/penalize_shielded_pool.rs | 17 ++--- .../batch/drive_op_batch/finalize_task.rs | 6 +- .../src/util/batch/grovedb_op_batch/mod.rs | 15 ++++- .../v0/mod.rs | 8 +-- .../batch_remove_raw/v0/mod.rs | 2 +- .../v0/mod.rs | 2 - .../v0/mod.rs | 2 - .../grove_batch_operations_costs/v0/mod.rs | 2 - packages/rs-platform-version/Cargo.toml | 2 +- 14 files changed, 78 insertions(+), 77 deletions(-) diff --git a/.github/grpc-queries-cache.json b/.github/grpc-queries-cache.json index e6ddc5013b4..9612978c5d5 100644 --- a/.github/grpc-queries-cache.json +++ b/.github/grpc-queries-cache.json @@ -161,5 +161,5 @@ "status": "implemented" } }, - "last_updated": "2026-03-05T17:22:30.340784" + "last_updated": "2026-03-09T19:05:06.309464" } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2d1533fd6a0..9991ee19708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2033,7 +2033,7 @@ dependencies = [ "dpp", "enum-map", "grovedb", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-epoch-based-storage-flags", "grovedb-path", "grovedb-storage", @@ -2727,15 +2727,15 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", "grovedb-bulk-append-tree", - "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", @@ -2765,11 +2765,11 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-merkle-mountain-range", "grovedb-query", @@ -2781,10 +2781,12 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", + "grovedb-bulk-append-tree", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-storage", "incrementalmerkletree", "orchard", "thiserror 2.0.18", @@ -2793,12 +2795,10 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "blake3", - "grovedb-bulk-append-tree", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", - "grovedb-storage", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", "incrementalmerkletree", "orchard", "thiserror 2.0.18", @@ -2807,7 +2807,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "integer-encoding", "intmap", @@ -2817,7 +2817,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "integer-encoding", "intmap", @@ -2827,11 +2827,11 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-query", "grovedb-storage", "thiserror 2.0.18", @@ -2840,7 +2840,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "bincode_derive", @@ -2855,9 +2855,9 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "hex", "integer-encoding", "intmap", @@ -2867,7 +2867,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "bincode_derive", @@ -2875,7 +2875,7 @@ dependencies = [ "byteorder", "colored", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-element", "grovedb-path", "grovedb-query", @@ -2893,18 +2893,18 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-storage", ] [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "hex", ] @@ -2912,12 +2912,12 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "bincode", "byteorder", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-storage", "hex", "indexmap 2.13.0", @@ -2928,10 +2928,10 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69)", + "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", "grovedb-path", "grovedb-visualize", "hex", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2956,7 +2956,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "hex", "itertools 0.14.0", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a7b878f72279a4893a7e9dc0ab2973ca0b09ce69#a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" +source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" dependencies = [ "serde", "serde_with 3.17.0", @@ -6061,7 +6061,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 2e7d036d953..5540ff48d5b 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/credit_pools/epochs/operations_factory.rs b/packages/rs-drive/src/drive/credit_pools/epochs/operations_factory.rs index 738cab59ea5..a3396005212 100644 --- a/packages/rs-drive/src/drive/credit_pools/epochs/operations_factory.rs +++ b/packages/rs-drive/src/drive/credit_pools/epochs/operations_factory.rs @@ -20,7 +20,7 @@ use dpp::fee::Credits; use dpp::prelude::Identifier; use dpp::util::deserializer::ProtocolVersion; use dpp::version::PlatformVersion; -use grovedb::batch::QualifiedGroveDbOp; +use grovedb::batch::{QualifiedGroveDbOp, SubelementsDeletionBehavior}; use grovedb::{Element, TransactionArg, TreeType}; /// Operations on Epochs @@ -297,6 +297,7 @@ impl EpochOperations for Epoch { self.get_path_vec(), KEY_PROPOSERS.to_vec(), TreeType::NormalTree, + SubelementsDeletionBehavior::DeleteChildren, ) } diff --git a/packages/rs-drive/src/drive/initialization/v0/mod.rs b/packages/rs-drive/src/drive/initialization/v0/mod.rs index 7b4243ace3b..148a47f7336 100644 --- a/packages/rs-drive/src/drive/initialization/v0/mod.rs +++ b/packages/rs-drive/src/drive/initialization/v0/mod.rs @@ -1360,7 +1360,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 286); //it + left + right + parent + sibling + parent sibling + grandparent (was 251, +35 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::Pools as u8]); @@ -1381,7 +1381,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 253); //it + left + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 288); //it + left + parent + sibling + parent sibling + grandparent (was 253, +35 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::WithdrawalTransactions as u8]); @@ -1402,7 +1402,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 286); //it + left + right + parent + sibling + parent sibling + grandparent (was 251, +35 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::Votes as u8]); @@ -1423,7 +1423,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + left + right + parent + sibling + parent sibling + grandparent + assert_eq!(proof.len(), 286); //it + left + right + parent + sibling + parent sibling + grandparent (was 251, +35 from KVValueHashFeatureTypeWithChildHash) // Merk Level 3 @@ -1490,7 +1490,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 288); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 321); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent (was 288, +33 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::AddressBalances as u8]); @@ -1511,7 +1511,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 252); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 287); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent (was 252, +35 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::SpentAssetLockTransactions as u8]); @@ -1574,7 +1574,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 286); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent (was 251, +35 from KVValueHashFeatureTypeWithChildHash) let mut query = Query::new(); query.insert_key(vec![RootTree::Versions as u8]); @@ -1595,7 +1595,7 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 251); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent + assert_eq!(proof.len(), 286); //it + parent + sibling + parent sibling + grandparent + grandparent sibling + great-grandparent (was 251, +35 from KVValueHashFeatureTypeWithChildHash) // Merk Level 4 @@ -1618,6 +1618,6 @@ mod tests { drive_version, ) .expect("expected to get root elements"); - assert_eq!(proof.len(), 287); //it + parent + parent sibling + grandparent + grandparent sibling + great-grandparent + great-grandparent sibling + great-great-grandparent + assert_eq!(proof.len(), 320); //it + parent + parent sibling + grandparent + grandparent sibling + great-grandparent + great-grandparent sibling + great-great-grandparent (was 287, +33 from KVValueHashFeatureTypeWithChildHash) } } diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs index fee9484df0f..29a3a9d1437 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/system/penalize_shielded_pool.rs @@ -27,14 +27,15 @@ impl DriveHighLevelOperationConverter for PenalizeShieldedPoolAction { } // 2. Deduct penalty from pool total balance - let new_total_balance = - v0.current_total_balance - .checked_sub(v0.penalty_amount) - .ok_or_else(|| { - Error::Drive(DriveError::CorruptedDriveState( - "shielded pool total balance underflow when subtracting penalty_amount".to_string(), - )) - })?; + let new_total_balance = v0 + .current_total_balance + .checked_sub(v0.penalty_amount) + .ok_or_else(|| { + Error::Drive(DriveError::CorruptedDriveState( + "shielded pool total balance underflow when subtracting penalty_amount" + .to_string(), + )) + })?; ops.push(DriveOperation::ShieldedPoolOperation( ShieldedPoolOperationType::UpdateTotalBalance { new_total_balance }, )); diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs index b6d1e5d3d5e..4c85892dcb3 100644 --- a/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs +++ b/packages/rs-drive/src/util/batch/drive_op_batch/finalize_task.rs @@ -18,11 +18,7 @@ pub trait DriveOperationFinalizationTasks { } impl DriveOperationFinalizeTask { - pub fn execute( - self, - drive: &Drive, - _platform_version: &PlatformVersion, - ) -> Result<(), Error> { + pub fn execute(self, drive: &Drive, _platform_version: &PlatformVersion) -> Result<(), Error> { match self { DriveOperationFinalizeTask::RemoveDataContractFromCache { contract_id } => { drive.cache.data_contracts.remove(contract_id.to_buffer()); diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index 5c95b248bdd..6b1f8a2c60c 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -16,7 +16,10 @@ use dpp::block::epoch::Epoch; use dpp::identity::{Purpose, SecurityLevel}; use dpp::prelude::Identifier; use grovedb::batch::key_info::KeyInfo; -use grovedb::batch::{GroveDbOpConsistencyResults, GroveOp, KeyInfoPath, QualifiedGroveDbOp}; +use grovedb::batch::{ + GroveDbOpConsistencyResults, GroveOp, KeyInfoPath, QualifiedGroveDbOp, + SubelementsDeletionBehavior, +}; use grovedb::operations::proof::util::hex_to_ascii; use grovedb::{Element, TreeType}; use std::borrow::Cow; @@ -590,9 +593,15 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { } /// Adds a `Delete` tree operation to a list of GroveDB ops. + /// Uses `DontCheck` because callers (e.g. `batch_delete_up_tree_while_empty`) + /// have already verified the tree is empty. fn add_delete_tree(&mut self, path: Vec>, key: Vec, tree_type: TreeType) { - self.operations - .push(QualifiedGroveDbOp::delete_tree_op(path, key, tree_type)) + self.operations.push(QualifiedGroveDbOp::delete_tree_op( + path, + key, + tree_type, + SubelementsDeletionBehavior::DontCheck, + )) } /// Adds an `Insert` operation with an element to a list of GroveDB ops. diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs index 3f1bd9b0b8b..f52c2dfe841 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs @@ -52,7 +52,7 @@ impl Drive { } else if let GroveOperation(grove_op) = previous_drive_operation { if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path - && matches!(grove_op.op, GroveOp::DeleteTree(_)) + && matches!(grove_op.op, GroveOp::DeleteTree(_, _)) { found = true; existing_operations.remove(i); @@ -119,7 +119,7 @@ impl Drive { } else if let GroveOperation(grove_op) = previous_drive_operation { if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path - && matches!(grove_op.op, GroveOp::DeleteTree(_)) + && matches!(grove_op.op, GroveOp::DeleteTree(_, _)) { found = true; existing_operations.remove(i); @@ -184,7 +184,7 @@ impl Drive { } else if let GroveOperation(grove_op) = previous_drive_operation { if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path - && matches!(grove_op.op, GroveOp::DeleteTree(_)) + && matches!(grove_op.op, GroveOp::DeleteTree(_, _)) { found = true; existing_operations.remove(i); @@ -249,7 +249,7 @@ impl Drive { } else if let GroveOperation(grove_op) = previous_drive_operation { if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path - && matches!(grove_op.op, GroveOp::DeleteTree(_)) + && matches!(grove_op.op, GroveOp::DeleteTree(_, _)) { found = true; existing_operations.remove(i); diff --git a/packages/rs-drive/src/util/grove_operations/batch_remove_raw/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_remove_raw/v0/mod.rs index c2c441cf7d4..9da64b2262c 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_remove_raw/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_remove_raw/v0/mod.rs @@ -45,7 +45,7 @@ impl Drive { "we should not be seeing internal grovedb operations", ))); } - Some(GroveOp::Delete) | Some(GroveOp::DeleteTree(_)) => false, + Some(GroveOp::Delete) | Some(GroveOp::DeleteTree(_, _)) => false, _ => true, }; diff --git a/packages/rs-drive/src/util/grove_operations/grove_apply_batch_with_add_costs/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_apply_batch_with_add_costs/v0/mod.rs index 79b67a77f7d..e44000512d7 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_apply_batch_with_add_costs/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_apply_batch_with_add_costs/v0/mod.rs @@ -65,8 +65,6 @@ impl Drive { Some(BatchApplyOptions { validate_insertion_does_not_override: validate, validate_insertion_does_not_override_tree: validate, - allow_deleting_non_empty_trees: false, - deleting_non_empty_trees_returns_error: true, disable_operation_consistency_check: !self.config.batching_consistency_verification, base_root_storage_is_free: true, batch_pause_height: None, diff --git a/packages/rs-drive/src/util/grove_operations/grove_apply_partial_batch_with_add_costs/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_apply_partial_batch_with_add_costs/v0/mod.rs index 9d7a6bc8eba..a736817ab8d 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_apply_partial_batch_with_add_costs/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_apply_partial_batch_with_add_costs/v0/mod.rs @@ -50,8 +50,6 @@ impl Drive { Some(BatchApplyOptions { validate_insertion_does_not_override: validate, validate_insertion_does_not_override_tree: validate, - allow_deleting_non_empty_trees: false, - deleting_non_empty_trees_returns_error: true, disable_operation_consistency_check: false, base_root_storage_is_free: true, batch_pause_height: None, diff --git a/packages/rs-drive/src/util/grove_operations/grove_batch_operations_costs/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_batch_operations_costs/v0/mod.rs index fb1f867e8d0..77d8d247a5c 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_batch_operations_costs/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_batch_operations_costs/v0/mod.rs @@ -26,8 +26,6 @@ impl Drive { Some(BatchApplyOptions { validate_insertion_does_not_override: validate, validate_insertion_does_not_override_tree: validate, - allow_deleting_non_empty_trees: false, - deleting_non_empty_trees_returns_error: true, disable_operation_consistency_check: false, base_root_storage_is_free: true, batch_pause_height: None, diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 8a1aacfa909..40966c3ba44 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a7b878f72279a4893a7e9dc0ab2973ca0b09ce69" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } [features] mock-versions = [] From 83ea01f370810e0b7af665353d48a587d79f5067 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 10 Mar 2026 15:49:13 +0700 Subject: [PATCH 23/25] chore: update grovedb to b191ff27 with DontCheckWithNoCleanup Updates grovedb to include dashpay/grovedb#639 which renames DontCheck to DontCheckWithNoCleanup, eliminating unnecessary find_subtrees + storage.clear() I/O costs for known-empty tree deletions during document transfers and deletions. Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 186 +++++------------- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive/Cargo.toml | 12 +- .../src/util/batch/grovedb_op_batch/mod.rs | 4 +- packages/rs-platform-version/Cargo.toml | 2 +- 5 files changed, 55 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9991ee19708..425169497ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,7 +507,7 @@ dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -1113,7 +1113,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1982,7 +1982,7 @@ dependencies = [ "dpp", "env_logger 0.11.9", "getrandom 0.2.17", - "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", + "grovedb-commitment-tree", "hex", "indexmap 2.13.0", "integer-encoding", @@ -2033,7 +2033,7 @@ dependencies = [ "dpp", "enum-map", "grovedb", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-epoch-based-storage-flags", "grovedb-path", "grovedb-storage", @@ -2331,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2727,15 +2727,15 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", "grovedb-bulk-append-tree", - "grovedb-commitment-tree 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-commitment-tree", + "grovedb-costs", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", @@ -2765,11 +2765,11 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-dense-fixed-sized-merkle-tree", "grovedb-merkle-mountain-range", "grovedb-query", @@ -2781,43 +2781,21 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "blake3", "grovedb-bulk-append-tree", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-storage", "incrementalmerkletree", "orchard", "thiserror 2.0.18", ] -[[package]] -name = "grovedb-commitment-tree" -version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" -dependencies = [ - "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b)", - "incrementalmerkletree", - "orchard", - "thiserror 2.0.18", -] - -[[package]] -name = "grovedb-costs" -version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" -dependencies = [ - "integer-encoding", - "intmap", - "thiserror 2.0.18", -] - [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "integer-encoding", "intmap", @@ -2827,11 +2805,11 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-query", "grovedb-storage", "thiserror 2.0.18", @@ -2840,7 +2818,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "bincode_derive", @@ -2855,9 +2833,9 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "hex", "integer-encoding", "intmap", @@ -2867,7 +2845,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "bincode_derive", @@ -2875,7 +2853,7 @@ dependencies = [ "byteorder", "colored", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-element", "grovedb-path", "grovedb-query", @@ -2893,18 +2871,18 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-storage", ] [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "hex", ] @@ -2912,12 +2890,12 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "bincode", "byteorder", "ed", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-storage", "hex", "indexmap 2.13.0", @@ -2928,10 +2906,10 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "blake3", - "grovedb-costs 4.0.0 (git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84)", + "grovedb-costs", "grovedb-path", "grovedb-visualize", "hex", @@ -2947,7 +2925,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2956,7 +2934,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "hex", "itertools 0.14.0", @@ -2965,7 +2943,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=2c9117cf3d77c2ffda060f45242d238fb01dca84#2c9117cf3d77c2ffda060f45242d238fb01dca84" +source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" dependencies = [ "serde", "serde_with 3.17.0", @@ -3392,7 +3370,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3647,7 +3625,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4353,7 +4331,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5129,8 +5107,8 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.5.0", - "itertools 0.14.0", + "heck 0.4.1", + "itertools 0.10.5", "log", "multimap", "petgraph", @@ -5151,7 +5129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.117", @@ -5164,7 +5142,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.117", @@ -5288,7 +5266,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -5326,9 +5304,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -6002,7 +5980,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6061,7 +6039,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6858,7 +6836,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8262,7 +8240,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -8377,15 +8355,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -8434,30 +8403,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -8476,12 +8428,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8500,12 +8446,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8524,24 +8464,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8560,12 +8488,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8584,12 +8506,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -8608,12 +8524,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8632,12 +8542,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.5.40" diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 074329c2a23..ce7d5ede9e2 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 5540ff48d5b..db5e0bd4dc5 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index 6b1f8a2c60c..eaf298f2ffa 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -593,14 +593,14 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { } /// Adds a `Delete` tree operation to a list of GroveDB ops. - /// Uses `DontCheck` because callers (e.g. `batch_delete_up_tree_while_empty`) + /// Uses `DontCheckWithNoCleanup` because callers (e.g. `batch_delete_up_tree_while_empty`) /// have already verified the tree is empty. fn add_delete_tree(&mut self, path: Vec>, key: Vec, tree_type: TreeType) { self.operations.push(QualifiedGroveDbOp::delete_tree_op( path, key, tree_type, - SubelementsDeletionBehavior::DontCheck, + SubelementsDeletionBehavior::DontCheckWithNoCleanup, )) } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 40966c3ba44..ab06b3c7afa 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "2c9117cf3d77c2ffda060f45242d238fb01dca84" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } [features] mock-versions = [] From 160749df2d0817407d27102847c5dc7577ebb0b7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 10 Mar 2026 16:13:27 +0700 Subject: [PATCH 24/25] fix(drive-abci): update version upgrade test for proper DeleteChildren cleanup The test expected paid-out epoch proposers to still be readable, but this relied on the old grovedb behavior where DeleteTree silently failed to clean up child storage. Now that DeleteChildren properly removes the proposers tree contents, paid epochs correctly have 0 proposers. The proposer data is preserved in FinalizedEpochInfo in newer protocol versions (v6+), but the v0 fee distribution path used by this test does not persist it. Co-Authored-By: Claude Opus 4.6 --- .../tests/strategy_tests/test_cases/upgrade_fork_tests.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs index 9c244e90224..0a36de4fc20 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/upgrade_fork_tests.rs @@ -280,6 +280,9 @@ mod tests { .expect("expected to get epoch proposers"); assert_eq!(epoch_proposers_2.len(), 147); + // Epochs 0 and 1 have been paid out, so their proposers trees + // were deleted by add_mark_as_paid_operations (DeleteChildren + // properly cleans up the tree and its contents). let epoch_proposers_1 = platform .drive .fetch_epoch_proposers( @@ -289,7 +292,7 @@ mod tests { platform_version, ) .expect("expected to get epoch proposers"); - assert_eq!(epoch_proposers_1.len(), 299); // We had 299 proposers in epoch 1 + assert_eq!(epoch_proposers_1.len(), 0); let epoch_proposers_0 = platform .drive @@ -300,7 +303,7 @@ mod tests { platform_version, ) .expect("expected to get epoch proposers"); - assert_eq!(epoch_proposers_0.len(), 447); // We had 447 proposers in epoch 0 + assert_eq!(epoch_proposers_0.len(), 0); } #[test] From 82eddb801e33ac9e024b9aac5f201216734c8a93 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 10 Mar 2026 17:51:39 +0700 Subject: [PATCH 25/25] chore: update grovedb to dd99ed1d Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 30 ++++++++++++------------- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive/Cargo.toml | 12 +++++----- packages/rs-platform-version/Cargo.toml | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 425169497ff..c690b3766e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "axum 0.8.8", "bincode", @@ -2765,7 +2765,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "blake3", @@ -2781,7 +2781,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -2795,7 +2795,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "integer-encoding", "intmap", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "blake3", @@ -2818,7 +2818,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "bincode_derive", @@ -2833,7 +2833,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "grovedb-costs", "hex", @@ -2845,7 +2845,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "bincode_derive", @@ -2871,7 +2871,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "blake3", @@ -2882,7 +2882,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "hex", ] @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "bincode", "byteorder", @@ -2906,7 +2906,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "blake3", "grovedb-costs", @@ -2925,7 +2925,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "hex", "itertools 0.14.0", @@ -2943,7 +2943,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=b191ff27b0b00c2e1e6836cb4e29067449be9d63#b191ff27b0b00c2e1e6836cb4e29067449be9d63" +source = "git+https://github.com/dashpay/grovedb?rev=dd99ed1db0350e5f39127573808dd172c6bc2346#dd99ed1db0350e5f39127573808dd172c6bc2346" dependencies = [ "serde", "serde_with 3.17.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index ce7d5ede9e2..cfec13e9d5a 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index db5e0bd4dc5..5c83a330fbd 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index ab06b3c7afa..276f175a745 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "b191ff27b0b00c2e1e6836cb4e29067449be9d63" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "dd99ed1db0350e5f39127573808dd172c6bc2346" } [features] mock-versions = []