Skip to main content

drive_proof_verifier/
error.rs

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/// Errors
8#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11    /// Not initialized
12    #[error("not initialized: call initialize() first")]
13    NotInitialized,
14
15    #[error("already initialized: initialize() can be called only once")]
16    AlreadyInitialized,
17
18    /// Drive error
19    #[error("dash drive: {error}")]
20    DriveError { error: String },
21
22    /// GroveDB error, often for issues with proofs
23    #[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    /// Dash Protocol error
33    #[error("dash protocol: {error}")]
34    ProtocolError { error: String },
35
36    /// Empty response metadata
37    #[error("empty response metadata")]
38    EmptyResponseMetadata,
39
40    /// Empty version
41    #[error("empty version")]
42    EmptyVersion,
43
44    /// No proof in response
45    #[error("no proof in result")]
46    NoProofInResult,
47
48    /// GroveDB proof envelope older than Drive's
49    /// `MINIMUM_GROVEDB_PROOF_ENVELOPE_VERSION`; the payload is refused before
50    /// Drive verifies it.
51    #[error(
52        "unsupported GroveDB proof envelope version {version} in the {proof}: at least version {minimum} is required"
53    )]
54    UnsupportedGroveDBProofVersion {
55        /// Which proof was being read: "proof", "predecessor proof", "forward proof"
56        proof: &'static str,
57        version: u32,
58        minimum: u32,
59    },
60
61    /// Requested object not found
62    #[error("requested object not found")]
63    NotFound,
64
65    /// Decode protobuf error
66    #[error("decode request: {error}")]
67    RequestError { error: String },
68
69    /// Decode protobuf response error
70    #[error("decode response: {error}")]
71    ResponseDecodeError { error: String },
72
73    /// Error when preparing result
74    #[error("result encoding: {error}")]
75    ResultEncodingError { error: String },
76
77    /// Cannot generate signature digest for data
78    #[error("cannot generate signature digest for data: {error}")]
79    SignDigestFailed { error: String },
80
81    /// Error during signature verification
82    #[error("error during signature verification: {error}")]
83    SignatureVerificationError { error: String },
84
85    /// Signature format is invalid
86    #[error("invalid signature format: {error}")]
87    InvalidSignatureFormat { error: String },
88
89    /// Public key is invalid
90    #[error("invalid public key: {error}")]
91    InvalidPublicKey { error: String },
92
93    /// Invalid signature
94    #[error("invalid signature: {error}")]
95    InvalidSignature { error: String },
96
97    /// Callback error
98    #[error("unexpected callback error: {error}, reason: {reason}")]
99    UnexpectedCallbackError { error: String, reason: String },
100
101    /// Invalid version of object in response
102    #[error("invalid version of message")]
103    InvalidVersion(#[from] dpp::version::PlatformVersionError),
104
105    /// Context provider is not set
106    #[error("context provider is not set")]
107    ContextProviderNotSet,
108
109    /// Context provider error
110    #[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                // If InvalidProof error is returned, extract the path query from it
152                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    /// Helper: build a minimal `Proof` with the given grovedb_proof bytes.
177    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    /// Helper: build a minimal `ResponseMetadata`.
189    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    /// Helper: build a simple `PathQuery`.
201    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}