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}