dash_platform_queries/documents/
document_average.rs1use crate::documents::average_proof_helpers::{
22 assert_select_is_avg, check_average_request, verify_average_query,
23};
24use crate::documents::document_query::DocumentQuery;
25use dapi_grpc::platform::v0::{GetDocumentsResponse, Proof, ResponseMetadata};
26use dash_context_provider::ContextProvider;
27use dpp::dashcore::Network;
28use dpp::version::PlatformVersion;
29use drive_proof_verifier::{AverageEntry, DocumentAverage, FromProof};
30
31fn fold_average_entries(
43 entries: &[AverageEntry],
44) -> Result<DocumentAverage, drive_proof_verifier::Error> {
45 let mut total_count: u64 = 0;
46 let mut total_sum: i64 = 0;
47 for e in entries {
48 if let Some(c) = e.count {
49 total_count = total_count.checked_add(c).ok_or_else(|| {
50 drive_proof_verifier::Error::RequestError {
51 error: "DocumentAverage: u64 overflow folding per-branch counts into a \
52 single aggregate. The proof itself verified, but the requested \
53 total count doesn't fit in u64. Use DocumentSplitAverages to \
54 receive per-branch (u64, i64) and fold with your own arithmetic."
55 .to_string(),
56 }
57 })?;
58 }
59 if let Some(s) = e.sum {
60 total_sum = total_sum.checked_add(s).ok_or_else(|| {
61 drive_proof_verifier::Error::RequestError {
62 error: "DocumentAverage: i64 over/underflow folding per-branch sums into \
63 a single aggregate. The proof itself verified, but the requested \
64 total sum doesn't fit in i64. Use DocumentSplitAverages to \
65 receive per-branch (u64, i64) and fold with your own arithmetic \
66 (e.g. i128)."
67 .to_string(),
68 }
69 })?;
70 }
71 }
72 Ok(DocumentAverage {
73 count: total_count,
74 sum: total_sum,
75 })
76}
77
78impl FromProof<DocumentQuery> for DocumentAverage {
79 type Request = DocumentQuery;
80 type Response = GetDocumentsResponse;
81
82 fn check_request(
86 request: &Self::Request,
87 platform_version: &PlatformVersion,
88 ) -> Result<(), drive_proof_verifier::Error> {
89 check_average_request(request, true, platform_version)
90 }
91
92 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
93 request: I,
94 response: O,
95 _network: Network,
96 platform_version: &PlatformVersion,
97 provider: &'a dyn ContextProvider,
98 ) -> Result<(Option<Self>, ResponseMetadata, Proof), drive_proof_verifier::Error>
99 where
100 Self: 'a,
101 {
102 let request: Self::Request = request.into();
103 assert_select_is_avg(&request)?;
104 let response: Self::Response = response.into();
105 let (entries, mtd, proof) =
106 verify_average_query(request, response, true, platform_version, provider)?;
107 let avg = match entries {
111 None => None,
112 Some(es) => Some(fold_average_entries(&es)?),
113 };
114 Ok((avg, mtd, proof))
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
129
130 fn entry(in_key: Option<Vec<u8>>, count: Option<u64>, sum: Option<i64>) -> AverageEntry {
131 AverageEntry {
132 in_key,
133 key: vec![0u8],
134 count,
135 sum,
136 }
137 }
138
139 #[test]
141 fn fold_average_entries_single_branch_passes_through() {
142 let entries = vec![entry(None, Some(10), Some(250))];
143 let avg = fold_average_entries(&entries).expect("single branch should fold cleanly");
144 assert_eq!(
145 avg,
146 DocumentAverage {
147 count: 10,
148 sum: 250
149 }
150 );
151 }
152
153 #[test]
156 fn fold_average_entries_multi_branch_with_absent_axes() {
157 let entries = vec![
158 entry(Some(vec![1]), Some(5), Some(100)),
159 entry(Some(vec![2]), None, None), entry(Some(vec![3]), Some(3), Some(50)),
161 entry(Some(vec![4]), Some(2), None), ];
163 let avg = fold_average_entries(&entries)
164 .expect("absent axes must contribute 0 on their respective axis");
165 assert_eq!(
166 avg,
167 DocumentAverage {
168 count: 10,
169 sum: 150
170 }
171 );
172 }
173
174 #[test]
179 fn fold_average_entries_count_overflow_returns_error() {
180 let entries = vec![
181 entry(Some(vec![1]), Some(u64::MAX), Some(0)),
182 entry(Some(vec![2]), Some(1), Some(0)),
183 ];
184 let err = fold_average_entries(&entries)
185 .expect_err("u64 count overflow must surface as RequestError, not saturate");
186 let msg = format!("{err:?}");
187 assert!(
188 msg.contains("u64 overflow") && msg.contains("DocumentSplitAverages"),
189 "error must name the count overflow + hint at DocumentSplitAverages; got {msg}"
190 );
191 }
192
193 #[test]
196 fn fold_average_entries_positive_sum_overflow_returns_error() {
197 let entries = vec![
198 entry(Some(vec![1]), Some(0), Some(i64::MAX)),
199 entry(Some(vec![2]), Some(0), Some(1)),
200 ];
201 let err = fold_average_entries(&entries)
202 .expect_err("positive i64 sum overflow must surface as RequestError");
203 let msg = format!("{err:?}");
204 assert!(
205 msg.contains("i64 over/underflow") && msg.contains("DocumentSplitAverages"),
206 "error must name the sum over/underflow + hint at DocumentSplitAverages; got {msg}"
207 );
208 }
209
210 #[test]
215 fn fold_average_entries_negative_sum_underflow_returns_error() {
216 let entries = vec![
217 entry(Some(vec![1]), Some(0), Some(i64::MIN)),
218 entry(Some(vec![2]), Some(0), Some(-1)),
219 ];
220 let err = fold_average_entries(&entries)
221 .expect_err("negative i64 sum underflow must surface as RequestError");
222 let msg = format!("{err:?}");
223 assert!(msg.contains("i64 over/underflow"));
224 }
225
226 #[test]
229 fn fold_average_entries_empty_returns_zero_pair() {
230 let avg = fold_average_entries(&[]).expect("empty fold must succeed");
231 assert_eq!(avg, DocumentAverage { count: 0, sum: 0 });
232 }
233}