Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ impl StateTransitionStructureValidation for ShieldTransitionV0 {
);
}

// amount must fit in i64 (Orchard protocol uses i64 internally for value_balance)
if self.amount > i64::MAX as u64 {
return SimpleConsensusValidationResult::new_with_error(
BasicError::ShieldedInvalidValueBalanceError(
ShieldedInvalidValueBalanceError::new(
"shield amount exceeds maximum allowed value".to_string(),
),
)
.into(),
);
}

// Proof must not be empty
let result = validate_proof_not_empty(&self.proof);
if !result.is_valid() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 {
return result;
}

// value_balance must be positive (it IS the fee for shielded transfers)
if self.value_balance == 0 {
return SimpleConsensusValidationResult::new_with_error(
BasicError::ShieldedInvalidValueBalanceError(
ShieldedInvalidValueBalanceError::new(
"shielded transfer value_balance must be greater than zero".to_string(),
),
)
.into(),
);
}

// value_balance must fit in i64 (required for Orchard protocol)
if self.value_balance > i64::MAX as u64 {
return SimpleConsensusValidationResult::new_with_error(
Expand Down
6 changes: 6 additions & 0 deletions packages/rs-drive-abci/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ 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 = "dd99ed1db0350e5f39127573808dd172c6bc2346" }
nonempty = "0.11"

[dev-dependencies]
platform-version = { path = "../rs-platform-version", features = [
Expand All @@ -101,6 +103,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 = "dd99ed1db0350e5f39127573808dd172c6bc2346", 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 All @@ -120,6 +123,8 @@ testing-config = []
grovedbg = ["drive/grovedbg"]
# `abci-server replay` command
replay = ["dep:time", "tenderdash-abci/serde"]
# Long-running shielded strategy tests (ZK proof generation)
__shielded_strategy_tests = []
[[bin]]
name = "drive-abci"
path = "src/main.rs"
Expand All @@ -128,4 +133,5 @@ path = "src/main.rs"
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(tokio_unstable)',
'cfg(create_sdk_test_data)',
'cfg(feature, values("__shielded_strategy_tests"))',
] }
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,21 @@ 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,
)?;

// Prune anchors older than the configured retention depth
self.prune_shielded_pool_anchors(block_proposal.height, transaction, platform_version)?;

// 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,7 @@
mod add_process_epoch_change_operations;
pub mod process_block_fees_and_validate_sum_trees;
mod prune_shielded_pool_anchors;
mod record_shielded_pool_anchor;

#[cfg(test)]
mod tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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,
{
/// Prunes shielded pool anchors older than the configured retention depth.
pub(in crate::execution) fn prune_shielded_pool_anchors(
&self,
block_height: u64,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
match platform_version
.drive_abci
.methods
.block_end
.prune_shielded_pool_anchors
{
None => Ok(()),
Some(0) => {
self.prune_shielded_pool_anchors_v0(block_height, transaction, platform_version)
}
Some(version) => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
method: "prune_shielded_pool_anchors".to_string(),
known_versions: vec![0],
received: version,
})),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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,
{
/// Prunes anchors older than `shielded_anchor_retention_blocks` from the current height.
///
/// Checks interval and retention depth conditions, then delegates to
/// `Drive::prune_shielded_pool_anchors` for the actual GroveDB operations.
pub(super) fn prune_shielded_pool_anchors_v0(
&self,
block_height: u64,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
let event_constants = &platform_version
.drive_abci
.validation_and_processing
.event_constants;
let retention_blocks = event_constants.shielded_anchor_retention_blocks;
let pruning_interval = event_constants.shielded_anchor_pruning_interval;

// Only prune every N blocks to avoid unnecessary work
if !block_height.is_multiple_of(pruning_interval) {
return Ok(());
}

// Nothing to prune if we haven't reached the retention depth yet
if block_height <= retention_blocks {
return Ok(());
}

let cutoff_height = block_height - retention_blocks;

self.drive
.prune_shielded_pool_anchors(cutoff_height, transaction, platform_version)
.map_err(Error::Drive)
}
}
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,27 @@
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.
///
/// Delegates to `Drive::record_shielded_pool_anchor_if_changed` which handles
/// all GroveDB operations: reading the current and most recent anchors, and
/// conditionally writing to the anchors tree, anchors-by-height tree, and
/// most recent anchor item.
pub(super) fn record_shielded_pool_anchor_if_changed_v0(
&self,
block_height: u64,
transaction: &Transaction,
platform_version: &PlatformVersion,
) -> Result<(), Error> {
self.drive
.record_shielded_pool_anchor_if_changed(block_height, transaction, platform_version)
.map_err(Error::Drive)
}
}
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(())
}
}
Loading
Loading