1use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
2use dpp::ProtocolError;
3use drive::error::proof::ProofError;
4use drive::grovedb::Error as GroveError;
5use drive::query::PathQuery;
6
7#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11 #[error("not initialized: call initialize() first")]
13 NotInitialized,
14
15 #[error("already initialized: initialize() can be called only once")]
16 AlreadyInitialized,
17
18 #[error("dash drive: {error}")]
20 DriveError { error: String },
21
22 #[error("grovedb: {error}")]
24 GroveDBError {
25 proof_bytes: Vec<u8>,
26 path_query: Option<PathQuery>,
27 height: u64,
28 time_ms: u64,
29 error: String,
30 },
31
32 #[error("dash protocol: {error}")]
34 ProtocolError { error: String },
35
36 #[error("empty response metadata")]
38 EmptyResponseMetadata,
39
40 #[error("empty version")]
42 EmptyVersion,
43
44 #[error("no proof in result")]
46 NoProofInResult,
47
48 #[error(
52 "unsupported GroveDB proof envelope version {version} in the {proof}: at least version {minimum} is required"
53 )]
54 UnsupportedGroveDBProofVersion {
55 proof: &'static str,
57 version: u32,
58 minimum: u32,
59 },
60
61 #[error("requested object not found")]
63 NotFound,
64
65 #[error("decode request: {error}")]
67 RequestError { error: String },
68
69 #[error("decode response: {error}")]
71 ResponseDecodeError { error: String },
72
73 #[error("result encoding: {error}")]
75 ResultEncodingError { error: String },
76
77 #[error("cannot generate signature digest for data: {error}")]
79 SignDigestFailed { error: String },
80
81 #[error("error during signature verification: {error}")]
83 SignatureVerificationError { error: String },
84
85 #[error("invalid signature format: {error}")]
87 InvalidSignatureFormat { error: String },
88
89 #[error("invalid public key: {error}")]
91 InvalidPublicKey { error: String },
92
93 #[error("invalid signature: {error}")]
95 InvalidSignature { error: String },
96
97 #[error("unexpected callback error: {error}, reason: {reason}")]
99 UnexpectedCallbackError { error: String, reason: String },
100
101 #[error("invalid version of message")]
103 InvalidVersion(#[from] dpp::version::PlatformVersionError),
104
105 #[error("context provider is not set")]
107 ContextProviderNotSet,
108
109 #[error("context provider error: {0}")]
111 ContextProviderError(#[from] dash_context_provider::ContextProviderError),
112}
113
114impl From<drive::error::Error> for Error {
115 fn from(error: drive::error::Error) -> Self {
116 match error {
117 drive::error::Error::Proof(ProofError::UnsupportedGroveDBProofEnvelopeVersion {
118 proof,
119 version,
120 minimum,
121 }) => Self::UnsupportedGroveDBProofVersion {
122 proof,
123 version,
124 minimum,
125 },
126 error => Self::DriveError {
127 error: error.to_string(),
128 },
129 }
130 }
131}
132
133impl From<ProtocolError> for Error {
134 fn from(error: ProtocolError) -> Self {
135 Self::ProtocolError {
136 error: error.to_string(),
137 }
138 }
139}
140
141pub(crate) trait MapGroveDbError<O> {
142 fn map_drive_error(self, proof: &Proof, metadata: &ResponseMetadata) -> Result<O, Error>;
143}
144
145impl<O> MapGroveDbError<O> for Result<O, drive::error::Error> {
146 fn map_drive_error(self, proof: &Proof, metadata: &ResponseMetadata) -> Result<O, Error> {
147 match self {
148 Ok(o) => Ok(o),
149
150 Err(drive::error::Error::GroveDB(grove_err)) => {
151 let maybe_query = match grove_err.as_ref() {
153 GroveError::InvalidProof(path_query, ..) => Some(path_query.clone()),
154 _ => None,
155 };
156
157 Err(Error::GroveDBError {
158 proof_bytes: proof.grovedb_proof.clone(),
159 path_query: maybe_query,
160 height: metadata.height,
161 time_ms: metadata.time_ms,
162 error: grove_err.to_string(),
163 })
164 }
165
166 Err(other) => Err(other.into()),
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use drive::grovedb::{Error as GroveError, Query, SizedQuery};
175
176 fn test_proof(grovedb_proof: Vec<u8>) -> Proof {
178 Proof {
179 grovedb_proof,
180 quorum_hash: vec![0u8; 32],
181 signature: vec![0u8; 96],
182 round: 1,
183 block_id_hash: vec![0u8; 32],
184 quorum_type: 1,
185 }
186 }
187
188 fn test_metadata(height: u64, time_ms: u64) -> ResponseMetadata {
190 ResponseMetadata {
191 height,
192 core_chain_locked_height: 1,
193 epoch: 0,
194 time_ms,
195 protocol_version: 1,
196 chain_id: "test-chain".to_string(),
197 }
198 }
199
200 fn test_path_query() -> PathQuery {
202 let query = Query::new();
203 let sized = SizedQuery::new(query, Some(10), None);
204 PathQuery::new(vec![vec![1, 2, 3]], sized)
205 }
206
207 #[test]
208 fn test_map_drive_error_ok_passes_through() {
209 let result: Result<u64, drive::error::Error> = Ok(42u64);
210 let proof = test_proof(vec![0xAA, 0xBB]);
211 let metadata = test_metadata(100, 5000);
212
213 let mapped = result.map_drive_error(&proof, &metadata);
214 assert_eq!(mapped.unwrap(), 42u64);
215 }
216
217 #[test]
218 fn test_map_drive_error_grovedb_invalid_proof() {
219 let pq = test_path_query();
220 let grove_err = GroveError::InvalidProof(pq.clone(), "bad proof data".to_string());
221 let drive_err = drive::error::Error::GroveDB(Box::new(grove_err));
222 let result: Result<u64, drive::error::Error> = Err(drive_err);
223
224 let proof_bytes = vec![0xDE, 0xAD];
225 let proof = test_proof(proof_bytes.clone());
226 let metadata = test_metadata(42, 9999);
227
228 let mapped = result.map_drive_error(&proof, &metadata);
229 let err = mapped.unwrap_err();
230
231 match err {
232 Error::GroveDBError {
233 proof_bytes: pb,
234 path_query: maybe_pq,
235 height,
236 time_ms,
237 error,
238 } => {
239 assert_eq!(pb, proof_bytes, "proof_bytes should match the input proof");
240 assert!(
241 maybe_pq.is_some(),
242 "path_query should be Some for InvalidProof"
243 );
244 assert_eq!(
245 maybe_pq.unwrap(),
246 pq,
247 "path_query should match the original"
248 );
249 assert_eq!(height, 42);
250 assert_eq!(time_ms, 9999);
251 assert!(
252 error.contains("bad proof data"),
253 "error message should contain the original message, got: {error}"
254 );
255 }
256 other => panic!("expected GroveDBError, got: {other:?}"),
257 }
258 }
259
260 #[test]
261 fn test_map_drive_error_grovedb_other() {
262 let grove_err = GroveError::InternalError("something broke".to_string());
263 let drive_err = drive::error::Error::GroveDB(Box::new(grove_err));
264 let result: Result<u64, drive::error::Error> = Err(drive_err);
265
266 let proof = test_proof(vec![0x01]);
267 let metadata = test_metadata(10, 2000);
268
269 let mapped = result.map_drive_error(&proof, &metadata);
270 let err = mapped.unwrap_err();
271
272 match err {
273 Error::GroveDBError {
274 path_query, error, ..
275 } => {
276 assert!(
277 path_query.is_none(),
278 "path_query should be None for non-InvalidProof GroveDB errors"
279 );
280 assert!(
281 error.contains("something broke"),
282 "error message should be preserved, got: {error}"
283 );
284 }
285 other => panic!("expected GroveDBError, got: {other:?}"),
286 }
287 }
288
289 #[test]
290 fn test_map_drive_error_non_grovedb() {
291 let drive_err = drive::error::Error::Drive(
292 drive::error::drive::DriveError::CorruptedCodeExecution("test drive error"),
293 );
294 let result: Result<u64, drive::error::Error> = Err(drive_err);
295
296 let proof = test_proof(vec![]);
297 let metadata = test_metadata(1, 1);
298
299 let mapped = result.map_drive_error(&proof, &metadata);
300 let err = mapped.unwrap_err();
301
302 match err {
303 Error::DriveError { error } => {
304 assert!(
305 error.contains("test drive error"),
306 "error message should contain the original text, got: {error}"
307 );
308 }
309 other => panic!("expected DriveError, got: {other:?}"),
310 }
311 }
312
313 #[test]
314 fn test_from_protocol_error() {
315 let protocol_err = ProtocolError::IdentifierError("bad id".to_string());
316 let err: Error = protocol_err.into();
317
318 match err {
319 Error::ProtocolError { error } => {
320 assert!(
321 error.contains("bad id"),
322 "error message should contain the original text, got: {error}"
323 );
324 }
325 other => panic!("expected ProtocolError, got: {other:?}"),
326 }
327 }
328
329 #[test]
330 fn test_from_drive_error() {
331 let drive_err = drive::error::Error::Drive(
332 drive::error::drive::DriveError::CorruptedCodeExecution("from conversion test"),
333 );
334 let err: Error = drive_err.into();
335
336 match err {
337 Error::DriveError { error } => {
338 assert!(
339 error.contains("from conversion test"),
340 "error message should contain the original text, got: {error}"
341 );
342 }
343 other => panic!("expected DriveError, got: {other:?}"),
344 }
345 }
346}