use super::*;
pub fn verify_log(
records: &[Record],
rules: &VerifierRules,
checkpoint: Option<&TrustedCheckpoint>,
) -> LogVerdict {
let start_index;
let mut state;
let mut index = PriorIndex::default();
if let Some(trusted) = checkpoint {
match sha256_canonical(rules) {
Ok(h) if h == *trusted.rules_hash() => {}
_ => return reject(ReasonCode::InvalidCheckpoint, 0, records),
}
let cp = trusted.checkpoint();
if cp.log_length > records.len() as u64 {
return reject(ReasonCode::InvalidCheckpoint, 0, records);
}
let prefix = &records[..cp.log_length as usize];
let ids: Vec<[u8; 32]> = prefix.iter().map(|r| r.id).collect();
let computed_log_hash = crate::base::hash::sha256_concat_ids(&ids);
if computed_log_hash != cp.log_hash {
return reject(ReasonCode::InvalidCheckpoint, 0, records);
}
start_index = cp.log_length as usize;
if start_index < records.len() && records[start_index].kind == Kind::Verdict {
return reject(ReasonCode::InvalidCheckpoint, 0, records);
}
state = match build_state_unchecked(prefix) {
Ok(s) => s,
Err(_) => {
return reject(ReasonCode::InvalidPayload, 0, records);
}
};
let (expect_time, expect_id) = prefix
.last()
.map(|r| (r.time, r.id))
.unwrap_or((0, [0u8; 32]));
let state_hash = match sha256_canonical(&state) {
Ok(h) => h,
Err(_) => {
return reject(ReasonCode::InvalidPayload, 0, records);
}
};
if cp.last_time != expect_time
|| cp.last_record_id != expect_id
|| cp.state_hash != state_hash
{
return reject(ReasonCode::InvalidCheckpoint, 0, records);
}
for (pos, record) in prefix.iter().enumerate() {
index.insert(pos, record);
}
} else {
start_index = 0;
state = State::default();
}
let mut checked = 0u64;
for i in 1..records.len() {
match records[i - 1].time.checked_add(1) {
Some(expected) if records[i].time == expected => {}
_ => return reject(ReasonCode::InvalidPayload, checked, records),
}
}
if !records.is_empty() && records[0].time != 1 {
return reject(ReasonCode::InvalidPayload, 0, records);
}
for record in records {
let computed_id = match record.compute_id() {
Ok(id) => id,
Err(_) => {
return reject(ReasonCode::InvalidPayload, checked, records);
}
};
if record.id != computed_id {
return reject(ReasonCode::InvalidPayload, checked, records);
}
}
let mut i = start_index;
while i < records.len() {
let record = &records[i];
if record.kind == Kind::Verdict {
let prior = index.view(records);
if let Some(reason) = check_verdict_record(record, &prior, rules) {
return reject(reason, checked, records);
}
let stored_verdict: VerdictData = match decode(&record.data) {
Ok(v) => v,
Err(_) => {
return reject(ReasonCode::InvalidPayload, checked, records);
}
};
let Some(subject_id) = record
.refs
.iter()
.find(|r| r.type_ == RefType::Cause)
.map(|r| r.target)
else {
return reject(ReasonCode::InvalidPayload, checked, records);
};
if let Some(subject) = prior.find(subject_id) {
if crate::state::incremental::apply_record(&mut state, subject, &stored_verdict)
.is_err()
{
return reject(ReasonCode::InvalidPayload, checked, records);
}
}
state.applied_up_to = record.time;
index.insert(i, record);
checked += 1;
i += 1;
continue;
}
if i + 1 >= records.len() {
return reject(ReasonCode::InvalidPayload, checked, records);
}
let next = &records[i + 1];
if next.kind != Kind::Verdict {
return reject(ReasonCode::InvalidPayload, checked, records);
}
index.insert(i, record);
let prior = index.view(records);
if let Some(reason) = check_verdict_record(next, &prior, rules) {
return reject(reason, checked, records);
}
if next.refs[0].target != record.id {
return reject(ReasonCode::InvalidPayload, checked, records);
}
let recomputed = match check_record(record, &prior, rules, &state) {
Some(reason) => VerdictData {
result: VerdictResult::Reject,
reason: Some(reason),
},
None => VerdictData {
result: VerdictResult::Accept,
reason: None,
},
};
let stored_verdict: VerdictData = match decode(&next.data) {
Ok(v) => v,
Err(_) => {
return reject(ReasonCode::InvalidPayload, checked, records);
}
};
if recomputed.result != stored_verdict.result || recomputed.reason != stored_verdict.reason
{
return reject(
recomputed.reason.unwrap_or(ReasonCode::InvalidPayload),
checked,
records,
);
}
if crate::state::incremental::apply_record(&mut state, record, &stored_verdict).is_err() {
return reject(ReasonCode::InvalidPayload, checked, records);
}
state.applied_up_to = next.time;
index.insert(i + 1, next);
checked += 2; i += 2;
}
LogVerdict {
result: VerdictResult::Accept,
reason: None,
checked_records: checked,
last_time: records.last().map(|r| r.time).unwrap_or(0),
retracted_records: state.retracted_records,
tainted_records: state.tainted_records,
}
}