Skip to main content

drive_proof_verifier/proof/
document_average.rs

1//! Verified average result + free-function proof verifiers for the
2//! average surface.
3//!
4//! Average-side analog of [`super::document_sum::DocumentSum`].
5//! Holds the `(count, sum)` pair recovered from a count-sum-bearing
6//! tree proof (`CountSumTree` / `ProvableCountSumTree` /
7//! `ProvableCountProvableSumTree`). `Aggregate` mode returns one
8//! [`DocumentAverage`]; `Entries` mode returns
9//! [`super::document_split_average::DocumentSplitAverages`] instead.
10//!
11//! Averages are NOT pre-divided server-side — the verifier surfaces
12//! the raw `(count, sum)` and the caller divides. See the proto
13//! file's `AverageResults` docstring for the rationale (precision +
14//! client-chosen representation).
15//!
16//! The generic `FromProof<Q>` impl below intentionally rejects
17//! calls (matching [`super::document_split_count::DocumentSplitCounts`]'s
18//! pattern). Real dispatch lives in the
19//! `FromProof<DocumentQuery>` impl in
20//! `rs-sdk/src/platform/documents/document_average.rs`, which picks
21//! among the free-function verifiers below based on the resolved
22//! `DocumentAverageMode`.
23
24use crate::error::MapGroveDbError;
25use crate::verify::verify_tenderdash_proof;
26use crate::{ContextProvider, Error};
27use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
28use dpp::version::PlatformVersion;
29use drive::query::drive_document_average_query::AverageEntry;
30use drive::query::drive_document_sum_query::DriveDocumentSumQuery;
31
32/// Verify a grovedb point-lookup proof against a count-sum-bearing
33/// index terminator and return per-branch `(count, sum)` entries.
34/// AVG analog of [`super::document_sum::verify_point_lookup_sum_proof`].
35///
36/// Thin tenderdash-composition wrapper over
37/// [`DriveDocumentSumQuery::verify_point_lookup_count_and_sum_proof`].
38/// Used by the prove path's `Aggregate` + Equal/In + no range
39/// shape when the chosen index declares BOTH `summable: "<prop>"`
40/// AND a `countable` terminator.
41pub fn verify_point_lookup_count_and_sum_proof(
42    query: &DriveDocumentSumQuery,
43    proof: &Proof,
44    mtd: &ResponseMetadata,
45    platform_version: &PlatformVersion,
46    provider: &dyn ContextProvider,
47) -> Result<Vec<AverageEntry>, Error> {
48    let (root_hash, entries) = query
49        .verify_point_lookup_count_and_sum_proof(&proof.grovedb_proof, platform_version)
50        .map_drive_error(proof, mtd)?;
51
52    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
53
54    Ok(entries)
55}
56
57/// Verify a per-distinct-key range-AVG proof against an index that
58/// declares BOTH `rangeCountable: true` AND `rangeSummable: true`
59/// (a `rangeAverageable: true` index) and return per-`(in_key,
60/// key)` `(count, sum)` entries. AVG analog of
61/// [`super::document_sum::verify_distinct_sum_proof`].
62///
63/// Thin tenderdash-composition wrapper over
64/// [`DriveDocumentSumQuery::verify_distinct_count_and_sum_proof`].
65/// Used by the prove path's `GroupByRange` / `GroupByCompound` +
66/// range shape on the AVG surface.
67pub fn verify_distinct_count_and_sum_proof(
68    query: &DriveDocumentSumQuery,
69    proof: &Proof,
70    mtd: &ResponseMetadata,
71    limit: u16,
72    left_to_right: bool,
73    platform_version: &PlatformVersion,
74    provider: &dyn ContextProvider,
75) -> Result<Vec<AverageEntry>, Error> {
76    let (root_hash, entries) = query
77        .verify_distinct_count_and_sum_proof(
78            &proof.grovedb_proof,
79            limit,
80            left_to_right,
81            platform_version,
82        )
83        .map_drive_error(proof, mtd)?;
84
85    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
86
87    Ok(entries)
88}
89
90/// The `(count, sum)` pair across documents matching a query,
91/// verified from proof. Client computes `avg = sum / count` using
92/// whichever precision representation it wants.
93///
94/// `count` is `u64` (counts are non-negative); `sum` is `i64`
95/// (matching `DocumentSum`). The grovedb primitive that backs this
96/// is `AggregateCountAndSumOnRange` (PCPS-leaf) for range-filtered
97/// queries, or the primary-key count-sum-bearing element direct
98/// read for empty-where queries on a
99/// `documentsCountable + documentsSummable` doctype.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct DocumentAverage {
102    /// Total matched-document count for the query.
103    pub count: u64,
104    /// Total aggregated value of the `sum_property` for the query.
105    pub sum: i64,
106}
107
108impl DocumentAverage {
109    /// Convenience: compute the average as `f64`. Returns `None`
110    /// when `count == 0` (preserving the divide-by-zero contract
111    /// rather than producing `NaN` / `inf`). Callers that need a
112    /// different representation should divide `self.sum /
113    /// self.count` directly.
114    pub fn as_f64(&self) -> Option<f64> {
115        if self.count == 0 {
116            None
117        } else {
118            Some(self.sum as f64 / self.count as f64)
119        }
120    }
121}
122
123// No generic `FromProof<Q>` impl is provided here — see the
124// `DocumentSum` docstring for the rationale. Callers reach this
125// through `FromProof<DocumentQuery> for DocumentAverage` in
126// `rs-sdk/src/platform/documents/document_average.rs`.
127
128/// Verify a leaf-PCPS `AggregateCountAndSumOnRange` proof and the
129/// surrounding tenderdash commit, returning the verified
130/// `(count, sum)` pair. Used by the prove path's
131/// `select=AVG, group_by=[]` with a range clause on an index that
132/// declares BOTH `rangeCountable: true` AND `rangeSummable: true`
133/// (i.e. the terminator is a `ProvableCountProvableSumTree`).
134///
135/// Thin tenderdash-composition wrapper over
136/// [`DriveDocumentSumQuery::verify_aggregate_count_and_sum_proof`].
137/// Both metrics come from one root-hash-committed traversal of the
138/// PCPS terminator — no way for the server to splice a count from
139/// one set with a sum from another.
140pub fn verify_aggregate_count_and_sum_proof(
141    query: &DriveDocumentSumQuery,
142    proof: &Proof,
143    mtd: &ResponseMetadata,
144    platform_version: &PlatformVersion,
145    provider: &dyn ContextProvider,
146) -> Result<(u64, i64), Error> {
147    let (root_hash, count, sum) = query
148        .verify_aggregate_count_and_sum_proof(&proof.grovedb_proof, platform_version)
149        .map_drive_error(proof, mtd)?;
150
151    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
152
153    Ok((count, sum))
154}
155
156/// Verify a grovedb proof of the document type's primary-key
157/// count-sum-bearing element (`CountSumTree` /
158/// `ProvableCountSumTree` / `ProvableCountProvableSumTree`) and
159/// return the unfiltered `(count, sum)` pair.
160///
161/// Thin tenderdash-composition wrapper over
162/// [`DriveDocumentSumQuery::verify_primary_key_count_sum_tree_proof`].
163/// Used by the prove path's AVG fast path on a doctype that has
164/// both `documentsCountable: true` and `documentsSummable: "<prop>"`
165/// set, with empty where clauses — the server proves the
166/// primary-key element directly and the SDK extracts both metrics
167/// from one verified element.
168pub fn verify_primary_key_count_sum_tree_proof(
169    contract_id: [u8; 32],
170    document_type_name: &str,
171    proof: &Proof,
172    mtd: &ResponseMetadata,
173    platform_version: &PlatformVersion,
174    provider: &dyn ContextProvider,
175) -> Result<(u64, i64), Error> {
176    let (root_hash, count, sum) = DriveDocumentSumQuery::verify_primary_key_count_sum_tree_proof(
177        &proof.grovedb_proof,
178        contract_id,
179        document_type_name,
180        platform_version,
181    )
182    .map_drive_error(proof, mtd)?;
183
184    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
185
186    Ok((count, sum))
187}
188
189/// Verify a **carrier**-PCPS `AggregateCountAndSumOnRange` proof
190/// and return the per-`In`-branch `(count, sum)` triples. AVG analog
191/// of count's
192/// [`super::document_count::verify_carrier_aggregate_count_proof`]
193/// and sum's
194/// [`super::document_sum::verify_carrier_aggregate_sum_proof`].
195///
196/// Thin tenderdash-composition wrapper over
197/// [`DriveDocumentSumQuery::verify_carrier_aggregate_count_and_sum_proof`].
198/// Used by the prove path when the request shape is `select=AVG,
199/// group_by=[in_field], where = In(in_field) + range(other_field),
200/// prove=true` against a PCPS-eligible index — drive routes it to
201/// the carrier-PCPS executor.
202///
203/// Result: one [`AverageEntry`] per **present** In branch with
204/// `in_key = <serialized In value>`, `key = []`, `count = Some(n)`,
205/// `sum = Some(v)`. Absent In branches are omitted; the count and
206/// sum axes never disagree on present/absent because the proof
207/// commits both metrics from the same merk traversal.
208pub fn verify_carrier_aggregate_count_and_sum_proof(
209    query: &DriveDocumentSumQuery,
210    proof: &Proof,
211    mtd: &ResponseMetadata,
212    limit: Option<u16>,
213    left_to_right: bool,
214    platform_version: &PlatformVersion,
215    provider: &dyn ContextProvider,
216) -> Result<Vec<AverageEntry>, Error> {
217    let (root_hash, per_key_count_sum) = query
218        .verify_carrier_aggregate_count_and_sum_proof(
219            &proof.grovedb_proof,
220            limit,
221            left_to_right,
222            platform_version,
223        )
224        .map_drive_error(proof, mtd)?;
225
226    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
227
228    let entries = per_key_count_sum
229        .into_iter()
230        .map(|(in_key, count, sum)| AverageEntry {
231            in_key: Some(in_key),
232            key: Vec::new(),
233            count: Some(count),
234            sum: Some(sum),
235        })
236        .collect();
237    Ok(entries)
238}