Skip to content

Commit

Permalink
added registry index for fetched records and updated proof endpoints …
Browse files Browse the repository at this point in the history
…to use the log length and registry index (#187)
  • Loading branch information
calvinrp authored Aug 28, 2023
1 parent d9ef671 commit 99f1b62
Show file tree
Hide file tree
Showing 20 changed files with 542 additions and 372 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/content
/target
*.swp
*.swo
43 changes: 23 additions & 20 deletions crates/api/src/v1/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ use std::{borrow::Cow, collections::HashMap};
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::{
registry::{LogId, RecordId},
ProtoEnvelopeBody,
registry::{LogId, RecordId, RegistryLen},
PublishedProtoEnvelopeBody,
};

/// Represents a fetch logs request.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FetchLogsRequest<'a> {
/// The root checkpoint ID hash of the registry.
pub checkpoint_id: Cow<'a, AnyHash>,
/// The checkpoint log length.
pub log_length: RegistryLen,
/// The limit for the number of operator and package records to fetch.
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u16>,
Expand All @@ -36,19 +36,19 @@ pub struct FetchLogsResponse {
pub more: bool,
/// The operator records appended since the last known operator record.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub operator: Vec<ProtoEnvelopeBody>,
pub operator: Vec<PublishedProtoEnvelopeBody>,
/// The package records appended since last known package record ids.
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub packages: HashMap<LogId, Vec<ProtoEnvelopeBody>>,
pub packages: HashMap<LogId, Vec<PublishedProtoEnvelopeBody>>,
}

/// Represents a fetch API error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum FetchError {
/// The provided checkpoint was not found.
#[error("checkpoint `{0}` was not found")]
CheckpointNotFound(AnyHash),
#[error("checkpoint log length `{0}` was not found")]
CheckpointNotFound(RegistryLen),
/// The provided log was not found.
#[error("log `{0}` was not found")]
LogNotFound(LogId),
Expand Down Expand Up @@ -78,7 +78,7 @@ impl FetchError {
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum EntityType {
Checkpoint,
LogLength,
Log,
Record,
}
Expand All @@ -90,6 +90,12 @@ where
T: Clone + ToOwned,
<T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
{
CheckpointNotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: RegistryLen,
},
NotFound {
status: Status<404>,
#[serde(rename = "type")]
Expand All @@ -105,10 +111,10 @@ where
impl Serialize for FetchError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::CheckpointNotFound(checkpoint) => RawError::NotFound {
Self::CheckpointNotFound(log_length) => RawError::CheckpointNotFound::<RegistryLen> {
status: Status::<404>,
ty: EntityType::Checkpoint,
id: Cow::Borrowed(checkpoint),
ty: EntityType::LogLength,
id: *log_length,
}
.serialize(serializer),
Self::LogNotFound(log_id) => RawError::NotFound {
Expand Down Expand Up @@ -138,15 +144,8 @@ impl<'de> Deserialize<'de> for FetchError {
D: serde::Deserializer<'de>,
{
match RawError::<String>::deserialize(deserializer)? {
RawError::CheckpointNotFound { id, .. } => Ok(Self::CheckpointNotFound(id)),
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::Checkpoint => {
Ok(Self::CheckpointNotFound(id.parse().map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid checkpoint hash",
)
})?))
}
EntityType::Log => Ok(Self::LogNotFound(
id.parse::<AnyHash>()
.map_err(|_| {
Expand All @@ -164,6 +163,10 @@ impl<'de> Deserialize<'de> for FetchError {
})?
.into(),
)),
_ => Err(serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid log length",
)),
},
RawError::Message { status, message } => Ok(Self::Message {
status,
Expand Down
6 changes: 3 additions & 3 deletions crates/api/src/v1/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{borrow::Cow, collections::HashMap};
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::{
registry::{LogId, PackageId, RecordId},
registry::{LogId, PackageId, RecordId, RegistryIndex},
ProtoEnvelopeBody,
};

Expand Down Expand Up @@ -105,10 +105,10 @@ pub enum PackageRecordState {
Published {
/// The envelope of the package record.
record: ProtoEnvelopeBody,
/// The index of the record in the registry log.
registry_log_index: u32,
/// The content sources of the record.
content_sources: HashMap<AnyHash, Vec<ContentSource>>,
/// The published index of the record in the registry log.
registry_index: RegistryIndex,
},
}

Expand Down
101 changes: 30 additions & 71 deletions crates/api/src/v1/proof.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
//! Types relating to the proof API.

use crate::Status;
use serde::{de::Unexpected, Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer};
use serde_with::{base64::Base64, serde_as};
use std::borrow::Cow;
use thiserror::Error;
use warg_crypto::hash::AnyHash;
use warg_protocol::registry::{Checkpoint, LogId, LogLeaf};
use warg_protocol::registry::{LogId, RegistryIndex, RegistryLen};

/// Represents a consistency proof request.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConsistencyRequest {
/// The starting log length to check for consistency.
pub from: u32,
pub from: RegistryLen,
/// The ending log length to check for consistency.
pub to: u32,
pub to: RegistryLen,
}

/// Represents a consistency proof response.
Expand All @@ -31,11 +31,11 @@ pub struct ConsistencyResponse {
/// Represents an inclusion proof request.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InclusionRequest<'a> {
/// The checkpoint to check for inclusion.
pub checkpoint: Cow<'a, Checkpoint>,
/// The log leafs to check for inclusion.
pub leafs: Cow<'a, [LogLeaf]>,
pub struct InclusionRequest {
/// The log length to check for inclusion.
pub log_length: RegistryLen,
/// The log leaf indexes in the registry log to check for inclusion.
pub leafs: Vec<RegistryIndex>,
}

/// Represents an inclusion proof response.
Expand All @@ -55,12 +55,12 @@ pub struct InclusionResponse {
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ProofError {
/// The provided log root was not found.
#[error("log root `{0}` was not found")]
RootNotFound(AnyHash),
/// The checkpoint could not be found for the provided log length.
#[error("checkpoint not found for log length {0}")]
CheckpointNotFound(RegistryLen),
/// The provided log leaf was not found.
#[error("log leaf `{}:{}` was not found", .0.log_id, .0.record_id)]
LeafNotFound(LogLeaf),
#[error("log leaf `{0}` not found")]
LeafNotFound(RegistryIndex),
/// Failed to prove inclusion of a package.
#[error("failed to prove inclusion of package log `{0}`")]
PackageLogNotIncluded(LogId),
Expand Down Expand Up @@ -89,7 +89,7 @@ impl ProofError {
/// Returns the HTTP status code of the error.
pub fn status(&self) -> u16 {
match self {
Self::RootNotFound(_) | Self::LeafNotFound(_) => 404,
Self::CheckpointNotFound(_) | Self::LeafNotFound(_) => 404,
Self::BundleFailure(_)
| Self::PackageLogNotIncluded(_)
| Self::IncorrectProof { .. } => 422,
Expand All @@ -101,7 +101,7 @@ impl ProofError {
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum EntityType {
LogRoot,
LogLength,
Leaf,
}

Expand All @@ -122,16 +122,12 @@ enum BundleError<'a> {

#[derive(Serialize, Deserialize)]
#[serde(untagged, rename_all = "camelCase")]
enum RawError<'a, T>
where
T: Clone + ToOwned,
<T as ToOwned>::Owned: Serialize + for<'b> Deserialize<'b>,
{
enum RawError<'a> {
NotFound {
status: Status<404>,
#[serde(rename = "type")]
ty: EntityType,
id: Cow<'a, T>,
id: RegistryIndex,
},
BundleError {
status: Status<422>,
Expand All @@ -147,41 +143,41 @@ where
impl Serialize for ProofError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::RootNotFound(root) => RawError::NotFound {
Self::CheckpointNotFound(log_length) => RawError::NotFound {
status: Status::<404>,
ty: EntityType::LogRoot,
id: Cow::Borrowed(root),
ty: EntityType::LogLength,
id: *log_length,
}
.serialize(serializer),
Self::LeafNotFound(leaf) => RawError::NotFound::<String> {
Self::LeafNotFound(leaf_index) => RawError::NotFound {
status: Status::<404>,
ty: EntityType::Leaf,
id: Cow::Owned(format!("{}|{}", leaf.log_id, leaf.record_id)),
id: *leaf_index,
}
.serialize(serializer),
Self::PackageLogNotIncluded(log_id) => RawError::BundleError::<()> {
Self::PackageLogNotIncluded(log_id) => RawError::BundleError {
status: Status::<422>,
error: BundleError::PackageNotIncluded {
log_id: Cow::Borrowed(log_id),
},
}
.serialize(serializer),
Self::IncorrectProof { root, found } => RawError::BundleError::<()> {
Self::IncorrectProof { root, found } => RawError::BundleError {
status: Status::<422>,
error: BundleError::IncorrectProof {
root: Cow::Borrowed(root),
found: Cow::Borrowed(found),
},
}
.serialize(serializer),
Self::BundleFailure(message) => RawError::BundleError::<()> {
Self::BundleFailure(message) => RawError::BundleError {
status: Status::<422>,
error: BundleError::Failure {
message: Cow::Borrowed(message),
},
}
.serialize(serializer),
Self::Message { status, message } => RawError::Message::<()> {
Self::Message { status, message } => RawError::Message {
status: *status,
message: Cow::Borrowed(message),
}
Expand All @@ -195,47 +191,10 @@ impl<'de> Deserialize<'de> for ProofError {
where
D: serde::Deserializer<'de>,
{
match RawError::<String>::deserialize(deserializer)? {
match RawError::deserialize(deserializer)? {
RawError::NotFound { status: _, ty, id } => match ty {
EntityType::LogRoot => {
Ok(Self::RootNotFound(id.parse::<AnyHash>().map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid checkpoint id",
)
})?))
}
EntityType::Leaf => Ok(Self::LeafNotFound(
id.split_once('|')
.map(|(log_id, record_id)| {
Ok(LogLeaf {
log_id: log_id
.parse::<AnyHash>()
.map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(log_id),
&"a valid log id",
)
})?
.into(),
record_id: record_id
.parse::<AnyHash>()
.map_err(|_| {
serde::de::Error::invalid_value(
Unexpected::Str(record_id),
&"a valid record id",
)
})?
.into(),
})
})
.ok_or_else(|| {
serde::de::Error::invalid_value(
Unexpected::Str(&id),
&"a valid leaf id",
)
})??,
)),
EntityType::LogLength => Ok(Self::CheckpointNotFound(id)),
EntityType::Leaf => Ok(Self::LeafNotFound(id)),
},
RawError::BundleError { status: _, error } => match error {
BundleError::PackageNotIncluded { log_id } => {
Expand Down
13 changes: 7 additions & 6 deletions crates/client/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,12 @@ impl Client {
}

/// Proves the inclusion of the given package log heads in the registry.
pub async fn prove_inclusion(&self, request: InclusionRequest<'_>) -> Result<(), ClientError> {
pub async fn prove_inclusion(
&self,
request: InclusionRequest,
checkpoint: &Checkpoint,
leafs: &[LogLeaf],
) -> Result<(), ClientError> {
let url = self.url.join(paths::prove_inclusion());
tracing::debug!("proving checkpoint inclusion at `{url}`");

Expand All @@ -246,11 +251,7 @@ impl Client {
)
.await?;

Self::validate_inclusion_response(
response,
request.checkpoint.as_ref(),
request.leafs.as_ref(),
)
Self::validate_inclusion_response(response, checkpoint, leafs)
}

/// Proves consistency between two log roots.
Expand Down
Loading

0 comments on commit 99f1b62

Please sign in to comment.