hyphae_engine/retrieval_proof/
model.rs1use std::{io, time::Duration};
4
5use hyphae_core::VectorValueError;
6use hyphae_retrieval::{
7 ExactRetrievalError, ExactRetrievalOutcome, ExactRetrievalRequest, HybridError, HybridOutcome,
8 HybridRequest, LexicalError, LexicalOutcome, LexicalRequest,
9};
10use hyphae_storage::{SnapshotError, SnapshotInfo, SnapshotReadLimits};
11use thiserror::Error;
12
13use crate::DocumentError;
14
15pub const RETRIEVAL_PROOF_FORMAT_VERSION: u16 = 1;
17
18pub const EXACT_RETRIEVAL_SEMANTICS_VERSION: u16 = 2;
20
21pub const LEXICAL_RETRIEVAL_SEMANTICS_VERSION: u16 = 1;
23
24pub const HYBRID_RETRIEVAL_SEMANTICS_VERSION: u16 = 1;
26
27pub const MAX_RETRIEVAL_PROOF_BYTES: u64 = 64 * 1024 * 1024;
29
30#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct RetrievalProofAnchor {
33 pub checkpoint_sequence: u64,
35 pub checkpoint_digest: Option<[u8; 32]>,
37 pub snapshot_digest: [u8; 32],
39}
40
41impl RetrievalProofAnchor {
42 pub fn from_snapshot(snapshot: &SnapshotInfo) -> Self {
44 Self {
45 checkpoint_sequence: snapshot.checkpoint_sequence,
46 checkpoint_digest: snapshot.checkpoint_digest,
47 snapshot_digest: snapshot.snapshot_digest,
48 }
49 }
50
51 pub fn digest(&self) -> [u8; 32] {
53 let mut hasher = blake3::Hasher::new();
54 hasher.update(b"hyphae-retrieval-anchor-v1");
55 hasher.update(&self.checkpoint_sequence.to_le_bytes());
56 hasher.update(&self.checkpoint_digest.unwrap_or([0; 32]));
57 hasher.update(&self.snapshot_digest);
58 *hasher.finalize().as_bytes()
59 }
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
64pub struct ExactRetrievalProof {
65 pub(crate) anchor: RetrievalProofAnchor,
66 pub(crate) semantics_version: u16,
67 pub(crate) request: ExactRetrievalRequest,
68 pub(crate) outcome: ExactRetrievalOutcome,
69 pub(crate) proof_digest: [u8; 32],
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
74pub struct ExactRetrievalProofArtifact {
75 pub proof: ExactRetrievalProof,
77 pub snapshot: SnapshotInfo,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq)]
83pub struct LexicalRetrievalProof {
84 pub(crate) anchor: RetrievalProofAnchor,
85 pub(crate) semantics_version: u16,
86 pub(crate) request: LexicalRequest,
87 pub(crate) outcome: LexicalOutcome,
88 pub(crate) proof_digest: [u8; 32],
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
93pub struct LexicalRetrievalProofArtifact {
94 pub proof: LexicalRetrievalProof,
96 pub snapshot: SnapshotInfo,
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
102pub struct HybridRetrievalProof {
103 pub(crate) anchor: RetrievalProofAnchor,
104 pub(crate) semantics_version: u16,
105 pub(crate) lexical_request: LexicalRequest,
106 pub(crate) lexical_outcome: LexicalOutcome,
107 pub(crate) vector_request: ExactRetrievalRequest,
108 pub(crate) vector_outcome: ExactRetrievalOutcome,
109 pub(crate) fusion_request: HybridRequest,
110 pub(crate) outcome: HybridOutcome,
111 pub(crate) proof_digest: [u8; 32],
112}
113
114#[derive(Clone, Debug, Eq, PartialEq)]
116pub struct HybridRetrievalProofArtifact {
117 pub proof: HybridRetrievalProof,
119 pub snapshot: SnapshotInfo,
121}
122
123impl ExactRetrievalProof {
124 pub fn anchor(&self) -> &RetrievalProofAnchor {
126 &self.anchor
127 }
128
129 pub fn anchor_digest(&self) -> [u8; 32] {
131 self.anchor.digest()
132 }
133
134 pub fn semantics_version(&self) -> u16 {
136 self.semantics_version
137 }
138
139 pub fn request(&self) -> &ExactRetrievalRequest {
141 &self.request
142 }
143
144 pub fn outcome(&self) -> &ExactRetrievalOutcome {
146 &self.outcome
147 }
148
149 pub fn proof_digest(&self) -> [u8; 32] {
151 self.proof_digest
152 }
153}
154
155impl LexicalRetrievalProof {
156 pub fn anchor(&self) -> &RetrievalProofAnchor {
158 &self.anchor
159 }
160
161 pub fn anchor_digest(&self) -> [u8; 32] {
163 self.anchor.digest()
164 }
165
166 pub fn semantics_version(&self) -> u16 {
168 self.semantics_version
169 }
170
171 pub fn request(&self) -> &LexicalRequest {
173 &self.request
174 }
175
176 pub fn outcome(&self) -> &LexicalOutcome {
178 &self.outcome
179 }
180
181 pub fn proof_digest(&self) -> [u8; 32] {
183 self.proof_digest
184 }
185}
186
187impl HybridRetrievalProof {
188 pub fn anchor(&self) -> &RetrievalProofAnchor {
190 &self.anchor
191 }
192
193 pub fn anchor_digest(&self) -> [u8; 32] {
195 self.anchor.digest()
196 }
197
198 pub fn semantics_version(&self) -> u16 {
200 self.semantics_version
201 }
202
203 pub fn lexical_request(&self) -> &LexicalRequest {
205 &self.lexical_request
206 }
207
208 pub fn lexical_outcome(&self) -> &LexicalOutcome {
210 &self.lexical_outcome
211 }
212
213 pub fn vector_request(&self) -> &ExactRetrievalRequest {
215 &self.vector_request
216 }
217
218 pub fn vector_outcome(&self) -> &ExactRetrievalOutcome {
220 &self.vector_outcome
221 }
222
223 pub fn fusion_request(&self) -> &HybridRequest {
225 &self.fusion_request
226 }
227
228 pub fn outcome(&self) -> &HybridOutcome {
230 &self.outcome
231 }
232
233 pub fn proof_digest(&self) -> [u8; 32] {
235 self.proof_digest
236 }
237}
238
239#[derive(Clone, Debug, Eq, PartialEq)]
241pub struct RetrievalVerificationLimits {
242 pub proof_bytes: u64,
244 pub snapshot: SnapshotReadLimits,
246 pub max_candidates: u64,
248 pub max_candidate_bytes: u64,
250 pub max_returned: usize,
252 pub max_documents: u64,
254 pub max_tokens: u64,
256 pub max_lexical_candidates: u64,
258 pub max_lexical_returned: usize,
260 pub max_hybrid_returned: usize,
262 pub timeout: Duration,
264}
265
266impl Default for RetrievalVerificationLimits {
267 fn default() -> Self {
268 Self {
269 proof_bytes: MAX_RETRIEVAL_PROOF_BYTES,
270 snapshot: SnapshotReadLimits::default(),
271 max_candidates: 100_000,
272 max_candidate_bytes: 1024 * 1024 * 1024,
273 max_returned: 1_000,
274 max_documents: 1_000_000,
275 max_tokens: 10_000_000,
276 max_lexical_candidates: 100_000,
277 max_lexical_returned: 1_000,
278 max_hybrid_returned: 1_000,
279 timeout: Duration::from_secs(60),
280 }
281 }
282}
283
284#[derive(Clone, Debug, Eq, PartialEq)]
286pub struct ExactRetrievalVerificationReport {
287 pub anchor: RetrievalProofAnchor,
289 pub anchor_digest: [u8; 32],
291 pub proof_digest: [u8; 32],
293 pub outcome: ExactRetrievalOutcome,
295}
296
297#[derive(Clone, Debug, Eq, PartialEq)]
299pub struct LexicalRetrievalVerificationReport {
300 pub anchor: RetrievalProofAnchor,
302 pub anchor_digest: [u8; 32],
304 pub proof_digest: [u8; 32],
306 pub outcome: LexicalOutcome,
308}
309
310#[derive(Clone, Debug, Eq, PartialEq)]
312pub struct HybridRetrievalVerificationReport {
313 pub anchor: RetrievalProofAnchor,
315 pub anchor_digest: [u8; 32],
317 pub proof_digest: [u8; 32],
319 pub outcome: HybridOutcome,
321}
322
323#[derive(Debug, Error)]
325pub enum RetrievalProofError {
326 #[error(transparent)]
328 Io(#[from] io::Error),
329
330 #[error("snapshot witness failed: {source}")]
332 Snapshot {
333 #[source]
335 source: Box<SnapshotError>,
336 },
337
338 #[error(transparent)]
340 Vector(#[from] VectorValueError),
341
342 #[error("exact retrieval replay failed: {source}")]
344 Retrieval {
345 #[source]
347 source: Box<ExactRetrievalError>,
348 },
349
350 #[error("lexical retrieval replay failed: {source}")]
352 Lexical {
353 #[source]
355 source: Box<LexicalError>,
356 },
357
358 #[error("hybrid retrieval replay failed: {source}")]
360 Hybrid {
361 #[source]
363 source: Box<HybridError>,
364 },
365
366 #[error("snapshot document decoding failed: {source}")]
368 Document {
369 #[source]
371 source: Box<DocumentError>,
372 },
373
374 #[error("invalid retrieval proof: {reason}")]
376 Invalid {
377 reason: &'static str,
379 },
380
381 #[error("unsupported retrieval-proof format {found}; supported format is {supported}")]
383 UnsupportedVersion {
384 found: u16,
386 supported: u16,
388 },
389
390 #[error("unsupported retrieval-proof operation {found}")]
392 UnsupportedOperation {
393 found: u16,
395 },
396
397 #[error("unsupported exact-retrieval semantics {found}; supported semantics is {supported}")]
399 UnsupportedSemantics {
400 found: u16,
402 supported: u16,
404 },
405
406 #[error("retrieval proof is {actual} bytes; verification limit is {maximum}")]
408 ProofLimitExceeded {
409 actual: u64,
411 maximum: u64,
413 },
414
415 #[error("retrieval-proof length overflow")]
417 LengthOverflow,
418
419 #[error("retrieval-proof CRC32C mismatch")]
421 ChecksumMismatch,
422
423 #[error("retrieval-proof BLAKE3 mismatch")]
425 DigestMismatch,
426
427 #[error("retrieval-proof anchor does not match the trusted anchor digest")]
429 AnchorMismatch,
430
431 #[error("snapshot witness does not match the retrieval-proof anchor")]
433 SnapshotAnchorMismatch,
434
435 #[error("retrieval proofs require a disk-format-2 snapshot witness")]
437 SnapshotFormatMismatch,
438
439 #[error("offline reexecution does not match the retrieval proof")]
441 ReexecutionMismatch,
442
443 #[error("retrieval-proof verification timed out")]
445 TimedOut,
446}
447
448impl From<SnapshotError> for RetrievalProofError {
449 fn from(source: SnapshotError) -> Self {
450 Self::Snapshot {
451 source: Box::new(source),
452 }
453 }
454}
455
456impl From<ExactRetrievalError> for RetrievalProofError {
457 fn from(source: ExactRetrievalError) -> Self {
458 Self::Retrieval {
459 source: Box::new(source),
460 }
461 }
462}
463
464impl From<LexicalError> for RetrievalProofError {
465 fn from(source: LexicalError) -> Self {
466 Self::Lexical {
467 source: Box::new(source),
468 }
469 }
470}
471
472impl From<HybridError> for RetrievalProofError {
473 fn from(source: HybridError) -> Self {
474 Self::Hybrid {
475 source: Box::new(source),
476 }
477 }
478}
479
480impl From<DocumentError> for RetrievalProofError {
481 fn from(source: DocumentError) -> Self {
482 Self::Document {
483 source: Box::new(source),
484 }
485 }
486}