Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ed3a886
feat(drive-abci): add shielded pool drive-abci integration
QuantumExplorer Mar 10, 2026
4e84d4f
fix(drive-abci): fix shielded test compilation and proof verification…
QuantumExplorer Mar 10, 2026
c9a74f7
chore(drive-abci): cargo fmt
QuantumExplorer Mar 10, 2026
cfa16af
refactor(drive): remove unused PenalizeShieldedPoolAction
QuantumExplorer Mar 10, 2026
1771400
fix(drive-abci): gate shielded basic_structure validation on platform…
QuantumExplorer Mar 10, 2026
75f5650
docs(drive-abci): explain why ShieldFromAssetLock skips early proof v…
QuantumExplorer Mar 10, 2026
c1de6f0
Merge branch 'v3.1-dev' into feat/zk-drive-abci
QuantumExplorer Mar 10, 2026
f104242
fix(drive-abci): audit fixes for shielded pool integration
QuantumExplorer Mar 10, 2026
fe0697e
fix(drive): adapt proof verification for shielded pool tree structure…
QuantumExplorer Mar 10, 2026
1486922
fix(drive-abci): address CodeRabbit review comments
QuantumExplorer Mar 10, 2026
db74541
refactor(drive): store anchors as key with O(1) lookup and dedicated …
QuantumExplorer Mar 10, 2026
b17ad9e
feat(drive): add anchors-by-height reverse index tree for pruning sup…
QuantumExplorer Mar 10, 2026
d1297b7
feat(drive-abci): prune shielded pool anchors older than 1000 blocks
QuantumExplorer Mar 11, 2026
22354c0
perf(drive-abci): only prune shielded anchors every 100 blocks
QuantumExplorer Mar 11, 2026
2b93bd4
fix(drive-abci): reject zero value_balance in shielded transfer and a…
QuantumExplorer Mar 11, 2026
1518d6a
docs(drive-abci): update audit findings statuses and clarify strategy…
QuantumExplorer Mar 11, 2026
738b243
fix(drive-abci): fix CI failures - formatting, clippy, and drive init…
QuantumExplorer Mar 11, 2026
5712538
refactor(drive-abci): rename err to consensus_error in transform_into…
QuantumExplorer Mar 11, 2026
e0a8c54
Merge branch 'v3.1-dev' into feat/zk-drive-abci
QuantumExplorer Mar 11, 2026
f7fb180
refactor(drive-abci): extract anchor pruning interval to version field
QuantumExplorer Mar 11, 2026
001b40f
Merge branch 'v3.1-dev' into feat/zk-drive-abci
QuantumExplorer Mar 12, 2026
5d1a593
refactor(drive): extract shielded pool GroveDB operations into Drive …
QuantumExplorer Mar 12, 2026
af1fb34
fix(wasm-dpp2): replace missing impl_wasm_conversions with impl_wasm_…
QuantumExplorer Mar 12, 2026
8e6764c
chore(drive-abci): comment out shielded strategy tests temporarily
QuantumExplorer Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/rs-drive-abci/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] }
async-trait = "0.1.77"
console-subscriber = { version = "0.4", optional = true }
bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true }
grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" }
sha2 = "0.10"
nonempty = "0.11"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

[dev-dependencies]
platform-version = { path = "../rs-platform-version", features = [
Expand All @@ -102,6 +105,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [
drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] }
drive-proof-verifier = { path = "../rs-drive-proof-verifier" }
strategy-tests = { path = "../strategy-tests" }
grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", features = ["client"] }
assert_matches = "1.5.0"
drive-abci = { path = ".", features = ["testing-config", "mocks"] }
bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,18 @@ where
platform_version,
)?;

// Clean up expired compacted nullifier entries
self.cleanup_recent_block_storage_nullifiers(&block_info, transaction, platform_version)?;

// Record shielded pool anchor if the commitment tree changed this block.
// This stores block_height → anchor_bytes so shielded transactions can
// reference a recent anchor for spend authorization.
self.record_shielded_pool_anchor_if_changed(
block_proposal.height,
transaction,
platform_version,
)?;
Comment thread
QuantumExplorer marked this conversation as resolved.

// Pool withdrawals into transactions queue

// Takes queued withdrawals, creates untiled withdrawal transaction payload, saves them to queue
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod add_process_epoch_change_operations;
pub mod process_block_fees_and_validate_sum_trees;
mod record_shielded_pool_anchor;

#[cfg(test)]
mod tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
mod v0;

use crate::error::execution::ExecutionError;
use crate::error::Error;
use crate::platform_types::platform::Platform;
use crate::rpc::core::CoreRPCLike;
use dpp::version::PlatformVersion;
use drive::grovedb::Transaction;

impl<C> Platform<C>
where
C: CoreRPCLike,
{
/// Records the current shielded pool anchor if the commitment tree changed this block.
pub(in crate::execution) fn record_shielded_pool_anchor_if_changed(
&self,
block_height: u64,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
match platform_version
.drive_abci
.methods
.block_end
.record_shielded_pool_anchor
{
None => Ok(()),
Some(0) => self.record_shielded_pool_anchor_if_changed_v0(
block_height,
transaction,
platform_version,
),
Some(version) => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "record_shielded_pool_anchor_if_changed".to_string(),
known_versions: vec![0],
received: version,
})),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::error::Error;
use crate::platform_types::platform::Platform;
use crate::rpc::core::CoreRPCLike;
use dpp::version::PlatformVersion;
use drive::drive::shielded::paths::{
shielded_credit_pool_anchors_path, shielded_credit_pool_path, SHIELDED_NOTES_KEY,
};
use drive::grovedb::query_result_type::QueryResultType;
use drive::grovedb::{Element, PathQuery, Query, QueryItem, SizedQuery, Transaction};

impl<C> Platform<C>
where
C: CoreRPCLike,
{
/// Records the current shielded pool anchor if the commitment tree changed this block.
///
/// After all state transitions are processed, reads the current Sinsemilla anchor
/// from the CommitmentTree at [AddressBalances, "s", [1]]. If it differs from the
/// most recently stored anchor (or no anchor exists yet), inserts
/// `block_height.to_be_bytes() → anchor_bytes` into the anchors tree at
/// [AddressBalances, "s", [6]].
///
/// This ensures anchors are only recorded once per block (not per-transaction),
/// and only when the commitment tree actually changed.
pub(super) fn record_shielded_pool_anchor_if_changed_v0(
&self,
block_height: u64,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
let grove_version = &platform_version.drive.grove_version;
let pool_path = shielded_credit_pool_path();

// 1. Read current anchor from CommitmentTree
let current_anchor = self
Comment thread
shumkov marked this conversation as resolved.
Outdated
.drive
.grove
.commitment_tree_anchor(
&pool_path,
&[SHIELDED_NOTES_KEY],
Some(transaction),
grove_version,
)
.unwrap()
.map_err(|e| Error::Drive(drive::error::Error::from(e)))?;

let current_anchor_bytes: [u8; 32] = current_anchor.to_bytes();

// 2. Query latest stored anchor (descending, limit 1)
let anchors_path = shielded_credit_pool_anchors_path();
let mut query = Query::new();
query.insert_item(QueryItem::RangeFull(..));
let path_query = PathQuery {
path: anchors_path.iter().map(|p| p.to_vec()).collect(),
query: SizedQuery {
query,
limit: Some(1),
offset: None,
},
};

let (results, _) = self.drive.grove_get_raw_path_query(
&path_query,
Some(transaction),
QueryResultType::QueryKeyElementPairResultType,
&mut vec![],
&platform_version.drive,
)?;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

let latest_stored_anchor: Option<[u8; 32]> = results
.to_key_elements()
.into_iter()
.last()
.and_then(|(_key, element)| {
if let Element::Item(value, _) = element {
value.try_into().ok()
} else {
None
}
});

// 3. Only store if different (or none stored yet)
let should_store = match latest_stored_anchor {
None => {
// No anchors stored yet — only store if the tree has notes
// (an empty tree has a zero anchor which isn't useful)
current_anchor_bytes != [0u8; 32]
}
Some(stored) => stored != current_anchor_bytes,
};

if should_store {
self.drive
.grove
.insert(
&anchors_path,
&block_height.to_be_bytes(),
Element::new_item(current_anchor_bytes.to_vec()),
None,
Some(transaction),
grove_version,
)
.unwrap()
.map_err(|e| Error::Drive(drive::error::Error::from(e)))?;
}

Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ use drive::drive::saved_block_transactions::{
ADDRESS_BALANCES_KEY_U8, COMPACTED_ADDRESSES_EXPIRATION_TIME_KEY_U8,
COMPACTED_ADDRESS_BALANCES_KEY_U8,
};
use drive::drive::shielded::paths::{
shielded_credit_pool_path, SHIELDED_ANCHORS_IN_POOL_KEY, SHIELDED_CREDIT_POOL_KEY_U8,
SHIELDED_NOTES_KEY, SHIELDED_NULLIFIERS_KEY, SHIELDED_TOTAL_BALANCE_KEY,
};
use drive::drive::system::misc_path;
use drive::drive::tokens::paths::{
token_distributions_root_path, token_timed_distributions_path, tokens_root_path,
Expand Down Expand Up @@ -110,6 +114,10 @@ impl<C> Platform<C> {
self.transition_to_version_11(transaction, platform_version)?;
}

if previous_protocol_version < 12 && platform_version.protocol_version >= 12 {
self.transition_to_version_12(transaction, platform_version)?;
}

Ok(())
}

Expand Down Expand Up @@ -598,4 +606,68 @@ impl<C> Platform<C> {

Ok(())
}

/// We introduced in version 12 Shielded Pools
fn transition_to_version_12(
&self,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
let addresses_path = Drive::addresses_path();

// Shielded credit pool SumTree under AddressBalances: [AddressBalances] / "s"
self.drive.grove_insert_if_not_exists(
addresses_path.as_slice().into(),
&[SHIELDED_CREDIT_POOL_KEY_U8],
Element::empty_sum_tree(),
Some(transaction),
None,
&platform_version.drive,
)?;

// Notes tree (CommitmentTree = CountTree items + Sinsemilla Frontier):
// [AddressBalances, "s"] / [1]
let shielded_pool_path = shielded_credit_pool_path();
self.drive.grove_insert_if_not_exists(
(&shielded_pool_path).into(),
&[SHIELDED_NOTES_KEY],
Element::empty_commitment_tree(11).expect("chunk_power 11 is valid"),
Some(transaction),
None,
&platform_version.drive,
)?;

// Nullifiers tree (ProvableCountTree): [AddressBalances, "s"] / [2]
self.drive.grove_insert_if_not_exists(
(&shielded_pool_path).into(),
&[SHIELDED_NULLIFIERS_KEY],
Element::empty_provable_count_tree(),
Some(transaction),
None,
&platform_version.drive,
)?;

// Total balance SumItem(0): [AddressBalances, "s"] / [5]
self.drive.grove_insert_if_not_exists(
(&shielded_pool_path).into(),
&[SHIELDED_TOTAL_BALANCE_KEY],
Element::new_sum_item(0),
Some(transaction),
None,
&platform_version.drive,
)?;

// Anchors tree (NormalTree) inside pool: [AddressBalances, "s"] / [6]
// Stores block_height_be → anchor_bytes
self.drive.grove_insert_if_not_exists(
(&shielded_pool_path).into(),
&[SHIELDED_ANCHORS_IN_POOL_KEY],
Element::empty_tree(),
Some(transaction),
None,
&platform_version.drive,
)?;

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
mod v0;

use crate::error::execution::ExecutionError;
use crate::error::Error;
use crate::platform_types::platform::Platform;
use crate::rpc::core::CoreRPCLike;
use dpp::block::block_info::BlockInfo;
use dpp::version::PlatformVersion;
use drive::grovedb::Transaction;

impl<C> Platform<C>
where
C: CoreRPCLike,
{
/// Cleans up expired compacted nullifier entries from recent block storage.
pub(in crate::execution) fn cleanup_recent_block_storage_nullifiers(
&self,
block_info: &BlockInfo,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
match platform_version
.drive_abci
.methods
.state_transition_processing
.cleanup_recent_block_storage_nullifiers
{
None => Ok(()),
Some(0) => self.cleanup_recent_block_storage_nullifiers_v0(
block_info,
transaction,
platform_version,
),
Some(version) => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "cleanup_recent_block_storage_nullifiers".to_string(),
known_versions: vec![0],
received: version,
})),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::error::Error;
use crate::platform_types::platform::Platform;
use crate::rpc::core::CoreRPCLike;
use dpp::block::block_info::BlockInfo;
use dpp::version::PlatformVersion;
use drive::grovedb::Transaction;

impl<C> Platform<C>
where
C: CoreRPCLike,
{
/// Version 0 implementation of cleaning up expired compacted nullifier entries.
pub(super) fn cleanup_recent_block_storage_nullifiers_v0(
&self,
block_info: &BlockInfo,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
self.drive.cleanup_expired_nullifier_compactions(
block_info.time_ms,
Some(transaction),
platform_version,
)?;

Ok(())
}
}
Loading
Loading