use crate::{
atomic_batch_scope,
cow_to_cloned,
cow_to_copied,
helpers::{Map, MapRead},
};
use console::network::prelude::*;
use ledger_committee::Committee;
use aleo_std_storage::StorageMode;
use anyhow::Result;
use core::marker::PhantomData;
const ROUND_KEY: u8 = 0;
pub trait CommitteeStorage<N: Network>: 'static + Clone + Send + Sync {
type CurrentRoundMap: for<'a> Map<'a, u8, u64>;
type RoundToHeightMap: for<'a> Map<'a, u64, u32>;
type CommitteeMap: for<'a> Map<'a, u32, Committee<N>>;
fn open<S: Clone + Into<StorageMode>>(storage: S) -> Result<Self>;
#[cfg(any(test, feature = "test"))]
fn open_testing(temp_dir: std::path::PathBuf, dev: Option<u16>) -> Result<Self>;
fn current_round_map(&self) -> &Self::CurrentRoundMap;
fn round_to_height_map(&self) -> &Self::RoundToHeightMap;
fn committee_map(&self) -> &Self::CommitteeMap;
fn storage_mode(&self) -> &StorageMode;
fn start_atomic(&self) {
self.current_round_map().start_atomic();
self.round_to_height_map().start_atomic();
self.committee_map().start_atomic();
}
fn is_atomic_in_progress(&self) -> bool {
self.current_round_map().is_atomic_in_progress()
|| self.round_to_height_map().is_atomic_in_progress()
|| self.committee_map().is_atomic_in_progress()
}
fn atomic_checkpoint(&self) {
self.current_round_map().atomic_checkpoint();
self.round_to_height_map().atomic_checkpoint();
self.committee_map().atomic_checkpoint();
}
fn clear_latest_checkpoint(&self) {
self.current_round_map().clear_latest_checkpoint();
self.round_to_height_map().clear_latest_checkpoint();
self.committee_map().clear_latest_checkpoint();
}
fn atomic_rewind(&self) {
self.current_round_map().atomic_rewind();
self.round_to_height_map().atomic_rewind();
self.committee_map().atomic_rewind();
}
fn abort_atomic(&self) {
self.current_round_map().abort_atomic();
self.round_to_height_map().abort_atomic();
self.committee_map().abort_atomic();
}
fn finish_atomic(&self) -> Result<()> {
self.current_round_map().finish_atomic()?;
self.round_to_height_map().finish_atomic()?;
self.committee_map().finish_atomic()
}
fn insert(&self, next_height: u32, committee: Committee<N>) -> Result<()> {
let next_round = committee.starting_round();
ensure!(next_round >= next_height as u64, "Next round must be at least the next height");
match self.current_round() {
Err(..) => ensure!(next_round == 0, "Next round must be block round 0"),
Ok(current_round) => ensure!(
next_round > current_round,
"Next round {next_round} must be greater than current round {current_round}"
),
}
match self.current_height() {
Err(..) => ensure!(next_height == 0, "Next height must be block height 0"),
Ok(current_height) => ensure!(next_height == current_height + 1, "Next height must be sequential"),
}
ensure!(
!self.round_to_height_map().contains_key_confirmed(&next_round)?,
"Next round {next_round} already exists in committee storage"
);
let catch_up_round = match self.current_round() {
Err(..) => 0,
Ok(current_round) => current_round + 1,
};
atomic_batch_scope!(self, {
self.current_round_map().insert(ROUND_KEY, next_round)?;
if let Ok(current_height) = self.current_height() {
for round in catch_up_round..next_round {
self.round_to_height_map().insert(round, current_height)?;
}
}
self.round_to_height_map().insert(next_round, next_height)?;
self.committee_map().insert(next_height, committee)?;
Ok(())
})
}
fn remove(&self, height: u32) -> Result<()> {
let current_round = self.current_round()?;
let current_height = self.current_height()?;
let Some(committee) = self.get_committee(height)? else {
bail!("Committee not found for height {height} in committee storage");
};
let committee_round = committee.starting_round();
let is_latest_committee = current_height == height;
let mut earliest_round = committee_round;
while earliest_round > 0 && self.get_height_for_round(earliest_round)? == Some(height) {
earliest_round = earliest_round.saturating_sub(1);
}
let is_multiple = earliest_round != committee_round;
if is_multiple {
earliest_round += 1;
}
let mut latest_round = committee_round;
while self.get_height_for_round(latest_round)? == Some(height) {
latest_round = latest_round.saturating_add(1);
}
let mut next_current_round = current_round.saturating_sub(1);
if is_latest_committee {
while next_current_round > 0 {
if let Some(next_current_height) = self.get_height_for_round(next_current_round)? {
if next_current_height < current_height {
break;
}
}
next_current_round = next_current_round.saturating_sub(1);
}
}
atomic_batch_scope!(self, {
if is_latest_committee {
self.current_round_map().remove(&ROUND_KEY)?;
if current_round != next_current_round && next_current_round > 0 {
self.current_round_map().insert(ROUND_KEY, next_current_round)?;
}
}
for round in earliest_round..latest_round {
self.round_to_height_map().remove(&round)?;
}
self.committee_map().remove(&height)?;
Ok(())
})
}
fn current_round(&self) -> Result<u64> {
match self.current_round_map().get_confirmed(&ROUND_KEY)? {
Some(round) => Ok(cow_to_copied!(round)),
None => bail!("Current round not found in committee storage"),
}
}
fn current_height(&self) -> Result<u32> {
let current_round = self.current_round()?;
match self.round_to_height_map().get_confirmed(¤t_round)? {
Some(height) => Ok(cow_to_copied!(height)),
None => bail!("Current height not found in committee storage"),
}
}
fn current_committee(&self) -> Result<Committee<N>> {
match self.get_committee(self.current_height()?)? {
Some(committee) => Ok(committee),
None => bail!("Current committee not found in committee storage"),
}
}
fn get_height_for_round(&self, round: u64) -> Result<Option<u32>> {
match self.round_to_height_map().get_confirmed(&round)? {
Some(height) => Ok(Some(cow_to_copied!(height))),
None => Ok(None),
}
}
fn get_committee(&self, height: u32) -> Result<Option<Committee<N>>> {
match self.committee_map().get_confirmed(&height)? {
Some(committee) => Ok(Some(cow_to_cloned!(committee))),
None => Ok(None),
}
}
fn get_committee_for_round(&self, round: u64) -> Result<Option<Committee<N>>> {
match self.get_height_for_round(round)? {
Some(height) => Ok(self.get_committee(height)?),
None => Ok(None),
}
}
}
#[derive(Clone)]
pub struct CommitteeStore<N: Network, C: CommitteeStorage<N>> {
storage: C,
_phantom: PhantomData<N>,
}
impl<N: Network, C: CommitteeStorage<N>> CommitteeStore<N, C> {
pub fn open<S: Clone + Into<StorageMode>>(storage: S) -> Result<Self> {
let storage = C::open(storage.clone())?;
Ok(Self { storage, _phantom: PhantomData })
}
#[cfg(any(test, feature = "test"))]
pub fn open_testing(temp_dir: std::path::PathBuf, dev: Option<u16>) -> Result<Self> {
let storage = C::open_testing(temp_dir, dev)?;
Ok(Self { storage, _phantom: PhantomData })
}
pub fn from(storage: C) -> Self {
Self { storage, _phantom: PhantomData }
}
pub fn start_atomic(&self) {
self.storage.start_atomic();
}
pub fn is_atomic_in_progress(&self) -> bool {
self.storage.is_atomic_in_progress()
}
pub fn atomic_checkpoint(&self) {
self.storage.atomic_checkpoint();
}
pub fn clear_latest_checkpoint(&self) {
self.storage.clear_latest_checkpoint();
}
pub fn atomic_rewind(&self) {
self.storage.atomic_rewind();
}
pub fn abort_atomic(&self) {
self.storage.abort_atomic();
}
pub fn finish_atomic(&self) -> Result<()> {
self.storage.finish_atomic()
}
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}
}
impl<N: Network, C: CommitteeStorage<N>> CommitteeStore<N, C> {
pub fn insert(&self, next_height: u32, committee: Committee<N>) -> Result<()> {
self.storage.insert(next_height, committee)
}
pub fn remove(&self, height: u32) -> Result<()> {
self.storage.remove(height)
}
}
impl<N: Network, C: CommitteeStorage<N>> CommitteeStore<N, C> {
pub fn current_round(&self) -> Result<u64> {
self.storage.current_round()
}
pub fn current_height(&self) -> Result<u32> {
self.storage.current_height()
}
pub fn current_committee(&self) -> Result<Committee<N>> {
self.storage.current_committee()
}
pub fn get_height_for_round(&self, round: u64) -> Result<Option<u32>> {
self.storage.get_height_for_round(round)
}
pub fn get_committee(&self, height: u32) -> Result<Option<Committee<N>>> {
self.storage.get_committee(height)
}
pub fn get_committee_for_round(&self, round: u64) -> Result<Option<Committee<N>>> {
self.storage.get_committee_for_round(round)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::memory::CommitteeMemory;
type CurrentNetwork = console::network::MainnetV0;
#[test]
fn test_insert_get_remove() {
let rng = &mut TestRng::default();
let committee_0 = ledger_committee::test_helpers::sample_committee_for_round(0, rng);
let store = CommitteeStore::<CurrentNetwork, CommitteeMemory<_>>::open(None).unwrap();
assert!(store.current_round().is_err());
assert!(store.current_height().is_err());
assert!(store.current_committee().is_err());
assert_eq!(store.get_height_for_round(rng.gen()).unwrap(), None);
assert_eq!(store.get_committee(rng.gen()).unwrap(), None);
assert_eq!(store.get_committee_for_round(rng.gen()).unwrap(), None);
store.insert(0, committee_0.clone()).unwrap();
assert_eq!(store.current_round().unwrap(), 0);
assert_eq!(store.current_height().unwrap(), 0);
assert_eq!(store.current_committee().unwrap(), committee_0);
assert_eq!(store.get_height_for_round(0).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(1).unwrap(), None);
assert_eq!(store.get_height_for_round(2).unwrap(), None);
assert_eq!(store.get_height_for_round(3).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee(1).unwrap(), None);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee_for_round(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(1).unwrap(), None);
assert_eq!(store.get_committee_for_round(2).unwrap(), None);
assert_eq!(store.get_committee_for_round(3).unwrap(), None);
let committee_1 = ledger_committee::test_helpers::sample_committee_for_round(5, rng);
store.insert(1, committee_1.clone()).unwrap();
assert_eq!(store.current_round().unwrap(), 5);
assert_eq!(store.current_height().unwrap(), 1);
assert_eq!(store.current_committee().unwrap(), committee_1);
assert_eq!(store.get_height_for_round(0).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(1).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(2).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(3).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(4).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(5).unwrap().unwrap(), 1);
assert_eq!(store.get_height_for_round(6).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee(1).unwrap().unwrap(), committee_1);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(1).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(2).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(3).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(4).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(5).unwrap().unwrap(), committee_1);
assert_eq!(store.get_committee_for_round(6).unwrap(), None);
assert!(store.remove(2).is_err());
store.remove(1).unwrap();
assert!(store.remove(1).is_err());
assert_eq!(store.current_round().unwrap(), 4);
assert_eq!(store.current_height().unwrap(), 0);
assert_eq!(store.current_committee().unwrap(), committee_0);
assert_eq!(store.get_height_for_round(0).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(1).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(2).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(3).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(4).unwrap().unwrap(), 0);
assert_eq!(store.get_height_for_round(5).unwrap(), None);
assert_eq!(store.get_height_for_round(6).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee(1).unwrap(), None);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(0).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(1).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(2).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(3).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(4).unwrap().unwrap(), committee_0);
assert_eq!(store.get_committee_for_round(5).unwrap(), None);
assert_eq!(store.get_committee_for_round(6).unwrap(), None);
store.remove(0).unwrap();
assert!(store.current_round().is_err());
assert!(store.current_height().is_err());
assert!(store.current_committee().is_err());
assert_eq!(store.get_height_for_round(0).unwrap(), None);
assert_eq!(store.get_height_for_round(1).unwrap(), None);
assert_eq!(store.get_height_for_round(2).unwrap(), None);
assert_eq!(store.get_height_for_round(3).unwrap(), None);
assert_eq!(store.get_height_for_round(4).unwrap(), None);
assert_eq!(store.get_height_for_round(5).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap(), None);
assert_eq!(store.get_committee(1).unwrap(), None);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(0).unwrap(), None);
assert_eq!(store.get_committee_for_round(1).unwrap(), None);
assert_eq!(store.get_committee_for_round(2).unwrap(), None);
assert_eq!(store.get_committee_for_round(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(4).unwrap(), None);
assert_eq!(store.get_committee_for_round(5).unwrap(), None);
}
#[test]
fn test_remove_hole() {
let rng = &mut TestRng::default();
let committee_0 = ledger_committee::test_helpers::sample_committee_for_round(0, rng);
let store = CommitteeStore::<CurrentNetwork, CommitteeMemory<_>>::open(None).unwrap();
assert!(store.current_round().is_err());
assert!(store.current_height().is_err());
assert!(store.current_committee().is_err());
store.insert(0, committee_0.clone()).unwrap();
assert_eq!(store.current_round().unwrap(), 0);
assert_eq!(store.current_height().unwrap(), 0);
assert_eq!(store.current_committee().unwrap(), committee_0);
let committee_1 = ledger_committee::test_helpers::sample_committee_for_round(5, rng);
store.insert(1, committee_1.clone()).unwrap();
assert_eq!(store.current_round().unwrap(), 5);
assert_eq!(store.current_height().unwrap(), 1);
assert_eq!(store.current_committee().unwrap(), committee_1);
store.remove(0).unwrap();
assert_eq!(store.current_round().unwrap(), 5);
assert_eq!(store.current_height().unwrap(), 1);
assert_eq!(store.current_committee().unwrap(), committee_1);
assert_eq!(store.get_height_for_round(1).unwrap(), None);
assert_eq!(store.get_height_for_round(2).unwrap(), None);
assert_eq!(store.get_height_for_round(3).unwrap(), None);
assert_eq!(store.get_height_for_round(4).unwrap(), None);
assert_eq!(store.get_height_for_round(5).unwrap().unwrap(), 1);
assert_eq!(store.get_height_for_round(6).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap(), None);
assert_eq!(store.get_committee(1).unwrap().unwrap(), committee_1);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(1).unwrap(), None);
assert_eq!(store.get_committee_for_round(2).unwrap(), None);
assert_eq!(store.get_committee_for_round(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(4).unwrap(), None);
assert_eq!(store.get_committee_for_round(5).unwrap().unwrap(), committee_1);
assert_eq!(store.get_committee_for_round(6).unwrap(), None);
store.remove(1).unwrap();
assert!(store.current_round().is_err());
assert!(store.current_height().is_err());
assert!(store.current_committee().is_err());
assert_eq!(store.get_height_for_round(1).unwrap(), None);
assert_eq!(store.get_height_for_round(2).unwrap(), None);
assert_eq!(store.get_height_for_round(3).unwrap(), None);
assert_eq!(store.get_height_for_round(4).unwrap(), None);
assert_eq!(store.get_height_for_round(5).unwrap(), None);
assert_eq!(store.get_committee(0).unwrap(), None);
assert_eq!(store.get_committee(1).unwrap(), None);
assert_eq!(store.get_committee(2).unwrap(), None);
assert_eq!(store.get_committee(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(1).unwrap(), None);
assert_eq!(store.get_committee_for_round(2).unwrap(), None);
assert_eq!(store.get_committee_for_round(3).unwrap(), None);
assert_eq!(store.get_committee_for_round(4).unwrap(), None);
assert_eq!(store.get_committee_for_round(5).unwrap(), None);
}
}