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    /// The proof and its signature verified, but the verified result cannot be returned as
78    /// the requested type: for example several documents for a single-document fetch, or a
79    /// total that does not fit its integer type.
80    ///
81    /// Every honest node proves the same result, so asking another node cannot help; the
82    /// request has to change. Raise it only after the proof and signature verified, never
83    /// for a result computed from parts of the response that are not verified yet.
84    #[error("unusable verified result: {reason}")]
85    UnusableVerifiedResult { reason: String },
86
87    /// Cannot generate signature digest for data
88    #[error("cannot generate signature digest for data: {error}")]
89    SignDigestFailed { error: String },
90
91    /// Error during signature verification
92    #[error("error during signature verification: {error}")]
93    SignatureVerificationError { error: String },
94
95    /// Signature format is invalid
96    #[error("invalid signature format: {error}")]
97    InvalidSignatureFormat { error: String },
98
99    /// Public key is invalid
100    #[error("invalid public key: {error}")]
101    InvalidPublicKey { error: String },
102
103    /// Invalid signature
104    #[error("invalid signature: {error}")]
105    InvalidSignature { error: String },
106
107    /// Callback error
108    #[error("unexpected callback error: {error}, reason: {reason}")]
109    UnexpectedCallbackError { error: String, reason: String },
110
111    /// A document query answer names a protocol version other than the one
112    /// it is verified with. Document queries are rebuilt with the verifier's
113    /// protocol version, so the answer is not verified.
114    #[error(
115        "response metadata names protocol version {signed}, but this query is verified with protocol version {verifier}"
116    )]
117    ProofProtocolVersionMismatch {
118        /// Protocol version in the response metadata
119        signed: u32,
120        /// Protocol version of the verifier's rules
121        verifier: u32,
122    },
123
124    /// Invalid version of object in response
125    #[error("invalid version of message")]
126    InvalidVersion(#[from] dpp::version::PlatformVersionError),
127
128    /// Context provider is not set
129    #[error("context provider is not set")]
130    ContextProviderNotSet,
131
132    /// Context provider error
133    #[error("context provider error: {0}")]
134    ContextProviderError(#[from] dash_context_provider::ContextProviderError),
135
136    /// The context provider could not supply the public key of the quorum
137    /// that signed the proof. The quorum fields come from the response, which
138    /// is not verified yet; they say which key to look for, not that the
139    /// quorum exists.
140    #[error(
141        "quorum key unavailable for quorum {quorum_type}:{}: {error}",
142        hex::encode(.quorum_hash)
143    )]
144    QuorumKeyUnavailable {
145        /// Quorum type named by the proof
146        quorum_type: u32,
147        /// Quorum hash named by the proof
148        quorum_hash: [u8; 32],
149        /// Core chain locked height from the response metadata
150        core_chain_locked_height: u32,
151        /// Why the provider could not supply the key
152        #[source]
153        error: dash_context_provider::ContextProviderError,
154    },
155}
156
157impl From<drive::error::Error> for Error {
158    fn from(error: drive::error::Error) -> Self {
159        match error {
160            drive::error::Error::Proof(ProofError::UnsupportedGroveDBProofEnvelopeVersion {
161                proof,
162                version,
163                minimum,
164            }) => Self::UnsupportedGroveDBProofVersion {
165                proof,
166                version,
167                minimum,
168            },
169            error => Self::DriveError {
170                error: error.to_string(),
171            },
172        }
173    }
174}
175
176impl From<ProtocolError> for Error {
177    fn from(error: ProtocolError) -> Self {
178        Self::ProtocolError {
179            error: error.to_string(),
180        }
181    }
182}
183
184pub(crate) trait MapGroveDbError<O> {
185    fn map_drive_error(self, proof: &Proof, metadata: &ResponseMetadata) -> Result<O, Error>;
186}
187
188impl<O> MapGroveDbError<O> for Result<O, drive::error::Error> {
189    fn map_drive_error(self, proof: &Proof, metadata: &ResponseMetadata) -> Result<O, Error> {
190        match self {
191            Ok(o) => Ok(o),
192
193            Err(drive::error::Error::GroveDB(grove_err)) => {
194                // If InvalidProof error is returned, extract the path query from it
195                let maybe_query = match grove_err.as_ref() {
196                    GroveError::InvalidProof(path_query, ..) => Some(path_query.clone()),
197                    _ => None,
198                };
199
200                Err(Error::GroveDBError {
201                    proof_bytes: proof.grovedb_proof.clone(),
202                    path_query: maybe_query,
203                    height: metadata.height,
204                    time_ms: metadata.time_ms,
205                    error: grove_err.to_string(),
206                })
207            }
208
209            Err(other) => Err(other.into()),
210        }
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use drive::grovedb::{Error as GroveError, Query, SizedQuery};
218
219    /// Helper: build a minimal `Proof` with the given grovedb_proof bytes.
220    fn test_proof(grovedb_proof: Vec<u8>) -> Proof {
221        Proof {
222            grovedb_proof,
223            quorum_hash: vec![0u8; 32],
224            signature: vec![0u8; 96],
225            round: 1,
226            block_id_hash: vec![0u8; 32],
227            quorum_type: 1,
228        }
229    }
230
231    /// Helper: build a minimal `ResponseMetadata`.
232    fn test_metadata(height: u64, time_ms: u64) -> ResponseMetadata {
233        ResponseMetadata {
234            height,
235            core_chain_locked_height: 1,
236            epoch: 0,
237            time_ms,
238            protocol_version: 1,
239            chain_id: "test-chain".to_string(),
240        }
241    }
242
243    /// Helper: build a simple `PathQuery`.
244    fn test_path_query() -> PathQuery {
245        let query = Query::new();
246        let sized = SizedQuery::new(query, Some(10), None);
247        PathQuery::new(vec![vec![1, 2, 3]], sized)
248    }
249
250    #[test]
251    fn test_map_drive_error_ok_passes_through() {
252        let result: Result<u64, drive::error::Error> = Ok(42u64);
253        let proof = test_proof(vec![0xAA, 0xBB]);
254        let metadata = test_metadata(100, 5000);
255
256        let mapped = result.map_drive_error(&proof, &metadata);
257        assert_eq!(mapped.unwrap(), 42u64);
258    }
259
260    #[test]
261    fn test_map_drive_error_grovedb_invalid_proof() {
262        let pq = test_path_query();
263        let grove_err = GroveError::InvalidProof(pq.clone(), "bad proof data".to_string());
264        let drive_err = drive::error::Error::GroveDB(Box::new(grove_err));
265        let result: Result<u64, drive::error::Error> = Err(drive_err);
266
267        let proof_bytes = vec![0xDE, 0xAD];
268        let proof = test_proof(proof_bytes.clone());
269        let metadata = test_metadata(42, 9999);
270
271        let mapped = result.map_drive_error(&proof, &metadata);
272        let err = mapped.unwrap_err();
273
274        match err {
275            Error::GroveDBError {
276                proof_bytes: pb,
277                path_query: maybe_pq,
278                height,
279                time_ms,
280                error,
281            } => {
282                assert_eq!(pb, proof_bytes, "proof_bytes should match the input proof");
283                assert!(
284                    maybe_pq.is_some(),
285                    "path_query should be Some for InvalidProof"
286                );
287                assert_eq!(
288                    maybe_pq.unwrap(),
289                    pq,
290                    "path_query should match the original"
291                );
292                assert_eq!(height, 42);
293                assert_eq!(time_ms, 9999);
294                assert!(
295                    error.contains("bad proof data"),
296                    "error message should contain the original message, got: {error}"
297                );
298            }
299            other => panic!("expected GroveDBError, got: {other:?}"),
300        }
301    }
302
303    #[test]
304    fn test_map_drive_error_grovedb_other() {
305        let grove_err = GroveError::InternalError("something broke".to_string());
306        let drive_err = drive::error::Error::GroveDB(Box::new(grove_err));
307        let result: Result<u64, drive::error::Error> = Err(drive_err);
308
309        let proof = test_proof(vec![0x01]);
310        let metadata = test_metadata(10, 2000);
311
312        let mapped = result.map_drive_error(&proof, &metadata);
313        let err = mapped.unwrap_err();
314
315        match err {
316            Error::GroveDBError {
317                path_query, error, ..
318            } => {
319                assert!(
320                    path_query.is_none(),
321                    "path_query should be None for non-InvalidProof GroveDB errors"
322                );
323                assert!(
324                    error.contains("something broke"),
325                    "error message should be preserved, got: {error}"
326                );
327            }
328            other => panic!("expected GroveDBError, got: {other:?}"),
329        }
330    }
331
332    #[test]
333    fn test_map_drive_error_non_grovedb() {
334        let drive_err = drive::error::Error::Drive(
335            drive::error::drive::DriveError::CorruptedCodeExecution("test drive error"),
336        );
337        let result: Result<u64, drive::error::Error> = Err(drive_err);
338
339        let proof = test_proof(vec![]);
340        let metadata = test_metadata(1, 1);
341
342        let mapped = result.map_drive_error(&proof, &metadata);
343        let err = mapped.unwrap_err();
344
345        match err {
346            Error::DriveError { error } => {
347                assert!(
348                    error.contains("test drive error"),
349                    "error message should contain the original text, got: {error}"
350                );
351            }
352            other => panic!("expected DriveError, got: {other:?}"),
353        }
354    }
355
356    #[test]
357    fn test_from_protocol_error() {
358        let protocol_err = ProtocolError::IdentifierError("bad id".to_string());
359        let err: Error = protocol_err.into();
360
361        match err {
362            Error::ProtocolError { error } => {
363                assert!(
364                    error.contains("bad id"),
365                    "error message should contain the original text, got: {error}"
366                );
367            }
368            other => panic!("expected ProtocolError, got: {other:?}"),
369        }
370    }
371
372    #[test]
373    fn test_from_drive_error() {
374        let drive_err = drive::error::Error::Drive(
375            drive::error::drive::DriveError::CorruptedCodeExecution("from conversion test"),
376        );
377        let err: Error = drive_err.into();
378
379        match err {
380            Error::DriveError { error } => {
381                assert!(
382                    error.contains("from conversion test"),
383                    "error message should contain the original text, got: {error}"
384                );
385            }
386            other => panic!("expected DriveError, got: {other:?}"),
387        }
388    }
389}