use super::events::HeaderRootAuthState;
use super::state::{RangePriority, RangeRequest};
use super::HeaderRangePayload;
use zakura_chain::block;
pub(super) fn retained_root_auth_range(
auth: HeaderRootAuthState,
payload: &HeaderRangePayload,
handoff: block::Height,
) -> Option<RangeRequest> {
let expected_start = auth
.authenticated_height
.0
.checked_add(1)
.map(block::Height)?;
(payload.range().start() == expected_start
&& payload.range().count() >= 2
&& payload.has_tree_aux_roots()
&& payload.range().end() <= auth.completed_checkpoint_height
&& payload.range().end() <= handoff)
.then_some(RangeRequest {
range: payload.range(),
anchor_hash: Some(auth.authenticated_hash),
finalized: true,
want_tree_aux_roots: true,
priority: RangePriority::AuthenticateRoots,
})
}
pub(super) fn root_auth_pipeline_compatible(
previous: Option<HeaderRootAuthState>,
next: Option<HeaderRootAuthState>,
) -> bool {
match (previous, next) {
(Some(old), Some(new)) => {
frontier_not_rebased(
old.authenticated_height,
old.authenticated_hash,
new.authenticated_height,
new.authenticated_hash,
) && frontier_not_rebased(
old.completed_checkpoint_height,
old.completed_checkpoint_hash,
new.completed_checkpoint_height,
new.completed_checkpoint_hash,
)
}
_ => false,
}
}
fn frontier_not_rebased(
old_height: block::Height,
old_hash: block::Hash,
new_height: block::Height,
new_hash: block::Hash,
) -> bool {
new_height > old_height || (new_height == old_height && new_hash == old_hash)
}
#[cfg(test)]
mod tests {
use super::*;
fn auth(
authenticated_height: u32,
authenticated_hash: u8,
completed_checkpoint_height: u32,
completed_checkpoint_hash: u8,
) -> HeaderRootAuthState {
HeaderRootAuthState {
authenticated_height: block::Height(authenticated_height),
authenticated_hash: block::Hash([authenticated_hash; 32]),
completed_checkpoint_height: block::Height(completed_checkpoint_height),
completed_checkpoint_hash: block::Hash([completed_checkpoint_hash; 32]),
header_witness: None,
}
}
#[test]
fn missing_either_side_is_incompatible() {
let state = auth(1, 1, 4, 4);
assert!(!root_auth_pipeline_compatible(None, None));
assert!(!root_auth_pipeline_compatible(None, Some(state)));
assert!(!root_auth_pipeline_compatible(Some(state), None));
}
#[test]
fn advancing_or_unchanged_frontiers_are_compatible() {
let previous = auth(1, 1, 4, 4);
assert!(root_auth_pipeline_compatible(
Some(previous),
Some(auth(2, 2, 4, 4))
));
assert!(root_auth_pipeline_compatible(
Some(previous),
Some(auth(1, 1, 6, 6))
));
assert!(root_auth_pipeline_compatible(
Some(previous),
Some(previous)
));
}
#[test]
fn same_height_hash_change_is_a_rebase() {
let previous = auth(1, 1, 4, 4);
assert!(!root_auth_pipeline_compatible(
Some(previous),
Some(auth(1, 9, 4, 4))
));
assert!(!root_auth_pipeline_compatible(
Some(previous),
Some(auth(1, 1, 4, 9))
));
}
#[test]
fn stale_successor_witness_degradation_keeps_the_frontier_compatible() {
let mut previous = auth(4, 4, 5, 5);
previous.header_witness = Some(crate::zakura::HeaderWitnessState {
height: block::Height(5),
hash: block::Hash([5; 32]),
});
let next = HeaderRootAuthState {
header_witness: None,
..previous
};
assert!(root_auth_pipeline_compatible(Some(previous), Some(next)));
}
}