Skip to main content

drive/query/drive_document_count_query/
execute_range_count.rs

1//! Range execution paths for the count query.
2//!
3//! Four executors, each keyed on a `range_countable: true` index or a
4//! `summableOffCountIndex` index (see below):
5//! - [`DriveDocumentCountQuery::execute_range_count_no_proof`] — Rust-
6//!   side walk of the property-name `ProvableCountTree`'s children,
7//!   returning per-(in_key, key) entries (or a single sum) without a
8//!   proof.
9//! - [`DriveDocumentCountQuery::execute_aggregate_count_with_proof`] —
10//!   grovedb `AggregateCountOnRange` proof, returning a single u64.
11//! - [`DriveDocumentCountQuery::execute_distinct_count_with_proof`] —
12//!   regular range proof against the `ProvableCountTree`, returning
13//!   per-key `KVCount` ops bound to the merk root.
14//! - [`DriveDocumentCountQuery::execute_carrier_aggregate_count_with_proof`]
15//!   — one `AggregateCountOnRange` per `In` branch (or outer range key),
16//!   proved together.
17//!
18//! Over a `summableOffCountIndex` index each executor reads the index's
19//! range sums instead, through the sum surface's counterpart
20//! ([`DriveDocumentCountQuery::counter_sums_query`]): its count trees count
21//! its counters, one per group, while its sums are its document counts.
22//!
23//! Point-lookup execution (Equal/In with no range) lives in
24//! [`super::execute_point_lookup`](super::execute_point_lookup).
25//!
26//! Whole module is gated `feature = "server"` via the parent's
27//! `pub mod execute_range_count;` declaration.
28
29use super::super::conditions::{WhereClause, WhereOperator};
30use super::super::drive_document_sum_query::{RangeSumOptions, RangeSumWalkMode};
31use super::{counter_sum_entry_as_count_entry, DriveDocumentCountQuery, SplitCountEntry};
32use crate::drive::Drive;
33use crate::error::query::QuerySyntaxError;
34use crate::error::Error;
35use crate::query::{aggregate_or_zero_when_absent, index_keeps_empty_groups, is_absent_path};
36use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
37use dpp::version::PlatformVersion;
38use grovedb::query_result_type::QueryResultType;
39use grovedb::TransactionArg;
40use grovedb_costs::CostContext;
41
42/// Pagination + ordering knobs for `execute_range_count_no_proof`.
43///
44/// Mirrors the protobuf request fields on
45/// `GetDocumentsCountRequestV0` so the drive-abci handler can pass them
46/// through unmodified. `distinct = false` collapses the range walk to a
47/// single summed entry; `distinct = true` returns one entry per distinct
48/// property value within the range.
49#[derive(Debug, Clone, Default)]
50pub struct RangeCountOptions {
51    /// When `true`, return one [`SplitCountEntry`] per distinct property
52    /// value within the range. When `false`, return a single entry
53    /// (empty `key`) summing all per-value counts.
54    pub distinct: bool,
55    /// Maximum number of entries to return. Only meaningful when
56    /// `distinct = true`. `None` means no limit, except over a
57    /// `summableOffCountIndex` index, read through the sum surface, whose
58    /// walk is always bounded: it stops at `u16::MAX` entries. A limit over
59    /// `u16::MAX` reads `u16::MAX` entries.
60    ///
61    /// To paginate, callers narrow the range itself (`color >
62    /// <last-key-from-previous-page>`). There's no cursor field
63    /// because a single-`bytes` cursor would be ambiguous for
64    /// compound (`In + range + distinct`) queries whose natural sort
65    /// is `(in_key, key)`, and range narrowing has the same
66    /// expressivity for the simple cases.
67    pub limit: Option<u32>,
68    /// Sort order for distinct entries. `true` (default) is ascending by
69    /// serialized key bytes. Ignored when `distinct = false`.
70    pub order_by_ascending: bool,
71}
72
73impl DriveDocumentCountQuery<'_> {
74    /// Executes a range-aware count query against a `range_countable`
75    /// index (or a `summableOffCountIndex` index, read through its range sums;
76    /// see the module docs). Path layout is `[contract_doc, doctype, prefix...,
77    /// range_prop_name]`, whose children are the per-value
78    /// `CountTree` leaves keyed by the range property's serialized
79    /// value.
80    ///
81    /// The caller picks the index via
82    /// [`Self::find_range_countable_index_for_where_clauses`]; this
83    /// method assumes:
84    /// - `self.index.range_countable == true`, or the index is a
85    ///   `summableOffCountIndex` index
86    /// - All `Equal` / `In` where clauses cover the index prefix
87    /// - Exactly one range-operator where clause hits the index's last
88    ///   property
89    ///
90    /// ## Execution strategies by mode
91    ///
92    /// - **Flat summed** (no `In`, `distinct = false`): single
93    ///   `query_aggregate_count` call against the merk-level
94    ///   `AggregateCountOnRange` primitive. O(log n).
95    /// - **Compound summed** (`In` on prefix, `distinct = false`):
96    ///   per-In-value fan-out — one `query_aggregate_count` call per
97    ///   matched In branch, summed in Rust. Bounded by the In
98    ///   array's 100-element cap (enforced by
99    ///   [`WhereClause::in_values`]) times O(log n), so worst-case
100    ///   work is 100 × O(log n) regardless of how many documents
101    ///   the range actually matches. Closes the request-amplification
102    ///   surface a pre-fix walk-and-sum implementation had: that
103    ///   path materialized every matched `(in_key, key)` element
104    ///   even though the response was still a single aggregate
105    ///   `u64`.
106    /// - **Distinct mode** (`distinct = true`, with or without
107    ///   `In` on prefix): walks the unified
108    ///   [`Self::distinct_count_path_query`] and emits one entry per
109    ///   matched `(in_key, key)` pair. The path query carries
110    ///   `options.limit` (clamped to `max_query_limit` upstream by
111    ///   the dispatcher) and `options.order_by_ascending`, so
112    ///   per-query work is O(limit × log n). Cross-fork aggregation
113    ///   is intentionally NOT performed server-side; callers reduce
114    ///   by `key` client-side if they want a flat histogram. See the
115    ///   book chapter ("No-Merge Compound Semantics") for the rationale.
116    ///
117    /// ## Returned entry shape
118    ///
119    /// When `options.distinct = false`, returns a single entry with
120    /// `in_key = None`, empty `key`, and `count` equal to the sum of
121    /// all matched per-value counts. When `options.distinct = true`,
122    /// returns one entry per emitted `(in_key, key)` pair, after
123    /// applying `order_by_ascending` and `limit` over the
124    /// lexicographic `(in_key, key)` tuple.
125    pub fn execute_range_count_no_proof(
126        &self,
127        drive: &Drive,
128        options: &RangeCountOptions,
129        transaction: TransactionArg,
130        platform_version: &PlatformVersion,
131    ) -> Result<Vec<SplitCountEntry>, Error> {
132        // A `summableOffCountIndex` index's documents are its range sums.
133        if let Some(sums) = self.counter_sums_query() {
134            let walk_mode = if options.distinct {
135                RangeSumWalkMode::Distinct(distinct_walk_limit(options.limit).unwrap_or(u16::MAX))
136            } else {
137                RangeSumWalkMode::Aggregate
138            };
139            // The walk keeps a preallocated counter at zero, a group its
140            // limit counted, as the proof does, so a flat page ends only at
141            // the limit (across an `IN`, grovedb also charges a value whose
142            // range holds nothing, so there a short page may not be the end).
143            let entries = sums.execute_range_sum_no_proof(
144                drive,
145                &RangeSumOptions {
146                    walk_mode,
147                    carrier_outer_limit: None,
148                    left_to_right: options.order_by_ascending,
149                },
150                transaction,
151                platform_version,
152            )?;
153            return Ok(entries
154                .into_iter()
155                .map(counter_sum_entry_as_count_entry)
156                .collect());
157        }
158        let drive_version = &platform_version.drive;
159        let has_in_on_prefix = self
160            .where_clauses
161            .iter()
162            .any(|wc| wc.operator == WhereOperator::In);
163
164        // Summed mode (both flat and compound `In + range`) goes
165        // through grovedb's `AggregateCountOnRange` primitive
166        // (`query_aggregate_count`), bounding per-query work to
167        // O(log n) per merk-tree fan-out. Compound mode loops over
168        // the In values (≤100 per the `in_values()` validator cap
169        // in `WhereClause::in_values()`) and issues one aggregate
170        // call per value, then sums the results — total bound is
171        // O(|In| × log n), independent of how many documents
172        // actually match the range.
173        //
174        // The pre-fix walk-and-sum path materialized every matched
175        // `(in_key, key)` element via `query_raw` to sum them in
176        // Rust. With one broad range × 100 In values that scans
177        // potentially millions of CountTree elements even though
178        // the response is still a single aggregate `u64` — a
179        // classic request-amplification surface on a public DAPI
180        // endpoint. The per-In fan-out closes that surface.
181        if !options.distinct {
182            // An absent value reads zero as the proof of the same total
183            // verifies it (`aggregate_or_zero_when_absent`).
184            let range_total_verifier = platform_version
185                .drive
186                .methods
187                .verify
188                .document_count
189                .verify_aggregate_count_proof;
190            if has_in_on_prefix {
191                let in_clause = self
192                    .where_clauses
193                    .iter()
194                    .find(|wc| wc.operator == WhereOperator::In)
195                    .ok_or_else(|| {
196                        Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
197                            "compound summed range count path requires an `in` clause; \
198                             dispatcher bug if reached without one",
199                        ))
200                    })?;
201                // `in_values()` enforces non-empty, ≤100, no-duplicates
202                // — same defensive cap every other In consumer in
203                // drive uses. Without it a single 64 MiB gRPC request
204                // could schedule arbitrarily many backend aggregate
205                // reads.
206                let in_values = in_clause.in_values().into_data_with_error()??;
207                let other_clauses: Vec<WhereClause> = self
208                    .where_clauses
209                    .iter()
210                    .filter(|wc| wc.operator != WhereOperator::In)
211                    .cloned()
212                    .collect();
213
214                let mut total: u64 = 0;
215                let mut seen_keys: std::collections::BTreeSet<Vec<u8>> =
216                    std::collections::BTreeSet::new();
217                for value in in_values.iter() {
218                    // Dedupe by serialized canonical key, not by raw
219                    // Value, so that distinct DPP values that
220                    // collapse to the same indexed bytes don't get
221                    // double-counted. `in_values()` already rejects
222                    // raw-Value duplicates, but this is defense-in-
223                    // depth against future Value variants that
224                    // serialize identically (e.g. integer vs
225                    // float-with-zero-fraction).
226                    let key_bytes = self.document_type.serialize_value_for_key(
227                        in_clause.field.as_str(),
228                        value,
229                        platform_version,
230                    )?;
231                    if !seen_keys.insert(key_bytes) {
232                        continue;
233                    }
234
235                    // Per-In-value query: replace the In clause with
236                    // an Equal on the specific value. The resulting
237                    // shape is flat (no In, Equal-prefix + range
238                    // terminator), so `aggregate_count_path_query`
239                    // accepts it and `query_aggregate_count` walks
240                    // boundary nodes in O(log n).
241                    let mut clauses_for_value = other_clauses.clone();
242                    clauses_for_value.push(WhereClause {
243                        field: in_clause.field.clone(),
244                        operator: WhereOperator::Equal,
245                        value: value.clone(),
246                    });
247                    let per_value_query = DriveDocumentCountQuery {
248                        document_type: self.document_type,
249                        contract_id: self.contract_id,
250                        document_type_name: self.document_type_name.clone(),
251                        index: self.index,
252                        where_clauses: clauses_for_value,
253                    };
254                    let path_query =
255                        per_value_query.aggregate_count_path_query(platform_version)?;
256                    // Destructure the `CostContext` explicitly rather than
257                    // calling `.unwrap()` on it: `CostContext::unwrap` is
258                    // infallible (it just drops the cost field), but the
259                    // visual pattern collides with `Option/Result::unwrap`
260                    // and makes review noisier. Cost is discarded here
261                    // because the per-mode dispatcher in `drive_dispatcher`
262                    // wraps these executors with its own fee accounting —
263                    // see the module-level docstring.
264                    let CostContext { value, cost: _ } = drive.grove.query_aggregate_count(
265                        &path_query,
266                        transaction,
267                        &drive_version.grove_version,
268                    );
269                    let count = aggregate_or_zero_when_absent(
270                        drive,
271                        &path_query.path,
272                        value,
273                        range_total_verifier,
274                        transaction,
275                        platform_version,
276                    )?;
277                    total = total.saturating_add(count);
278                }
279                return Ok(vec![SplitCountEntry {
280                    in_key: None,
281                    key: Vec::new(),
282                    // Range-summed total derived from the executor's
283                    // per-In aggregate fan-out — verified count.
284                    count: Some(total),
285                }]);
286            }
287            // Flat summed (no In on prefix): single aggregate read.
288            let path_query = self.aggregate_count_path_query(platform_version)?;
289            // See In-fan-out branch above for the destructure rationale.
290            let CostContext { value, cost: _ } = drive.grove.query_aggregate_count(
291                &path_query,
292                transaction,
293                &drive_version.grove_version,
294            );
295            let count = aggregate_or_zero_when_absent(
296                drive,
297                &path_query.path,
298                value,
299                range_total_verifier,
300                transaction,
301                platform_version,
302            )?;
303            return Ok(vec![SplitCountEntry {
304                in_key: None,
305                key: Vec::new(),
306                // Single `AggregateCountOnRange` read — explicit
307                // verified count.
308                count: Some(count),
309            }]);
310        }
311
312        // Distinct mode (with or without In on prefix): walk and
313        // emit per-`(in_key, key)` entries. Bounded by the request's
314        // `limit` clause — the dispatcher already clamped that to
315        // `max_query_limit`, so this walk is O(limit × log n) and
316        // can't blow past the operator's DoS budget.
317        //
318        // Builds a single path query via the unified
319        // `distinct_count_path_query` builder. For an Equal-only
320        // prefix this collapses to a flat range-only query at the
321        // terminator's property-name subtree; for an In-on-prefix
322        // it becomes a compound query with one outer `Key` per In
323        // value (sorted lex-ascending by the builder) plus a
324        // `subquery_path`/`subquery` descending to the terminator's
325        // range item. The builder pushes the caller's `limit` and
326        // `order_by_ascending` directly into grovedb so the walk
327        // stops at `limit` elements in the requested direction —
328        // no Rust-side sort/reverse/truncate needed.
329        let (path_query_limit, left_to_right) = (
330            distinct_walk_limit(options.limit),
331            options.order_by_ascending,
332        );
333        let path_query =
334            self.distinct_count_path_query(path_query_limit, left_to_right, platform_version)?;
335        let base_path_len = path_query.path.len();
336
337        let mut drive_operations = vec![];
338        let result = drive.grove_get_raw_path_query(
339            &path_query,
340            transaction,
341            // PathKeyElementTrio so we can recover the In value from
342            // the emitted element's full path (for compound queries
343            // the In value sits at `path[base_path_len]` — the first
344            // segment beyond the path query's `path`).
345            QueryResultType::QueryPathKeyElementTrioResultType,
346            &mut drive_operations,
347            drive_version,
348        );
349        let elements = match result {
350            Ok((elements, _)) => elements,
351            Err(error) if is_absent_path(&error) => {
352                // No matching prefix path — distinct mode returns
353                // an empty entry list. (Summed modes returned earlier
354                // via the aggregate fast path, so the empty case is
355                // distinct-only here.)
356                return Ok(Vec::new());
357            }
358            Err(e) => return Err(e),
359        };
360
361        // Walk emitted `(path, key, element)` triples and build the
362        // unmerged entry list. For compound (In-on-prefix) queries
363        // the In value sits at `path[base_path_len]`; for flat
364        // queries `path.len() == base_path_len` so `in_key` is
365        // `None`. We DO NOT collapse multiple emitted entries with
366        // the same `key` into one — that's the whole point of the
367        // no-merge contract.
368        let keeps_empty_groups = index_keeps_empty_groups(self.document_type, self.index);
369        let mut entries: Vec<SplitCountEntry> = Vec::new();
370        for triple in elements.to_path_key_elements() {
371            let (path, key, element) = triple;
372            let count = element.count_value_or_default();
373            // An empty group a preallocation or an outliving index left
374            // stays, as a count of zero: the walk's limit counted it (see
375            // the helper).
376            if count == 0 && !keeps_empty_groups {
377                continue;
378            }
379            let in_key = if has_in_on_prefix && path.len() > base_path_len {
380                Some(path[base_path_len].clone())
381            } else {
382                None
383            };
384            // Distinct-walk emits one entry per distinct value
385            // with its verified count; always `Some(_)`.
386            entries.push(SplitCountEntry {
387                in_key,
388                key,
389                count: Some(count),
390            });
391        }
392
393        // Distinct mode: grovedb already emitted entries in the
394        // requested direction (controlled by `left_to_right`) and
395        // truncated to the path-query limit, so we return the entry
396        // list as-is. The In keys are lex-sorted by the builder
397        // (see `distinct_count_path_query`), so the natural emit
398        // order is `(in_key_lex_asc, key_lex_asc)` for ascending
399        // and `(in_key_lex_desc, key_lex_desc)` for descending —
400        // the documented order contract holds by construction.
401        //
402        // For pagination, callers narrow the range bound itself
403        // (`color > <last-key>` for the next page) rather than
404        // passing a cursor — see `RangeCountOptions::limit` doc.
405        Ok(entries)
406    }
407
408    /// Generates a grovedb `AggregateCountOnRange` proof for a
409    /// range-count query against a `range_countable` index (a
410    /// `summableOffCountIndex` index proves its range sum instead). The returned
411    /// proof bytes can be verified client-side via
412    /// `GroveDb::verify_aggregate_count_query`, which yields
413    /// `(root_hash, count)` — replacing the materialize-and-count proof
414    /// path that capped at `u16::MAX` documents.
415    ///
416    /// Limitations vs. [`Self::execute_range_count_no_proof`]:
417    /// - Returns ONLY the total count (a single number, no
418    ///   per-distinct-value entries) — `AggregateCountOnRange` is a
419    ///   single-aggregate primitive at the merk layer.
420    /// - Requires the prefix to resolve to exactly one path. `In` on
421    ///   prefix properties is not supported because grovedb's aggregate
422    ///   primitive only lifts a single inner range.
423    pub fn execute_aggregate_count_with_proof(
424        &self,
425        drive: &Drive,
426        transaction: TransactionArg,
427        platform_version: &PlatformVersion,
428    ) -> Result<Vec<u8>, Error> {
429        // A `summableOffCountIndex` index's documents are its range sums.
430        if let Some(sums) = self.counter_sums_query() {
431            return sums.execute_aggregate_sum_with_proof(drive, transaction, platform_version);
432        }
433        let drive_version = &platform_version.drive;
434        let path_query = self.aggregate_count_path_query(platform_version)?;
435        // Destructure rather than `.unwrap()` — see the In fan-out branch
436        // in `execute_range_count_no_proof` for rationale.
437        let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
438            &path_query,
439            None,
440            transaction,
441            &drive_version.grove_version,
442        );
443        let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
444        Ok(proof)
445    }
446
447    /// Generates a regular grovedb range proof against this count
448    /// query's `range_countable` index (a `summableOffCountIndex` index
449    /// proves its per-value range sums instead) — the distinct-counts-with-
450    /// proof companion to [`Self::execute_aggregate_count_with_proof`].
451    ///
452    /// No new prover code: the leaf is a `ProvableCountTree` and
453    /// merk's existing `prove_query` already emits `KVCount(key,
454    /// value, count)` per matched in-range key (via
455    /// `to_kv_count_node`). Each `count` is hash-bound to the merk
456    /// root via `node_hash_with_count`, so the per-key correctness
457    /// guarantee comes for free with the standard hash-chain check —
458    /// the SDK-side
459    /// [`drive_proof_verifier::verify_distinct_count_proof`] just
460    /// pulls the counts out of the proof's op stream after the
461    /// integrity check passes.
462    ///
463    /// Trade-off vs. the aggregate prove path:
464    /// - Returns per-distinct-value counts (one `(key, count)` per
465    ///   matched lot value), not just a single sum.
466    /// - Proof size is O(distinct values matched), not O(log n) — so
467    ///   ~1 `KVCount` op per matched key instead of subtree collapse
468    ///   via `HashWithCount`. Still strictly smaller than
469    ///   materialize-and-count, which would emit each underlying doc.
470    pub fn execute_distinct_count_with_proof(
471        &self,
472        drive: &Drive,
473        limit: u16,
474        left_to_right: bool,
475        transaction: TransactionArg,
476        platform_version: &PlatformVersion,
477    ) -> Result<Vec<u8>, Error> {
478        // A `summableOffCountIndex` index's documents are its range sums.
479        if let Some(sums) = self.counter_sums_query() {
480            return sums.execute_distinct_sum_with_proof(
481                drive,
482                limit,
483                left_to_right,
484                transaction,
485                platform_version,
486            );
487        }
488        let drive_version = &platform_version.drive;
489        let path_query =
490            self.distinct_count_path_query(Some(limit), left_to_right, platform_version)?;
491        // Destructure rather than `.unwrap()` — see the In fan-out branch
492        // in `execute_range_count_no_proof` for rationale.
493        let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
494            &path_query,
495            None,
496            transaction,
497            &drive_version.grove_version,
498        );
499        let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
500        Ok(proof)
501    }
502
503    /// Generates a grovedb **carrier** `AggregateCountOnRange` proof
504    /// for `In + range` queries with `group_by = [in_field]`. The
505    /// proof commits one aggregate count per resolved In branch
506    /// via grovedb's carrier-subquery composition
507    /// ([PR #663](https://github.com/dashpay/grovedb/pull/663)).
508    ///
509    /// Path query: see
510    /// [`Self::carrier_aggregate_count_path_query`].
511    ///
512    /// Trade-off vs. the alternative
513    /// [`Self::execute_distinct_count_with_proof`]
514    /// (`GroupByCompound` shape):
515    /// - **This** (carrier-ACOR): O(|In| · (log B + log C')) proof
516    ///   bytes. One commit per merk-tree boundary node per In
517    ///   branch — preserves the per-branch aggregate granularity
518    ///   that `group_by = [in_field, range_field]` can't express
519    ///   (the compound shape commits per-distinct-value-pair
520    ///   entries).
521    /// - **Alternative** (distinct compound): O(|In| · R · log C')
522    ///   where R is distinct in-range values per branch. Carries
523    ///   strictly more information (one `(in_key, range_key)`
524    ///   pair per resolved doc) at substantially larger bytes.
525    ///
526    /// Verified client-side via
527    /// [`grovedb::GroveDb::verify_aggregate_count_query_per_key`],
528    /// which returns `(RootHash, Vec<(Vec<u8>, u64)>)`.
529    ///
530    /// # Arguments
531    /// * `left_to_right` — proof-shaping bit. Threaded into the
532    ///   outer `Query` via `Query::new_with_direction(left_to_right)`
533    ///   on the inner carrier path query (see
534    ///   [`Self::carrier_aggregate_count_path_query`]). `true` walks
535    ///   the outer range ascending and emits the per-branch `u64`s
536    ///   in lex-ascending key order; `false` walks descending and
537    ///   emits them in lex-descending order. The serialized
538    ///   `PathQuery` bytes differ between the two — the verifier
539    ///   rebuilds the path query from `(query, limit, left_to_right)`
540    ///   on its side, so the value passed here must match what the
541    ///   caller will pass to
542    ///   [`Self::verify_carrier_aggregate_count_proof`] or the
543    ///   tenderdash root check fails.
544    pub fn execute_carrier_aggregate_count_with_proof(
545        &self,
546        drive: &Drive,
547        limit: Option<u16>,
548        left_to_right: bool,
549        transaction: TransactionArg,
550        platform_version: &PlatformVersion,
551    ) -> Result<Vec<u8>, Error> {
552        // A `summableOffCountIndex` index's documents are its range sums.
553        if let Some(sums) = self.counter_sums_query() {
554            return sums.execute_carrier_aggregate_sum_with_proof(
555                drive,
556                limit,
557                left_to_right,
558                transaction,
559                platform_version,
560            );
561        }
562        let drive_version = &platform_version.drive;
563        let path_query =
564            self.carrier_aggregate_count_path_query(limit, left_to_right, platform_version)?;
565        // Same destructure pattern as the sibling aggregate / distinct
566        // executors. `get_proved_path_query` returns `CostContext<Result>`;
567        // ignoring the cost field is the same pattern those use today.
568        let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
569            &path_query,
570            None,
571            transaction,
572            &drive_version.grove_version,
573        );
574        let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
575        Ok(proof)
576    }
577}
578
579/// The distinct walk's storage limit for a request's `limit`: a limit over
580/// `u16::MAX` saturates there. Shared by the regular walk and a
581/// `summableOffCountIndex` index's walk through the sum surface, so both
582/// read the same number of entries for one limit.
583fn distinct_walk_limit(limit: Option<u32>) -> Option<u16> {
584    limit.map(|limit| u16::try_from(limit).unwrap_or(u16::MAX))
585}