use std::{io, time::Duration};
use hyphae_core::VectorValueError;
use hyphae_retrieval::{
ExactRetrievalError, ExactRetrievalOutcome, ExactRetrievalRequest, HybridError, HybridOutcome,
HybridRequest, LexicalError, LexicalOutcome, LexicalRequest,
};
use hyphae_storage::{SnapshotError, SnapshotInfo, SnapshotReadLimits};
use thiserror::Error;
use crate::DocumentError;
pub const RETRIEVAL_PROOF_FORMAT_VERSION: u16 = 1;
pub const EXACT_RETRIEVAL_SEMANTICS_VERSION: u16 = 2;
pub const LEXICAL_RETRIEVAL_SEMANTICS_VERSION: u16 = 1;
pub const HYBRID_RETRIEVAL_SEMANTICS_VERSION: u16 = 1;
pub const MAX_RETRIEVAL_PROOF_BYTES: u64 = 64 * 1024 * 1024;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetrievalProofAnchor {
pub checkpoint_sequence: u64,
pub checkpoint_digest: Option<[u8; 32]>,
pub snapshot_digest: [u8; 32],
}
impl RetrievalProofAnchor {
pub fn from_snapshot(snapshot: &SnapshotInfo) -> Self {
Self {
checkpoint_sequence: snapshot.checkpoint_sequence,
checkpoint_digest: snapshot.checkpoint_digest,
snapshot_digest: snapshot.snapshot_digest,
}
}
pub fn digest(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"hyphae-retrieval-anchor-v1");
hasher.update(&self.checkpoint_sequence.to_le_bytes());
hasher.update(&self.checkpoint_digest.unwrap_or([0; 32]));
hasher.update(&self.snapshot_digest);
*hasher.finalize().as_bytes()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExactRetrievalProof {
pub(crate) anchor: RetrievalProofAnchor,
pub(crate) semantics_version: u16,
pub(crate) request: ExactRetrievalRequest,
pub(crate) outcome: ExactRetrievalOutcome,
pub(crate) proof_digest: [u8; 32],
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExactRetrievalProofArtifact {
pub proof: ExactRetrievalProof,
pub snapshot: SnapshotInfo,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LexicalRetrievalProof {
pub(crate) anchor: RetrievalProofAnchor,
pub(crate) semantics_version: u16,
pub(crate) request: LexicalRequest,
pub(crate) outcome: LexicalOutcome,
pub(crate) proof_digest: [u8; 32],
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LexicalRetrievalProofArtifact {
pub proof: LexicalRetrievalProof,
pub snapshot: SnapshotInfo,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HybridRetrievalProof {
pub(crate) anchor: RetrievalProofAnchor,
pub(crate) semantics_version: u16,
pub(crate) lexical_request: LexicalRequest,
pub(crate) lexical_outcome: LexicalOutcome,
pub(crate) vector_request: ExactRetrievalRequest,
pub(crate) vector_outcome: ExactRetrievalOutcome,
pub(crate) fusion_request: HybridRequest,
pub(crate) outcome: HybridOutcome,
pub(crate) proof_digest: [u8; 32],
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HybridRetrievalProofArtifact {
pub proof: HybridRetrievalProof,
pub snapshot: SnapshotInfo,
}
impl ExactRetrievalProof {
pub fn anchor(&self) -> &RetrievalProofAnchor {
&self.anchor
}
pub fn anchor_digest(&self) -> [u8; 32] {
self.anchor.digest()
}
pub fn semantics_version(&self) -> u16 {
self.semantics_version
}
pub fn request(&self) -> &ExactRetrievalRequest {
&self.request
}
pub fn outcome(&self) -> &ExactRetrievalOutcome {
&self.outcome
}
pub fn proof_digest(&self) -> [u8; 32] {
self.proof_digest
}
}
impl LexicalRetrievalProof {
pub fn anchor(&self) -> &RetrievalProofAnchor {
&self.anchor
}
pub fn anchor_digest(&self) -> [u8; 32] {
self.anchor.digest()
}
pub fn semantics_version(&self) -> u16 {
self.semantics_version
}
pub fn request(&self) -> &LexicalRequest {
&self.request
}
pub fn outcome(&self) -> &LexicalOutcome {
&self.outcome
}
pub fn proof_digest(&self) -> [u8; 32] {
self.proof_digest
}
}
impl HybridRetrievalProof {
pub fn anchor(&self) -> &RetrievalProofAnchor {
&self.anchor
}
pub fn anchor_digest(&self) -> [u8; 32] {
self.anchor.digest()
}
pub fn semantics_version(&self) -> u16 {
self.semantics_version
}
pub fn lexical_request(&self) -> &LexicalRequest {
&self.lexical_request
}
pub fn lexical_outcome(&self) -> &LexicalOutcome {
&self.lexical_outcome
}
pub fn vector_request(&self) -> &ExactRetrievalRequest {
&self.vector_request
}
pub fn vector_outcome(&self) -> &ExactRetrievalOutcome {
&self.vector_outcome
}
pub fn fusion_request(&self) -> &HybridRequest {
&self.fusion_request
}
pub fn outcome(&self) -> &HybridOutcome {
&self.outcome
}
pub fn proof_digest(&self) -> [u8; 32] {
self.proof_digest
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetrievalVerificationLimits {
pub proof_bytes: u64,
pub snapshot: SnapshotReadLimits,
pub max_candidates: u64,
pub max_candidate_bytes: u64,
pub max_returned: usize,
pub max_documents: u64,
pub max_tokens: u64,
pub max_lexical_candidates: u64,
pub max_lexical_returned: usize,
pub max_hybrid_returned: usize,
pub timeout: Duration,
}
impl Default for RetrievalVerificationLimits {
fn default() -> Self {
Self {
proof_bytes: MAX_RETRIEVAL_PROOF_BYTES,
snapshot: SnapshotReadLimits::default(),
max_candidates: 100_000,
max_candidate_bytes: 256 * 1024 * 1024,
max_returned: 1_000,
max_documents: 1_000_000,
max_tokens: 10_000_000,
max_lexical_candidates: 100_000,
max_lexical_returned: 1_000,
max_hybrid_returned: 1_000,
timeout: Duration::from_secs(60),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExactRetrievalVerificationReport {
pub anchor: RetrievalProofAnchor,
pub anchor_digest: [u8; 32],
pub proof_digest: [u8; 32],
pub outcome: ExactRetrievalOutcome,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LexicalRetrievalVerificationReport {
pub anchor: RetrievalProofAnchor,
pub anchor_digest: [u8; 32],
pub proof_digest: [u8; 32],
pub outcome: LexicalOutcome,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HybridRetrievalVerificationReport {
pub anchor: RetrievalProofAnchor,
pub anchor_digest: [u8; 32],
pub proof_digest: [u8; 32],
pub outcome: HybridOutcome,
}
#[derive(Debug, Error)]
pub enum RetrievalProofError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("snapshot witness failed: {source}")]
Snapshot {
#[source]
source: Box<SnapshotError>,
},
#[error(transparent)]
Vector(#[from] VectorValueError),
#[error("exact retrieval replay failed: {source}")]
Retrieval {
#[source]
source: Box<ExactRetrievalError>,
},
#[error("lexical retrieval replay failed: {source}")]
Lexical {
#[source]
source: Box<LexicalError>,
},
#[error("hybrid retrieval replay failed: {source}")]
Hybrid {
#[source]
source: Box<HybridError>,
},
#[error("snapshot document decoding failed: {source}")]
Document {
#[source]
source: Box<DocumentError>,
},
#[error("invalid retrieval proof: {reason}")]
Invalid {
reason: &'static str,
},
#[error("unsupported retrieval-proof format {found}; supported format is {supported}")]
UnsupportedVersion {
found: u16,
supported: u16,
},
#[error("unsupported retrieval-proof operation {found}")]
UnsupportedOperation {
found: u16,
},
#[error("unsupported exact-retrieval semantics {found}; supported semantics is {supported}")]
UnsupportedSemantics {
found: u16,
supported: u16,
},
#[error("retrieval proof is {actual} bytes; verification limit is {maximum}")]
ProofLimitExceeded {
actual: u64,
maximum: u64,
},
#[error("retrieval-proof length overflow")]
LengthOverflow,
#[error("retrieval-proof CRC32C mismatch")]
ChecksumMismatch,
#[error("retrieval-proof BLAKE3 mismatch")]
DigestMismatch,
#[error("retrieval-proof anchor does not match the trusted anchor digest")]
AnchorMismatch,
#[error("snapshot witness does not match the retrieval-proof anchor")]
SnapshotAnchorMismatch,
#[error("retrieval proofs require a disk-format-2 snapshot witness")]
SnapshotFormatMismatch,
#[error("offline reexecution does not match the retrieval proof")]
ReexecutionMismatch,
#[error("retrieval-proof verification timed out")]
TimedOut,
}
impl From<SnapshotError> for RetrievalProofError {
fn from(source: SnapshotError) -> Self {
Self::Snapshot {
source: Box::new(source),
}
}
}
impl From<ExactRetrievalError> for RetrievalProofError {
fn from(source: ExactRetrievalError) -> Self {
Self::Retrieval {
source: Box::new(source),
}
}
}
impl From<LexicalError> for RetrievalProofError {
fn from(source: LexicalError) -> Self {
Self::Lexical {
source: Box::new(source),
}
}
}
impl From<HybridError> for RetrievalProofError {
fn from(source: HybridError) -> Self {
Self::Hybrid {
source: Box::new(source),
}
}
}
impl From<DocumentError> for RetrievalProofError {
fn from(source: DocumentError) -> Self {
Self::Document {
source: Box::new(source),
}
}
}