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::{supported_grovedb_proof_bytes, 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(
50            supported_grovedb_proof_bytes(proof)?,
51            platform_version,
52        )
53        .map_drive_error(proof, mtd)?;
54
55    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
56
57    Ok(entries)
58}
59
60/// Verify a per-distinct-key range-AVG proof against an index that
61/// declares BOTH `rangeCountable: true` AND `rangeSummable: true`
62/// (a `rangeAverageable: true` index) and return per-`(in_key,
63/// key)` `(count, sum)` entries. AVG analog of
64/// [`super::document_sum::verify_distinct_sum_proof`].
65///
66/// Thin tenderdash-composition wrapper over
67/// [`DriveDocumentSumQuery::verify_distinct_count_and_sum_proof`].
68/// Used by the prove path's `GroupByRange` / `GroupByCompound` +
69/// range shape on the AVG surface.
70pub fn verify_distinct_count_and_sum_proof(
71    query: &DriveDocumentSumQuery,
72    proof: &Proof,
73    mtd: &ResponseMetadata,
74    limit: u16,
75    left_to_right: bool,
76    platform_version: &PlatformVersion,
77    provider: &dyn ContextProvider,
78) -> Result<Vec<AverageEntry>, Error> {
79    let (root_hash, entries) = query
80        .verify_distinct_count_and_sum_proof(
81            supported_grovedb_proof_bytes(proof)?,
82            limit,
83            left_to_right,
84            platform_version,
85        )
86        .map_drive_error(proof, mtd)?;
87
88    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
89
90    Ok(entries)
91}
92
93/// The `(count, sum)` pair across documents matching a query,
94/// verified from proof. Client computes `avg = sum / count` using
95/// whichever precision representation it wants.
96///
97/// `count` is `u64` (counts are non-negative); `sum` is `i64`
98/// (matching `DocumentSum`). The grovedb primitive that backs this
99/// is `AggregateCountAndSumOnRange` (PCPS-leaf) for range-filtered
100/// queries, or the primary-key count-sum-bearing element direct
101/// read for empty-where queries on a
102/// `documentsCountable + documentsSummable` doctype.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct DocumentAverage {
105    /// Total matched-document count for the query.
106    pub count: u64,
107    /// Total aggregated value of the `sum_property` for the query.
108    pub sum: i64,
109}
110
111impl DocumentAverage {
112    /// Convenience: compute the average as `f64`. Returns `None`
113    /// when `count == 0` (preserving the divide-by-zero contract
114    /// rather than producing `NaN` / `inf`). Callers that need a
115    /// different representation should divide `self.sum /
116    /// self.count` directly.
117    pub fn as_f64(&self) -> Option<f64> {
118        if self.count == 0 {
119            None
120        } else {
121            Some(self.sum as f64 / self.count as f64)
122        }
123    }
124}
125
126// No generic `FromProof<Q>` impl is provided here — see the
127// `DocumentSum` docstring for the rationale. Callers reach this
128// through `FromProof<DocumentQuery> for DocumentAverage` in
129// `rs-sdk/src/platform/documents/document_average.rs`.
130
131/// Verify a leaf-PCPS `AggregateCountAndSumOnRange` proof and the
132/// surrounding tenderdash commit, returning the verified
133/// `(count, sum)` pair. Used by the prove path's
134/// `select=AVG, group_by=[]` with a range clause on an index that
135/// declares BOTH `rangeCountable: true` AND `rangeSummable: true`
136/// (i.e. the terminator is a `ProvableCountProvableSumTree`).
137///
138/// Thin tenderdash-composition wrapper over
139/// [`DriveDocumentSumQuery::verify_aggregate_count_and_sum_proof`].
140/// Both metrics come from one root-hash-committed traversal of the
141/// PCPS terminator — no way for the server to splice a count from
142/// one set with a sum from another.
143pub fn verify_aggregate_count_and_sum_proof(
144    query: &DriveDocumentSumQuery,
145    proof: &Proof,
146    mtd: &ResponseMetadata,
147    platform_version: &PlatformVersion,
148    provider: &dyn ContextProvider,
149) -> Result<(u64, i64), Error> {
150    let (root_hash, count, sum) = query
151        .verify_aggregate_count_and_sum_proof(
152            supported_grovedb_proof_bytes(proof)?,
153            platform_version,
154        )
155        .map_drive_error(proof, mtd)?;
156
157    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
158
159    Ok((count, sum))
160}
161
162/// Verify a grovedb proof of the document type's primary-key
163/// count-sum-bearing element (`CountSumTree` /
164/// `ProvableCountSumTree` / `ProvableCountProvableSumTree`) and
165/// return the unfiltered `(count, sum)` pair.
166///
167/// Thin tenderdash-composition wrapper over
168/// [`DriveDocumentSumQuery::verify_primary_key_count_sum_tree_proof`].
169/// Used by the prove path's AVG fast path on a doctype that has
170/// both `documentsCountable: true` and `documentsSummable: "<prop>"`
171/// set, with empty where clauses — the server proves the
172/// primary-key element directly and the SDK extracts both metrics
173/// from one verified element.
174pub fn verify_primary_key_count_sum_tree_proof(
175    contract_id: [u8; 32],
176    document_type_name: &str,
177    proof: &Proof,
178    mtd: &ResponseMetadata,
179    platform_version: &PlatformVersion,
180    provider: &dyn ContextProvider,
181) -> Result<(u64, i64), Error> {
182    let (root_hash, count, sum) = DriveDocumentSumQuery::verify_primary_key_count_sum_tree_proof(
183        supported_grovedb_proof_bytes(proof)?,
184        contract_id,
185        document_type_name,
186        platform_version,
187    )
188    .map_drive_error(proof, mtd)?;
189
190    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
191
192    Ok((count, sum))
193}
194
195/// Verify a **carrier**-PCPS `AggregateCountAndSumOnRange` proof
196/// and return the per-`In`-branch `(count, sum)` triples. AVG analog
197/// of count's
198/// [`super::document_count::verify_carrier_aggregate_count_proof`]
199/// and sum's
200/// [`super::document_sum::verify_carrier_aggregate_sum_proof`].
201///
202/// Thin tenderdash-composition wrapper over
203/// [`DriveDocumentSumQuery::verify_carrier_aggregate_count_and_sum_proof`].
204/// Used by the prove path when the request shape is `select=AVG,
205/// group_by=[in_field], where = In(in_field) + range(other_field),
206/// prove=true` against a PCPS-eligible index — drive routes it to
207/// the carrier-PCPS executor.
208///
209/// Result: one [`AverageEntry`] per **present** In branch with
210/// `in_key = <serialized In value>`, `key = []`, `count = Some(n)`,
211/// `sum = Some(v)`. Absent In branches are omitted; the count and
212/// sum axes never disagree on present/absent because the proof
213/// commits both metrics from the same merk traversal.
214pub fn verify_carrier_aggregate_count_and_sum_proof(
215    query: &DriveDocumentSumQuery,
216    proof: &Proof,
217    mtd: &ResponseMetadata,
218    limit: Option<u16>,
219    left_to_right: bool,
220    platform_version: &PlatformVersion,
221    provider: &dyn ContextProvider,
222) -> Result<Vec<AverageEntry>, Error> {
223    let (root_hash, per_key_count_sum) = query
224        .verify_carrier_aggregate_count_and_sum_proof(
225            supported_grovedb_proof_bytes(proof)?,
226            limit,
227            left_to_right,
228            platform_version,
229        )
230        .map_drive_error(proof, mtd)?;
231
232    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
233
234    let entries = per_key_count_sum
235        .into_iter()
236        .map(|(in_key, count, sum)| AverageEntry {
237            in_key: Some(in_key),
238            key: Vec::new(),
239            count: Some(count),
240            sum: Some(sum),
241        })
242        .collect();
243    Ok(entries)
244}