use std::{
collections::{hash_map::Entry, HashMap},
fmt::{Debug, Display, Error as FmtError, Formatter},
};
use sp_runtime::SaturatedConversion;
use crate::{
phron_primitives::{AuraId, BlockNumber},
session::{SessionBoundaryInfo, SessionId},
session_map::AuthorityProvider,
sync::{
substrate::verification::{verifier::SessionVerifier, FinalizationInfo},
Header,
},
};
#[derive(Debug, PartialEq, Eq)]
pub enum CacheError {
UnknownAuthorities(SessionId),
UnknownAuraAuthorities(SessionId),
SessionTooOld(SessionId, SessionId),
SessionInFuture(SessionId, SessionId),
BadGenesisHeader,
}
impl Display for CacheError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
use CacheError::*;
match self {
SessionTooOld(session, lower_bound) => write!(
f,
"session {session:?} is too old. Should be at least {lower_bound:?}"
),
SessionInFuture(session, upper_bound) => write!(
f,
"session {session:?} without known authorities. Should be at most {upper_bound:?}"
),
UnknownAuthorities(session) => {
write!(
f,
"authorities for session {session:?} not known even though they should be"
)
}
UnknownAuraAuthorities(session) => {
write!(
f,
"Aura authorities for session {session:?} not known even though they should be"
)
}
BadGenesisHeader => {
write!(
f,
"the provided genesis header does not match the cached genesis header"
)
}
}
}
}
struct CachedData {
session_verifier: SessionVerifier,
aura_authorities: Vec<AuraId>,
}
pub struct VerifierCache<AP, FI, H>
where
AP: AuthorityProvider,
FI: FinalizationInfo,
H: Header,
{
cached_data: HashMap<SessionId, CachedData>,
session_info: SessionBoundaryInfo,
finalization_info: FI,
authority_provider: AP,
cache_size: usize,
lower_bound: SessionId,
genesis_header: H,
}
impl<AP, FI, H> VerifierCache<AP, FI, H>
where
AP: AuthorityProvider,
FI: FinalizationInfo,
H: Header,
{
pub fn new(
session_info: SessionBoundaryInfo,
finalization_info: FI,
authority_provider: AP,
cache_size: usize,
genesis_header: H,
) -> Self {
Self {
cached_data: HashMap::new(),
session_info,
finalization_info,
authority_provider,
cache_size,
lower_bound: SessionId(0),
genesis_header,
}
}
pub fn genesis_header(&self) -> &H {
&self.genesis_header
}
}
fn download_data<AP: AuthorityProvider>(
authority_provider: &AP,
session_id: SessionId,
session_info: &SessionBoundaryInfo,
) -> Result<CachedData, CacheError> {
Ok(match session_id {
SessionId(0) => CachedData {
session_verifier: authority_provider
.authority_data(0)
.ok_or(CacheError::UnknownAuthorities(session_id))?
.into(),
aura_authorities: authority_provider
.aura_authorities(0)
.ok_or(CacheError::UnknownAuraAuthorities(session_id))?,
},
SessionId(id) => {
let prev_first = session_info.first_block_of_session(SessionId(id - 1));
CachedData {
session_verifier: authority_provider
.next_authority_data(prev_first)
.ok_or(CacheError::UnknownAuthorities(session_id))?
.into(),
aura_authorities: authority_provider
.next_aura_authorities(prev_first)
.ok_or(CacheError::UnknownAuraAuthorities(session_id))?,
}
}
})
}
impl<AP, FI, H> VerifierCache<AP, FI, H>
where
AP: AuthorityProvider,
FI: FinalizationInfo,
H: Header,
{
fn try_prune(&mut self, session_id: SessionId) {
if session_id.0
>= self
.lower_bound
.0
.saturating_add(self.cache_size.saturated_into())
{
let new_lower_bound = SessionId(
session_id
.0
.saturating_sub(self.cache_size.saturated_into())
+ 1,
);
self.cached_data.retain(|&id, _| id >= new_lower_bound);
self.lower_bound = new_lower_bound;
}
}
fn get_data(&mut self, number: BlockNumber) -> Result<&CachedData, CacheError> {
let session_id = self.session_info.session_id_from_block_num(number);
if session_id < self.lower_bound {
return Err(CacheError::SessionTooOld(session_id, self.lower_bound));
}
let upper_bound = SessionId(
self.session_info
.session_id_from_block_num(self.finalization_info.finalized_number())
.0
+ 1,
);
if session_id > upper_bound {
return Err(CacheError::SessionInFuture(session_id, upper_bound));
}
self.try_prune(session_id);
Ok(match self.cached_data.entry(session_id) {
Entry::Occupied(occupied) => occupied.into_mut(),
Entry::Vacant(vacant) => vacant.insert(download_data(
&self.authority_provider,
session_id,
&self.session_info,
)?),
})
}
pub fn get_aura_authorities(
&mut self,
number: BlockNumber,
) -> Result<&Vec<AuraId>, CacheError> {
Ok(&self.get_data(number)?.aura_authorities)
}
pub fn get(&mut self, number: BlockNumber) -> Result<&SessionVerifier, CacheError> {
Ok(&self.get_data(number)?.session_verifier)
}
}
#[cfg(test)]
mod tests {
use std::{cell::Cell, collections::HashMap};
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_runtime::testing::UintAuthorityId;
use super::{
AuthorityProvider, BlockNumber, CacheError, FinalizationInfo, SessionVerifier,
VerifierCache,
};
use crate::{
phron_primitives::SessionAuthorityData,
session::{testing::authority_data, SessionBoundaryInfo, SessionId},
sync::mock::MockHeader,
SessionPeriod,
};
const SESSION_PERIOD: u32 = 30;
const CACHE_SIZE: usize = 3;
type TestVerifierCache<'a> =
VerifierCache<MockAuthorityProvider, MockFinalizationInfo<'a>, MockHeader>;
struct MockFinalizationInfo<'a> {
finalized_number: &'a Cell<BlockNumber>,
}
impl<'a> FinalizationInfo for MockFinalizationInfo<'a> {
fn finalized_number(&self) -> BlockNumber {
self.finalized_number.get()
}
}
struct MockAuthorityProvider {
session_map: HashMap<SessionId, SessionAuthorityData>,
aura_authority_map: HashMap<SessionId, Vec<AuraId>>,
session_info: SessionBoundaryInfo,
}
fn authority_data_for_session(session_id: u32) -> SessionAuthorityData {
authority_data(session_id * 4, (session_id + 1) * 4)
}
fn aura_authority_data_for_session(session_id: u32) -> Vec<AuraId> {
(session_id * 4..(session_id + 1) * 4)
.map(|id| UintAuthorityId(id.into()).to_public_key())
.collect()
}
impl MockAuthorityProvider {
fn new(session_n: u32) -> Self {
let session_map = (0..session_n + 1)
.map(|s| (SessionId(s), authority_data_for_session(s)))
.collect();
let aura_authority_map = (0..session_n + 1)
.map(|s| (SessionId(s), aura_authority_data_for_session(s)))
.collect();
Self {
session_map,
aura_authority_map,
session_info: SessionBoundaryInfo::new(SessionPeriod(SESSION_PERIOD)),
}
}
}
impl AuthorityProvider for MockAuthorityProvider {
fn authority_data(&self, block_number: BlockNumber) -> Option<SessionAuthorityData> {
self.session_map
.get(&self.session_info.session_id_from_block_num(block_number))
.cloned()
}
fn next_authority_data(&self, block_number: BlockNumber) -> Option<SessionAuthorityData> {
self.session_map
.get(&SessionId(
self.session_info.session_id_from_block_num(block_number).0 + 1,
))
.cloned()
}
fn aura_authorities(&self, block_number: BlockNumber) -> Option<Vec<AuraId>> {
self.aura_authority_map
.get(&self.session_info.session_id_from_block_num(block_number))
.cloned()
}
fn next_aura_authorities(&self, block_number: BlockNumber) -> Option<Vec<AuraId>> {
self.aura_authority_map
.get(&SessionId(
self.session_info.session_id_from_block_num(block_number).0 + 1,
))
.cloned()
}
}
fn setup_test(max_session_n: u32, finalized_number: &'_ Cell<u32>) -> TestVerifierCache<'_> {
let finalization_info = MockFinalizationInfo { finalized_number };
let authority_provider = MockAuthorityProvider::new(max_session_n);
let genesis_header = MockHeader::random_parentless(0);
VerifierCache::new(
SessionBoundaryInfo::new(SessionPeriod(SESSION_PERIOD)),
finalization_info,
authority_provider,
CACHE_SIZE,
genesis_header,
)
}
fn finalize_first_in_session(finalized_number: &Cell<u32>, session_id: u32) {
finalized_number.set(session_id * SESSION_PERIOD);
}
fn session_verifier(
verifier: &mut TestVerifierCache,
session_id: u32,
) -> Result<SessionVerifier, CacheError> {
verifier.get((session_id + 1) * SESSION_PERIOD - 1).cloned()
}
fn check_session_verifier(verifier: &mut TestVerifierCache, session_id: u32) {
let session_verifier =
session_verifier(verifier, session_id).expect("Should return verifier. Got error");
let expected_verifier: SessionVerifier = authority_data_for_session(session_id).into();
assert_eq!(session_verifier, expected_verifier);
}
#[test]
fn genesis_session() {
let finalized_number = Cell::new(0);
let mut verifier = setup_test(0, &finalized_number);
check_session_verifier(&mut verifier, 0);
}
#[test]
fn normal_session() {
let finalized_number = Cell::new(0);
let mut verifier = setup_test(3, &finalized_number);
check_session_verifier(&mut verifier, 0);
check_session_verifier(&mut verifier, 1);
finalize_first_in_session(&finalized_number, 1);
check_session_verifier(&mut verifier, 0);
check_session_verifier(&mut verifier, 1);
check_session_verifier(&mut verifier, 2);
finalize_first_in_session(&finalized_number, 2);
check_session_verifier(&mut verifier, 1);
check_session_verifier(&mut verifier, 2);
check_session_verifier(&mut verifier, 3);
}
#[test]
fn prunes_old_sessions() {
assert_eq!(CACHE_SIZE, 3);
let finalized_number = Cell::new(0);
let mut verifier = setup_test(4, &finalized_number);
check_session_verifier(&mut verifier, 0);
check_session_verifier(&mut verifier, 1);
finalize_first_in_session(&finalized_number, 1);
check_session_verifier(&mut verifier, 2);
finalize_first_in_session(&finalized_number, 2);
check_session_verifier(&mut verifier, 3);
assert_eq!(
session_verifier(&mut verifier, 0),
Err(CacheError::SessionTooOld(SessionId(0), SessionId(1)))
);
finalize_first_in_session(&finalized_number, 3);
check_session_verifier(&mut verifier, 4);
assert_eq!(
session_verifier(&mut verifier, 1),
Err(CacheError::SessionTooOld(SessionId(1), SessionId(2)))
);
}
#[test]
fn session_from_future() {
let finalized_number = Cell::new(0);
let mut verifier = setup_test(3, &finalized_number);
finalize_first_in_session(&finalized_number, 1);
assert_eq!(
session_verifier(&mut verifier, 3),
Err(CacheError::SessionInFuture(SessionId(3), SessionId(2)))
);
}
#[test]
fn authority_provider_error() {
let finalized_number = Cell::new(0);
let mut verifier = setup_test(0, &finalized_number);
assert_eq!(
session_verifier(&mut verifier, 1),
Err(CacheError::UnknownAuthorities(SessionId(1)))
);
finalize_first_in_session(&finalized_number, 1);
assert_eq!(
session_verifier(&mut verifier, 2),
Err(CacheError::UnknownAuthorities(SessionId(2)))
);
}
}