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