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