Skip to main content

drive/query/drive_document_ranked_query/
path.rs

1//! The grove path a ranked read / proof / verification is issued against.
2//!
3//! This is one half of the **prover/verifier-agreement boundary** for
4//! the ranked surface: both sides build the same axis `PathQuery` —
5//! these path segments plus the traversal (`axis, k, offset,
6//! descending`) — and grovedb re-executes the proof against that
7//! reconstruction at verification time. Prover and verifier
8//! both call [`DriveDocumentRankedQuery::indexed_property_name_tree_path`];
9//! a divergence here surfaces as a failed root-hash reconstruction, not a
10//! wrong answer, but it is still the one place the two sides must not
11//! drift.
12//!
13//! Gated `any(server, verify)` so the verifier crate reaches it through
14//! `DriveDocumentRankedQuery::*` method syntax.
15
16use super::DriveDocumentRankedQuery;
17use crate::drive::RootTree;
18use crate::error::drive::DriveError;
19use crate::error::Error;
20use dpp::data_contract::document_type::Index;
21
22/// Path of an index's terminal property-name tree — shared by the
23/// ranked and having-range query surfaces, which read the same indexed
24/// tree. See [`DriveDocumentRankedQuery::indexed_property_name_tree_path`]
25/// for the segment layout.
26///
27/// The branch's prefix segments carry the **encoded index-key bytes** of
28/// each leading property's pinned value, in index-property order — one
29/// per property before the terminal one. Empty for a single-property
30/// index. The arity must match exactly: a compound index's terminal
31/// tree sits under one prefix value tree per leading property, and only
32/// a `where` pin (an equality, or one element of the single permitted
33/// `IN`) can name those values, so a missing or surplus value means the
34/// caller resolved the wrong index — a typed error, not a guess.
35pub(crate) fn indexed_property_name_tree_path_for_index(
36    contract_id: &[u8; 32],
37    document_type_name: &str,
38    index: &Index,
39    equality_prefix_values: &[Vec<u8>],
40) -> Result<Vec<Vec<u8>>, Error> {
41    let Some((terminal_property, leading_properties)) = index.properties.split_last() else {
42        return Err(Error::Drive(DriveError::NotSupported(
43            "ranked and having-range queries require an index with at least one \
44             property",
45        )));
46    };
47    if leading_properties.len() != equality_prefix_values.len() {
48        return Err(Error::Drive(DriveError::NotSupported(
49            "ranked and having-range queries over a compound index require exactly one \
50             encoded equality value per leading index property: the axis secondary lives \
51             on the index's terminal property-name tree, which for a compound index sits \
52             under one prefix value tree per leading property, and only a `where` pin (an \
53             equality, or one element of the single permitted `IN`) can name those values",
54        )));
55    }
56    let mut path = Vec::with_capacity(5 + 2 * leading_properties.len());
57    path.push(vec![RootTree::DataContractDocuments as u8]);
58    path.push(contract_id.to_vec());
59    path.push(vec![1u8]);
60    path.push(document_type_name.as_bytes().to_vec());
61    for (property, value) in leading_properties.iter().zip(equality_prefix_values) {
62        path.push(property.name.as_bytes().to_vec());
63        path.push(value.clone());
64    }
65    path.push(terminal_property.name.as_bytes().to_vec());
66    Ok(path)
67}
68
69impl DriveDocumentRankedQuery<'_> {
70    /// Path of the **terminal property-name tree** — the indexed tree
71    /// whose primary holds one value tree per group and whose per-axis
72    /// secondaries hold the ranking.
73    ///
74    /// For a single-property index:
75    ///
76    /// ```text
77    /// [ RootTree::DataContractDocuments as u8 ]   // 0x01
78    ///   / <contract_id: 32 bytes>
79    ///   / [ 0x01 ]                                // "documents", not "contract"
80    ///   / <document_type_name: utf-8>
81    ///   / <index property name: utf-8>
82    /// ```
83    ///
84    /// For a compound index `[p1, …, pn]`, each leading property
85    /// contributes two segments — its name and the **encoded index-key
86    /// bytes of its pinned value** (from
87    /// [`Self::prefix_branches`]) — and the terminal property
88    /// name closes the path:
89    ///
90    /// ```text
91    ///   … / <p1 name> / <p1 pinned value bytes> / … / <pn name>
92    /// ```
93    ///
94    /// so the ranking read lands on **that prefix's** indexed tree: the
95    /// per-prefix secondary orders only the pinned prefix's groups.
96    ///
97    /// The children of the terminal tree are the groups, keyed by the
98    /// raw index-key bytes of the terminal property value — the same
99    /// bytes that come back as [`super::RankedEntry::key`].
100    ///
101    /// Errors when the number of encoded prefix values does not match
102    /// the index's leading-property count — the fail-closed backstop
103    /// for a caller that resolved the query against the wrong index.
104    ///
105    /// `branch` indexes into [`Self::prefix_branches`]; single-branch
106    /// queries (no `IN` pin) always pass `0`.
107    pub fn indexed_property_name_tree_path(&self, branch: usize) -> Result<Vec<Vec<u8>>, Error> {
108        let prefix_values =
109            self.prefix_branches
110                .get(branch)
111                .ok_or(Error::Drive(DriveError::NotSupported(
112                    "ranked and having-range queries addressed a prefix branch outside the \
113                 query's resolved branch set",
114                )))?;
115        indexed_property_name_tree_path_for_index(
116            &self.contract_id,
117            &self.document_type_name,
118            self.index,
119            prefix_values,
120        )
121    }
122}