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}