warg_api/v1/
proof.rs

1//! Types relating to the proof API.
2
3use crate::Status;
4use serde::{Deserialize, Serialize, Serializer};
5use serde_with::{base64::Base64, serde_as};
6use std::borrow::Cow;
7use thiserror::Error;
8use warg_crypto::hash::AnyHash;
9use warg_protocol::registry::{LogId, RegistryIndex, RegistryLen};
10
11/// Represents a consistency proof request.
12#[derive(Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ConsistencyRequest {
15    /// The starting log length to check for consistency.
16    pub from: RegistryLen,
17    /// The ending log length to check for consistency.
18    pub to: RegistryLen,
19}
20
21/// Represents a consistency proof response.
22#[serde_as]
23#[derive(Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct ConsistencyResponse {
26    /// The bytes of the consistency proof bundle.
27    #[serde_as(as = "Base64")]
28    pub proof: Vec<u8>,
29}
30
31/// Represents an inclusion proof request.
32#[derive(Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct InclusionRequest {
35    /// The log length to check for inclusion.
36    pub log_length: RegistryLen,
37    /// The log leaf indexes in the registry log to check for inclusion.
38    pub leafs: Vec<RegistryIndex>,
39}
40
41/// Represents an inclusion proof response.
42#[serde_as]
43#[derive(Serialize, Deserialize, Debug)]
44#[serde(rename_all = "camelCase")]
45pub struct InclusionResponse {
46    /// The bytes of the log log proof bundle.
47    #[serde_as(as = "Base64")]
48    pub log: Vec<u8>,
49    /// The bytes of the map inclusion proof bundle.
50    #[serde_as(as = "Base64")]
51    pub map: Vec<u8>,
52}
53
54/// Represents a proof API error.
55#[non_exhaustive]
56#[derive(Debug, Error)]
57pub enum ProofError {
58    /// The checkpoint could not be found for the provided log length.
59    #[error("checkpoint not found for log length {0}")]
60    CheckpointNotFound(RegistryLen),
61    /// The provided log leaf was not found.
62    #[error("log leaf `{0}` not found")]
63    LeafNotFound(RegistryIndex),
64    /// Failed to prove inclusion of a package.
65    #[error("failed to prove inclusion of package log `{0}`")]
66    PackageLogNotIncluded(LogId),
67    /// The provided root for an inclusion proof was incorrect.
68    #[error("failed to prove inclusion: found root `{found}` but was given root `{root}`")]
69    IncorrectProof {
70        /// The provided root.
71        root: AnyHash,
72        /// The found root.
73        found: AnyHash,
74    },
75    /// A failure was encountered while bundling proofs.
76    #[error("failed to bundle proofs: {0}")]
77    BundleFailure(String),
78    /// An error with a message occurred.
79    #[error("{message}")]
80    Message {
81        /// The HTTP status code.
82        status: u16,
83        /// The error message
84        message: String,
85    },
86}
87
88impl ProofError {
89    /// Returns the HTTP status code of the error.
90    pub fn status(&self) -> u16 {
91        match self {
92            Self::CheckpointNotFound(_) | Self::LeafNotFound(_) => 404,
93            Self::BundleFailure(_)
94            | Self::PackageLogNotIncluded(_)
95            | Self::IncorrectProof { .. } => 422,
96            Self::Message { status, .. } => *status,
97        }
98    }
99}
100
101#[derive(Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103enum EntityType {
104    LogLength,
105    Leaf,
106}
107
108#[derive(Serialize, Deserialize)]
109#[serde(tag = "reason", rename_all = "camelCase")]
110enum BundleError<'a> {
111    PackageNotIncluded {
112        log_id: Cow<'a, LogId>,
113    },
114    IncorrectProof {
115        root: Cow<'a, AnyHash>,
116        found: Cow<'a, AnyHash>,
117    },
118    Failure {
119        message: Cow<'a, str>,
120    },
121}
122
123#[derive(Serialize, Deserialize)]
124#[serde(untagged, rename_all = "camelCase")]
125enum RawError<'a> {
126    NotFound {
127        status: Status<404>,
128        #[serde(rename = "type")]
129        ty: EntityType,
130        id: RegistryIndex,
131    },
132    BundleError {
133        status: Status<422>,
134        #[serde(flatten)]
135        error: BundleError<'a>,
136    },
137    Message {
138        status: u16,
139        message: Cow<'a, str>,
140    },
141}
142
143impl Serialize for ProofError {
144    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
145        match self {
146            Self::CheckpointNotFound(log_length) => RawError::NotFound {
147                status: Status::<404>,
148                ty: EntityType::LogLength,
149                id: *log_length,
150            }
151            .serialize(serializer),
152            Self::LeafNotFound(leaf_index) => RawError::NotFound {
153                status: Status::<404>,
154                ty: EntityType::Leaf,
155                id: *leaf_index,
156            }
157            .serialize(serializer),
158            Self::PackageLogNotIncluded(log_id) => RawError::BundleError {
159                status: Status::<422>,
160                error: BundleError::PackageNotIncluded {
161                    log_id: Cow::Borrowed(log_id),
162                },
163            }
164            .serialize(serializer),
165            Self::IncorrectProof { root, found } => RawError::BundleError {
166                status: Status::<422>,
167                error: BundleError::IncorrectProof {
168                    root: Cow::Borrowed(root),
169                    found: Cow::Borrowed(found),
170                },
171            }
172            .serialize(serializer),
173            Self::BundleFailure(message) => RawError::BundleError {
174                status: Status::<422>,
175                error: BundleError::Failure {
176                    message: Cow::Borrowed(message),
177                },
178            }
179            .serialize(serializer),
180            Self::Message { status, message } => RawError::Message {
181                status: *status,
182                message: Cow::Borrowed(message),
183            }
184            .serialize(serializer),
185        }
186    }
187}
188
189impl<'de> Deserialize<'de> for ProofError {
190    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191    where
192        D: serde::Deserializer<'de>,
193    {
194        match RawError::deserialize(deserializer)? {
195            RawError::NotFound { status: _, ty, id } => match ty {
196                EntityType::LogLength => Ok(Self::CheckpointNotFound(id)),
197                EntityType::Leaf => Ok(Self::LeafNotFound(id)),
198            },
199            RawError::BundleError { status: _, error } => match error {
200                BundleError::PackageNotIncluded { log_id } => {
201                    Ok(Self::PackageLogNotIncluded(log_id.into_owned()))
202                }
203                BundleError::IncorrectProof { root, found } => Ok(Self::IncorrectProof {
204                    root: root.into_owned(),
205                    found: found.into_owned(),
206                }),
207                BundleError::Failure { message } => Ok(Self::BundleFailure(message.into_owned())),
208            },
209            RawError::Message { status, message } => Ok(Self::Message {
210                status,
211                message: message.into_owned(),
212            }),
213        }
214    }
215}