drive_proof_verifier/
error.rs

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