Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@ use dapi_grpc::platform::v0::get_most_recent_shielded_anchor_response::{
use dpp::check_validation_result_with_data;
use dpp::validation::ValidationResult;
use dpp::version::PlatformVersion;
use drive::drive::shielded::paths::{
shielded_credit_pool_path, shielded_credit_pool_path_vec, SHIELDED_MOST_RECENT_ANCHOR_KEY,
};
use drive::grovedb::{Element, PathQuery, Query, SizedQuery};
use drive::util::grove_operations::{DirectQueryType, GroveDBToUse};
use drive::drive::shielded::paths::shielded_latest_recorded_anchor_path_query;
use drive::grovedb::query_result_type::QueryResultType;
use drive::grovedb::Element;
use drive::util::grove_operations::GroveDBToUse;

impl<C> Platform<C> {
/// Answer `getMostRecentShieldedAnchor` by reading the latest
/// entry from the anchors-by-height index
/// (`[..., "s", [8]]`) — a `limit 1` reverse query. The anchor
/// is the value at the highest block-height key.
///
/// Returns `[0; 32]` (mapped to `None` by the response decoder
/// downstream) when the index is empty — the pool has never
/// recorded an anchor yet on this chain.
pub(super) fn query_most_recent_shielded_anchor_v0(
&self,
GetMostRecentShieldedAnchorRequestV0 { prove }: GetMostRecentShieldedAnchorRequestV0,
platform_state: &PlatformState,
platform_version: &PlatformVersion,
) -> Result<QueryValidationResult<GetMostRecentShieldedAnchorResponseV0>, Error> {
let response = if prove {
let path_query = PathQuery {
path: shielded_credit_pool_path_vec(),
query: SizedQuery {
query: Query::new_single_key(vec![SHIELDED_MOST_RECENT_ANCHOR_KEY]),
limit: Some(1),
offset: None,
},
};
let path_query = shielded_latest_recorded_anchor_path_query();

let response = if prove {
let proof = check_validation_result_with_data!(self.drive.grove_get_proved_path_query(
&path_query,
None,
Expand All @@ -50,19 +50,22 @@ impl<C> Platform<C> {
metadata: Some(self.response_metadata_v0(platform_state, grovedb_used)),
}
} else {
let pool_path = shielded_credit_pool_path();

let maybe_element = self.drive.grove_get_raw(
(&pool_path).into(),
&[SHIELDED_MOST_RECENT_ANCHOR_KEY],
DirectQueryType::StatefulDirectQuery,
let (results, _) = self.drive.grove_get_raw_path_query(
&path_query,
None,
QueryResultType::QueryKeyElementPairResultType,
&mut vec![],
&platform_version.drive,
)?;

let anchor_bytes = match maybe_element {
Some(Element::Item(bytes, _)) => bytes,
let entries = results.to_key_elements();
let anchor_bytes = match entries.into_iter().next() {
Some((_height_key, Element::Item(bytes, _))) => bytes,
// Empty index, or the entry isn't an Item (which
// would be a state-corruption bug elsewhere). Either
// way, return the zero-anchor sentinel — same shape
// the previous `[7]`-backed implementation used when
// the slot was uninitialised.
_ => vec![0u8; 32],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ mod tests {
fn run_chain_verify_anchors_after_shielding() {
use drive::drive::shielded::paths::{
shielded_credit_pool_anchors_by_height_path, shielded_credit_pool_anchors_path_vec,
shielded_credit_pool_path, SHIELDED_MOST_RECENT_ANCHOR_KEY,
};
use drive::grovedb::query_result_type::QueryResultType;
use drive::grovedb::{Element, PathQuery, Query, SizedQuery};
Expand Down Expand Up @@ -532,41 +531,27 @@ mod tests {
}
}

// 3. Verify most recent anchor is set and non-zero
let pool_path = shielded_credit_pool_path();
let most_recent_element = drive
.grove
.get(
&pool_path,
&[SHIELDED_MOST_RECENT_ANCHOR_KEY],
None,
&platform_version.drive.grove_version,
)
.unwrap()
.expect("most recent anchor element must exist");

if let Element::Item(most_recent_bytes, _) = most_recent_element {
assert_eq!(
most_recent_bytes.len(),
32,
"most recent anchor must be 32 bytes"
);
assert_ne!(
most_recent_bytes,
vec![0u8; 32],
"most recent anchor must not be all zeros after successful shields"
);
// Most recent anchor must be one of the recorded anchors
let is_known = anchor_to_height
.iter()
.any(|(a, _)| *a == most_recent_bytes);
assert!(
is_known,
"most recent anchor must match one of the recorded anchors"
);
} else {
panic!("most recent anchor must be an Item element");
}
// 3. Verify the derived "most recent anchor" — i.e. the
// highest-block-height entry in the anchors-by-height
// index — exists and matches one of the recorded anchors.
// There is no longer a separate "most recent anchor" slot;
// the index is the canonical source.
let most_recent_anchor = drive
.read_latest_recorded_shielded_anchor_v0(None, &platform_version.drive)
.expect("read latest recorded anchor")
.expect("most recent anchor must exist after successful shields");
assert_ne!(
most_recent_anchor.to_vec(),
vec![0u8; 32],
"most recent anchor must not be all zeros after successful shields"
);
let is_known = anchor_to_height
.iter()
.any(|(a, _)| *a == most_recent_anchor.to_vec());
assert!(
is_known,
"most recent anchor must match one of the recorded anchors"
);

tracing::info!(
anchor_count = anchor_entries.len(),
Expand Down
15 changes: 6 additions & 9 deletions packages/rs-drive/src/drive/initialization/v3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,18 @@ impl Drive {
Element::empty_tree(),
);

// 5b. Anchors-by-height tree (NormalTree): block_height_be → anchor_bytes
// Reverse index for pruning old anchors by height range.
// 5b. Anchors-by-height tree (NormalTree): block_height_be → anchor_bytes.
// Reverse index for pruning old anchors by height range. Also the
// canonical source of the most-recent anchor (read via `limit 1`
// reverse query) — there is no separate "most recent" slot; key 7
// was retired because the duplicate state could desync from the
// anchors tree under prune.
batch.add_insert(
shielded_credit_pool_path_vec(),
vec![SHIELDED_ANCHORS_BY_HEIGHT_KEY],
Element::empty_tree(),
);

// 5c. Most recent anchor item (empty initially, set on first block with notes)
batch.add_insert(
shielded_credit_pool_path_vec(),
vec![SHIELDED_MOST_RECENT_ANCHOR_KEY],
Element::new_item(vec![0u8; 32]),
);

// 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(
Expand Down
53 changes: 47 additions & 6 deletions packages/rs-drive/src/drive/shielded/paths.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::drive::RootTree;
use grovedb::{PathQuery, Query, SizedQuery};

/// The subtree key for the shielded credit pool under AddressBalances
pub const SHIELDED_CREDIT_POOL_KEY: &[u8; 1] = b"s";
Expand All @@ -15,14 +16,22 @@ 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 (anchor_bytes → block_height_be)
/// Key for the anchors tree inside a shielded pool (anchor_bytes → block_height_be).
/// Used by `validate_anchor_exists` for O(1) membership checks at spend time.
pub const SHIELDED_ANCHORS_IN_POOL_KEY: u8 = 6;

/// Key for the most recent anchor item inside a shielded pool
pub const SHIELDED_MOST_RECENT_ANCHOR_KEY: u8 = 7;

/// Key for the anchors-by-height tree inside a shielded pool (block_height_be → anchor_bytes)
/// Reverse index of SHIELDED_ANCHORS_IN_POOL_KEY, used for pruning old anchors by height range.
// Key 7 was previously `SHIELDED_MOST_RECENT_ANCHOR_KEY`, a redundant
// `Item([u8;32])` slot mirroring the latest entry in
// `SHIELDED_ANCHORS_BY_HEIGHT_KEY`. It was removed because the duplicated
// state could (and did) drift out of sync with the anchors tree under prune,
// leaving the validator's lookup table empty while the pool was still live.
// The most-recent anchor is now derived from `[8]` via a `limit 1` reverse
// query — see `Drive::query_most_recent_shielded_anchor`.

/// Key for the anchors-by-height tree inside a shielded pool (block_height_be → anchor_bytes).
/// Reverse index of `SHIELDED_ANCHORS_IN_POOL_KEY`, used both for pruning old
/// anchors by height range and as the canonical source of the most-recent
/// anchor (read via `limit 1` reverse query).
pub const SHIELDED_ANCHORS_BY_HEIGHT_KEY: u8 = 8;

/// Chunk power for the notes CommitmentTree (2^11 = 2048 items per chunk)
Expand Down Expand Up @@ -116,6 +125,38 @@ pub fn shielded_credit_pool_anchors_by_height_path_vec() -> Vec<Vec<u8>> {
]
}

/// Canonical `PathQuery` used to read the most-recent recorded
/// shielded-pool anchor: a `limit 1` reverse scan over
/// `SHIELDED_ANCHORS_BY_HEIGHT_KEY`, returning the entry with the
/// highest `block_height_be` key.
///
/// Shared between three call sites that must agree byte-for-byte:
/// - `Drive::read_latest_recorded_shielded_anchor_v0` (raw read used
/// by `record_shielded_pool_anchor_if_changed_v0` to decide whether
/// the anchor changed this block);
/// - `Platform::query_most_recent_shielded_anchor_v0` (proven RPC
/// handler);
/// - `Drive::verify_most_recent_shielded_anchor_v0` (SDK-side proof
/// verifier — replays the same `PathQuery`).
///
/// Keep these three in sync via this helper rather than open-coding
/// the `PathQuery` at each site; subtle differences (e.g. swapping
/// `left_to_right` or the `limit`) would silently produce
/// non-matching proofs.
pub fn shielded_latest_recorded_anchor_path_query() -> PathQuery {
let mut query = Query::new();
query.insert_all();
query.left_to_right = false;
PathQuery {
path: shielded_credit_pool_anchors_by_height_path_vec(),
query: SizedQuery {
query,
limit: Some(1),
offset: None,
},
}
}

/// Resolves the nullifiers path based on pool type.
///
/// Pool types:
Expand Down
Loading
Loading