1use std::path::Path;
13use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
14
15use kernel::{AxError, B3Hash};
16
17use crate::error::StorageError;
18use crate::jsonl::{JsonlLedger, LineCheck, segment_names};
19use crate::real_fs::RealFs;
20use crate::verified_prefix::{ProofRecords, SegmentRecord, line_check_version};
21use wave::{Hashed, PROOF_WAVE, Read, read_wave};
22
23mod wave;
24
25#[derive(Debug, Clone, PartialEq)]
27pub enum ChainAudit {
28 Whole { lines: u64 },
30 Broken(AxError),
32}
33
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
37pub struct ProofCount {
38 pub lines_checked: u64,
40 pub segments_by_digest: u64,
42 pub bytes_read: u64,
43 pub bytes_hashed: u64,
44 pub waves: u64,
47}
48
49#[derive(Debug, Clone, PartialEq)]
52pub struct Proven {
53 pub audit: ChainAudit,
54 pub counted: ProofCount,
55 pub unkept: Option<AxError>,
56}
57
58#[derive(Debug, Clone, Default)]
61pub struct ChainHalt {
62 verdict: Arc<(Mutex<Option<Verdict>>, Condvar)>,
63 before: Before,
64}
65
66#[derive(Debug, Clone)]
67enum Verdict {
68 Whole,
69 Broken(AxError),
70}
71
72#[derive(Debug, Clone, Copy, Default)]
74enum Before {
75 #[default]
78 Admit,
79 Refuse,
82}
83
84impl ChainHalt {
85 pub fn awaiting_proof() -> ChainHalt {
87 ChainHalt {
88 verdict: Arc::default(),
89 before: Before::Refuse,
90 }
91 }
92
93 pub fn prove(&self) {
95 self.settle(Verdict::Whole);
96 }
97
98 pub fn trip(&self, reason: AxError) {
101 self.settle(Verdict::Broken(reason));
102 }
103
104 pub fn reason(&self) -> Option<AxError> {
106 match &*self.held() {
107 Some(Verdict::Broken(reason)) => Some(reason.clone()),
108 Some(Verdict::Whole) | None => None,
109 }
110 }
111
112 pub fn proved(&self) -> bool {
114 matches!(&*self.held(), Some(Verdict::Whole))
115 }
116
117 pub fn await_verdict(&self) {
120 match self.before {
121 Before::Admit => {}
122 Before::Refuse => {
123 let (lock, settled) = &*self.verdict;
124 let guard = lock.lock().unwrap_or_else(PoisonError::into_inner);
125 drop(
126 settled
127 .wait_while(guard, |verdict| verdict.is_none())
128 .unwrap_or_else(PoisonError::into_inner),
129 );
130 }
131 }
132 }
133
134 pub(crate) fn admit(&self) -> Result<(), StorageError> {
135 match (&*self.held(), self.before) {
136 (Some(Verdict::Whole), _) | (None, Before::Admit) => Ok(()),
137 (Some(Verdict::Broken(reason)), _) => Err(StorageError::ChainHalted {
138 source: reason.clone(),
139 }),
140 (None, Before::Refuse) => Err(StorageError::Unproven),
141 }
142 }
143
144 fn settle(&self, verdict: Verdict) {
145 let (lock, settled) = &*self.verdict;
146 let mut held = lock.lock().unwrap_or_else(PoisonError::into_inner);
147 if held.is_none() {
148 *held = Some(verdict);
149 settled.notify_all();
150 }
151 }
152
153 fn held(&self) -> MutexGuard<'_, Option<Verdict>> {
156 self.verdict
157 .0
158 .lock()
159 .unwrap_or_else(PoisonError::into_inner)
160 }
161}
162
163impl JsonlLedger {
164 pub fn halt_on(&mut self, halt: ChainHalt) {
167 self.halt = halt;
168 }
169
170 pub fn await_verdict(&self) {
172 self.halt.await_verdict();
173 }
174
175 pub fn admits(&self) -> Result<(), StorageError> {
182 self.halt.admit()
183 }
184}
185
186pub fn audit_chain(dir: &Path) -> Result<ChainAudit, StorageError> {
193 walk(dir, None).map(|proven| proven.audit)
194}
195
196pub fn prove_chain(dir: &Path, records: &ProofRecords) -> Result<Proven, StorageError> {
204 walk(dir, Some(records))
205}
206
207fn walk(dir: &Path, records: Option<&ProofRecords>) -> Result<Proven, StorageError> {
210 let version = line_check_version();
211 let mut walked = Walked {
212 check: LineCheck::at_genesis(),
213 lines: 0,
214 counted: ProofCount::default(),
215 unkept: None,
216 };
217 for wave in segment_names(&RealFs::new(), dir)?.chunks(PROOF_WAVE) {
218 walked.counted.waves = walked.counted.waves.saturating_add(1);
219 for (name, read) in wave.iter().zip(read_wave(dir, wave, records, version)?) {
220 let proved = match walked.segment(read, version) {
221 Ok(proved) => proved,
222 Err(broken) => {
223 return Ok(Proven {
224 audit: ChainAudit::Broken(broken),
225 counted: walked.counted,
226 unkept: walked.unkept,
227 });
228 }
229 };
230 if let Some(records) = records {
231 let kept = records.keep(name, &proved).map_err(StorageError::into_ax);
232 walked.unkept = walked.unkept.take().or(kept.err());
233 }
234 }
235 }
236 Ok(Proven {
237 audit: ChainAudit::Whole {
238 lines: walked.lines,
239 },
240 counted: walked.counted,
241 unkept: walked.unkept,
242 })
243}
244
245struct Walked {
247 check: LineCheck,
248 lines: u64,
249 counted: ProofCount,
250 unkept: Option<AxError>,
251}
252
253impl Walked {
254 fn segment(&mut self, read: Read, version: u32) -> Result<SegmentRecord, AxError> {
258 let Read { bytes, hashed } = read;
259 self.counted.bytes_read = self.counted.bytes_read.saturating_add(len_of(&bytes));
260 let entry = self.check;
261 let complete = bytes
262 .iter()
263 .rposition(|byte| *byte == b'\n')
264 .map_or(0, |at| at.saturating_add(1));
265 let (from, mut in_segment, mut hasher) = hashed
266 .and_then(|hashed| self.reuse(hashed))
267 .unwrap_or_else(|| (0, 0, blake3::Hasher::new()));
268 let rest = bytes.get(from..complete).unwrap_or_default();
269 hasher.update(rest);
270 self.counted.bytes_hashed = self.counted.bytes_hashed.saturating_add(len_of(rest));
271 for line in rest.split(|byte| *byte == b'\n') {
272 if line.is_empty() {
273 continue;
274 }
275 self.lines = self.lines.saturating_add(1);
276 in_segment = in_segment.saturating_add(1);
277 self.counted.lines_checked = self.counted.lines_checked.saturating_add(1);
278 self.check
279 .advance(line)
280 .map_err(|fault| fault.into_ax(self.lines))?;
281 }
282 Ok(SegmentRecord {
283 version,
284 len: len_of(bytes.get(..complete).unwrap_or_default()),
285 lines: in_segment,
286 entry,
287 exit: self.check,
288 digest: B3Hash::from_bytes(*hasher.finalize().as_bytes()),
289 })
290 }
291
292 fn reuse(&mut self, hashed: Hashed) -> Option<(usize, u64, blake3::Hasher)> {
298 let Hashed {
299 record,
300 len,
301 hasher,
302 } = hashed;
303 self.counted.bytes_hashed = self
304 .counted
305 .bytes_hashed
306 .saturating_add(u64::try_from(len).unwrap_or(u64::MAX));
307 let (lines, exit) = record.stands(self.check, &hasher)?;
308 self.lines = self.lines.saturating_add(lines);
309 self.check = exit;
310 self.counted.segments_by_digest = self.counted.segments_by_digest.saturating_add(1);
311 Some((len, lines, hasher))
312 }
313}
314
315fn len_of(bytes: &[u8]) -> u64 {
316 u64::try_from(bytes.len()).unwrap_or(u64::MAX)
317}
318
319#[cfg(test)]
320mod tests;