Skip to main content

drive_proof_verifier/proof/
document_sum.rs

1//! Verified sum result + free-function proof verifiers for the
2//! sum-tree surface.
3//!
4//! Sum-side analog of [`super::document_count`]. Holds the
5//! aggregated `i64` recovered from a sum-tree proof — `Aggregate`
6//! mode returns one [`DocumentSum`]; `Entries` mode returns
7//! [`super::document_split_sum::DocumentSplitSums`] instead.
8//!
9//! The generic `FromProof<Q>` impl below intentionally rejects
10//! calls (matching [`super::document_split_count::DocumentSplitCounts`]'s
11//! pattern): the underlying proof primitive depends on the per-mode
12//! routing of `(group_by, where_clauses, prove)`, which the generic
13//! `Q` constraint can't carry. The real dispatch lives in the
14//! `FromProof<DocumentQuery>` impl in
15//! `rs-sdk/src/platform/documents/document_sum.rs`, which picks
16//! among the free-function verifiers below based on the resolved
17//! `DocumentSumMode`.
18
19use crate::error::MapGroveDbError;
20use crate::verify::{document_query_proof_bytes, verify_tenderdash_proof};
21use crate::{ContextProvider, Error};
22use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
23use dpp::version::PlatformVersion;
24use drive::query::drive_document_sum_query::{DriveDocumentSumQuery, SumEntry};
25
26/// The aggregated sum of an integer property across documents matching
27/// a query, verified from proof.
28///
29/// Signed because grovedb's `SumTree` value type is `i64` — sums can
30/// in principle be negative (typically signaling i64 overflow into
31/// negative space, which the verifier surfaces explicitly). For
32/// tip-jar-style non-negative aggregations this stays ≥ 0.
33///
34/// No generic `FromProof<Q>` impl is provided here — the real
35/// proof dispatch needs the `DocumentQuery` request shape to pick
36/// the right per-mode verifier (range-aggregate / point-lookup /
37/// primary-key SumTree / In-carrier). Callers reach this through
38/// `FromProof<DocumentQuery> for DocumentSum` in
39/// `rs-sdk/src/platform/documents/document_sum.rs`, which routes to
40/// the per-shape verifier free functions below.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct DocumentSum(pub i64);
43
44/// Verify a grovedb `AggregateSumOnRange` proof and the surrounding
45/// tenderdash commit, returning the verified `i64` sum from one
46/// range traversal. Used by the prove path's
47/// `select=SUM, group_by=[]` with a range clause on a
48/// `rangeSummable: true` index.
49///
50/// Thin tenderdash-composition wrapper over
51/// [`DriveDocumentSumQuery::verify_aggregate_sum_proof`] in rs-drive
52/// (which does the merk-level verification via
53/// `GroveDb::verify_aggregate_sum_query`). Both helpers share the
54/// prover's `aggregate_sum_path_query` so the path query bytes
55/// match byte-for-byte across the network.
56///
57/// `platform_version` must be the protocol version `mtd` names, or the latest known
58/// version when `mtd` names a newer one; otherwise the answer is refused with
59/// [`Error::ProofProtocolVersionMismatch`].
60pub fn verify_aggregate_sum_proof(
61    query: &DriveDocumentSumQuery,
62    proof: &Proof,
63    mtd: &ResponseMetadata,
64    platform_version: &PlatformVersion,
65    provider: &dyn ContextProvider,
66) -> Result<i64, Error> {
67    let (root_hash, sum) = query
68        .verify_aggregate_sum_proof(
69            document_query_proof_bytes(proof, mtd, platform_version)?,
70            platform_version,
71        )
72        .map_drive_error(proof, mtd)?;
73
74    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
75
76    Ok(sum)
77}
78
79/// Verify a grovedb proof of the document type's primary-key
80/// `SumTree` element and return the unfiltered total sum.
81///
82/// Thin tenderdash-composition wrapper over
83/// [`DriveDocumentSumQuery::verify_primary_key_sum_tree_proof`].
84/// Used by the prove path's `documents_summable: "<prop>"` fast
85/// path — when the where clauses are empty and the document type
86/// has a matching `documents_summable`, the server proves the
87/// primary-key SumTree element directly and the SDK extracts the
88/// sum from the verified element.
89///
90/// `platform_version` must be the protocol version `mtd` names, or the latest known
91/// version when `mtd` names a newer one; otherwise the answer is refused with
92/// [`Error::ProofProtocolVersionMismatch`].
93pub fn verify_primary_key_sum_tree_proof(
94    contract_id: [u8; 32],
95    document_type_name: &str,
96    proof: &Proof,
97    mtd: &ResponseMetadata,
98    platform_version: &PlatformVersion,
99    provider: &dyn ContextProvider,
100) -> Result<i64, Error> {
101    let (root_hash, sum) = DriveDocumentSumQuery::verify_primary_key_sum_tree_proof(
102        document_query_proof_bytes(proof, mtd, platform_version)?,
103        contract_id,
104        document_type_name,
105        platform_version,
106    )
107    .map_drive_error(proof, mtd)?;
108
109    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
110
111    Ok(sum)
112}
113
114/// Verify a grovedb point-lookup sum proof and return the per-branch
115/// entries. Sum analog of count's
116/// [`super::document_count::verify_point_lookup_count_proof`].
117///
118/// Thin tenderdash-composition wrapper over
119/// [`DriveDocumentSumQuery::verify_point_lookup_sum_proof`]. Used
120/// by the prove path's Equal/`In` sum queries against a
121/// `summable: "<prop>"` index — one entry per **present** queried
122/// key (absent keys are silently omitted because today's path
123/// query doesn't request absence proofs, matching count's
124/// behavior).
125///
126/// `platform_version` must be the protocol version `mtd` names, or the latest known
127/// version when `mtd` names a newer one; otherwise the answer is refused with
128/// [`Error::ProofProtocolVersionMismatch`].
129pub fn verify_point_lookup_sum_proof(
130    query: &DriveDocumentSumQuery,
131    proof: &Proof,
132    mtd: &ResponseMetadata,
133    platform_version: &PlatformVersion,
134    provider: &dyn ContextProvider,
135) -> Result<Vec<SumEntry>, Error> {
136    let (root_hash, entries) = query
137        .verify_point_lookup_sum_proof(
138            document_query_proof_bytes(proof, mtd, platform_version)?,
139            platform_version,
140        )
141        .map_drive_error(proof, mtd)?;
142
143    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
144
145    Ok(entries)
146}
147
148/// Verify a per-distinct-key range-sum proof against a
149/// `rangeSummable: true` index and return the per-`(in_key, key)`
150/// sums.
151///
152/// Thin tenderdash-composition wrapper over
153/// [`DriveDocumentSumQuery::verify_distinct_sum_proof`]. Sum analog
154/// of count's
155/// [`super::document_count::verify_distinct_count_proof`]. Used by
156/// the prove path's `RangeDistinctProof` mode (GroupByRange /
157/// GroupByCompound + range + prove against a `rangeSummable: true`
158/// index).
159///
160/// `platform_version` must be the protocol version `mtd` names, or the latest known
161/// version when `mtd` names a newer one; otherwise the answer is refused with
162/// [`Error::ProofProtocolVersionMismatch`].
163pub fn verify_distinct_sum_proof(
164    query: &DriveDocumentSumQuery,
165    proof: &Proof,
166    mtd: &ResponseMetadata,
167    limit: u16,
168    left_to_right: bool,
169    platform_version: &PlatformVersion,
170    provider: &dyn ContextProvider,
171) -> Result<Vec<SumEntry>, Error> {
172    let (root_hash, entries) = query
173        .verify_distinct_sum_proof(
174            document_query_proof_bytes(proof, mtd, platform_version)?,
175            limit,
176            left_to_right,
177            platform_version,
178        )
179        .map_drive_error(proof, mtd)?;
180
181    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
182
183    Ok(entries)
184}
185
186/// Verify a **carrier** `AggregateSumOnRange` proof against a
187/// `rangeSummable: true` index and return the per-`In`-branch sums.
188///
189/// Thin tenderdash-composition wrapper over
190/// [`DriveDocumentSumQuery::verify_carrier_aggregate_sum_proof`].
191/// Sum analog of count's
192/// [`super::document_count::verify_carrier_aggregate_count_proof`].
193/// Used by the prove path when the request shape is
194/// `select=SUM, group_by=[in_field], where = In(in_field) +
195/// range(other_field), prove=true` — drive's `detect_sum_mode`
196/// routes that shape to
197/// `DocumentSumMode::RangeAggregateCarrierProof`, which collapses
198/// each In branch's range into a single committed `i64`.
199///
200/// Result: one [`SumEntry`] per **present** In branch with
201/// `in_key = <serialized In value>`, `key = []`, `sum = Some(n)`.
202/// Absent In branches are omitted; callers that need to surface
203/// "queried but absent" diff their In array against the returned
204/// `in_key`s.
205///
206/// `platform_version` must be the protocol version `mtd` names, or the latest known
207/// version when `mtd` names a newer one; otherwise the answer is refused with
208/// [`Error::ProofProtocolVersionMismatch`].
209pub fn verify_carrier_aggregate_sum_proof(
210    query: &DriveDocumentSumQuery,
211    proof: &Proof,
212    mtd: &ResponseMetadata,
213    limit: Option<u16>,
214    left_to_right: bool,
215    platform_version: &PlatformVersion,
216    provider: &dyn ContextProvider,
217) -> Result<Vec<SumEntry>, Error> {
218    let (root_hash, per_key_sums) = query
219        .verify_carrier_aggregate_sum_proof(
220            document_query_proof_bytes(proof, mtd, platform_version)?,
221            limit,
222            left_to_right,
223            platform_version,
224        )
225        .map_drive_error(proof, mtd)?;
226
227    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
228
229    // Map drive's `Vec<(Vec<u8>, i64)>` carrier shape onto the
230    // SDK's `Vec<SumEntry>` so the call sites stay uniform.
231    let entries = per_key_sums
232        .into_iter()
233        .map(|(in_key, sum)| SumEntry {
234            in_key: Some(in_key),
235            key: Vec::new(),
236            sum: Some(sum),
237        })
238        .collect();
239    Ok(entries)
240}