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::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.
56pub fn verify_aggregate_sum_proof(
57    query: &DriveDocumentSumQuery,
58    proof: &Proof,
59    mtd: &ResponseMetadata,
60    platform_version: &PlatformVersion,
61    provider: &dyn ContextProvider,
62) -> Result<i64, Error> {
63    let (root_hash, sum) = query
64        .verify_aggregate_sum_proof(&proof.grovedb_proof, platform_version)
65        .map_drive_error(proof, mtd)?;
66
67    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
68
69    Ok(sum)
70}
71
72/// Verify a grovedb proof of the document type's primary-key
73/// `SumTree` element and return the unfiltered total sum.
74///
75/// Thin tenderdash-composition wrapper over
76/// [`DriveDocumentSumQuery::verify_primary_key_sum_tree_proof`].
77/// Used by the prove path's `documents_summable: "<prop>"` fast
78/// path — when the where clauses are empty and the document type
79/// has a matching `documents_summable`, the server proves the
80/// primary-key SumTree element directly and the SDK extracts the
81/// sum from the verified element.
82pub fn verify_primary_key_sum_tree_proof(
83    contract_id: [u8; 32],
84    document_type_name: &str,
85    proof: &Proof,
86    mtd: &ResponseMetadata,
87    platform_version: &PlatformVersion,
88    provider: &dyn ContextProvider,
89) -> Result<i64, Error> {
90    let (root_hash, sum) = DriveDocumentSumQuery::verify_primary_key_sum_tree_proof(
91        &proof.grovedb_proof,
92        contract_id,
93        document_type_name,
94        platform_version,
95    )
96    .map_drive_error(proof, mtd)?;
97
98    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
99
100    Ok(sum)
101}
102
103/// Verify a grovedb point-lookup sum proof and return the per-branch
104/// entries. Sum analog of count's
105/// [`super::document_count::verify_point_lookup_count_proof`].
106///
107/// Thin tenderdash-composition wrapper over
108/// [`DriveDocumentSumQuery::verify_point_lookup_sum_proof`]. Used
109/// by the prove path's Equal/`In` sum queries against a
110/// `summable: "<prop>"` index — one entry per **present** queried
111/// key (absent keys are silently omitted because today's path
112/// query doesn't request absence proofs, matching count's
113/// behavior).
114pub fn verify_point_lookup_sum_proof(
115    query: &DriveDocumentSumQuery,
116    proof: &Proof,
117    mtd: &ResponseMetadata,
118    platform_version: &PlatformVersion,
119    provider: &dyn ContextProvider,
120) -> Result<Vec<SumEntry>, Error> {
121    let (root_hash, entries) = query
122        .verify_point_lookup_sum_proof(&proof.grovedb_proof, platform_version)
123        .map_drive_error(proof, mtd)?;
124
125    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
126
127    Ok(entries)
128}
129
130/// Verify a per-distinct-key range-sum proof against a
131/// `rangeSummable: true` index and return the per-`(in_key, key)`
132/// sums.
133///
134/// Thin tenderdash-composition wrapper over
135/// [`DriveDocumentSumQuery::verify_distinct_sum_proof`]. Sum analog
136/// of count's
137/// [`super::document_count::verify_distinct_count_proof`]. Used by
138/// the prove path's `RangeDistinctProof` mode (GroupByRange /
139/// GroupByCompound + range + prove against a `rangeSummable: true`
140/// index).
141pub fn verify_distinct_sum_proof(
142    query: &DriveDocumentSumQuery,
143    proof: &Proof,
144    mtd: &ResponseMetadata,
145    limit: u16,
146    left_to_right: bool,
147    platform_version: &PlatformVersion,
148    provider: &dyn ContextProvider,
149) -> Result<Vec<SumEntry>, Error> {
150    let (root_hash, entries) = query
151        .verify_distinct_sum_proof(&proof.grovedb_proof, limit, left_to_right, platform_version)
152        .map_drive_error(proof, mtd)?;
153
154    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
155
156    Ok(entries)
157}
158
159/// Verify a **carrier** `AggregateSumOnRange` proof against a
160/// `rangeSummable: true` index and return the per-`In`-branch sums.
161///
162/// Thin tenderdash-composition wrapper over
163/// [`DriveDocumentSumQuery::verify_carrier_aggregate_sum_proof`].
164/// Sum analog of count's
165/// [`super::document_count::verify_carrier_aggregate_count_proof`].
166/// Used by the prove path when the request shape is
167/// `select=SUM, group_by=[in_field], where = In(in_field) +
168/// range(other_field), prove=true` — drive's `detect_sum_mode`
169/// routes that shape to
170/// `DocumentSumMode::RangeAggregateCarrierProof`, which collapses
171/// each In branch's range into a single committed `i64`.
172///
173/// Result: one [`SumEntry`] per **present** In branch with
174/// `in_key = <serialized In value>`, `key = []`, `sum = Some(n)`.
175/// Absent In branches are omitted; callers that need to surface
176/// "queried but absent" diff their In array against the returned
177/// `in_key`s.
178pub fn verify_carrier_aggregate_sum_proof(
179    query: &DriveDocumentSumQuery,
180    proof: &Proof,
181    mtd: &ResponseMetadata,
182    limit: Option<u16>,
183    left_to_right: bool,
184    platform_version: &PlatformVersion,
185    provider: &dyn ContextProvider,
186) -> Result<Vec<SumEntry>, Error> {
187    let (root_hash, per_key_sums) = query
188        .verify_carrier_aggregate_sum_proof(
189            &proof.grovedb_proof,
190            limit,
191            left_to_right,
192            platform_version,
193        )
194        .map_drive_error(proof, mtd)?;
195
196    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
197
198    // Map drive's `Vec<(Vec<u8>, i64)>` carrier shape onto the
199    // SDK's `Vec<SumEntry>` so the call sites stay uniform.
200    let entries = per_key_sums
201        .into_iter()
202        .map(|(in_key, sum)| SumEntry {
203            in_key: Some(in_key),
204            key: Vec::new(),
205            sum: Some(sum),
206        })
207        .collect();
208    Ok(entries)
209}