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::{supported_grovedb_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.
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(supported_grovedb_proof_bytes(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        supported_grovedb_proof_bytes(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(supported_grovedb_proof_bytes(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(
152            supported_grovedb_proof_bytes(proof)?,
153            limit,
154            left_to_right,
155            platform_version,
156        )
157        .map_drive_error(proof, mtd)?;
158
159    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
160
161    Ok(entries)
162}
163
164/// Verify a **carrier** `AggregateSumOnRange` proof against a
165/// `rangeSummable: true` index and return the per-`In`-branch sums.
166///
167/// Thin tenderdash-composition wrapper over
168/// [`DriveDocumentSumQuery::verify_carrier_aggregate_sum_proof`].
169/// Sum analog of count's
170/// [`super::document_count::verify_carrier_aggregate_count_proof`].
171/// Used by the prove path when the request shape is
172/// `select=SUM, group_by=[in_field], where = In(in_field) +
173/// range(other_field), prove=true` — drive's `detect_sum_mode`
174/// routes that shape to
175/// `DocumentSumMode::RangeAggregateCarrierProof`, which collapses
176/// each In branch's range into a single committed `i64`.
177///
178/// Result: one [`SumEntry`] per **present** In branch with
179/// `in_key = <serialized In value>`, `key = []`, `sum = Some(n)`.
180/// Absent In branches are omitted; callers that need to surface
181/// "queried but absent" diff their In array against the returned
182/// `in_key`s.
183pub fn verify_carrier_aggregate_sum_proof(
184    query: &DriveDocumentSumQuery,
185    proof: &Proof,
186    mtd: &ResponseMetadata,
187    limit: Option<u16>,
188    left_to_right: bool,
189    platform_version: &PlatformVersion,
190    provider: &dyn ContextProvider,
191) -> Result<Vec<SumEntry>, Error> {
192    let (root_hash, per_key_sums) = query
193        .verify_carrier_aggregate_sum_proof(
194            supported_grovedb_proof_bytes(proof)?,
195            limit,
196            left_to_right,
197            platform_version,
198        )
199        .map_drive_error(proof, mtd)?;
200
201    verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
202
203    // Map drive's `Vec<(Vec<u8>, i64)>` carrier shape onto the
204    // SDK's `Vec<SumEntry>` so the call sites stay uniform.
205    let entries = per_key_sums
206        .into_iter()
207        .map(|(in_key, sum)| SumEntry {
208            in_key: Some(in_key),
209            key: Vec::new(),
210            sum: Some(sum),
211        })
212        .collect();
213    Ok(entries)
214}