use std::{cmp::Ordering, collections::BTreeSet};
use brk_error::Result;
use brk_types::Height;
use tracing::{debug, warn};
use vecdb::Stamp;
use super::super::{
AddrsDataVecs,
addr::AnyAddrIndexesVecs,
cohorts::{AddrCohorts, UTXOCohorts},
};
pub struct RecoveredState {
pub starting_height: Height,
}
pub(crate) fn recover_state(
height: Height,
chain_state_rollback: vecdb::Result<Stamp>,
any_addr_indexes: &mut AnyAddrIndexesVecs,
addrs_data: &mut AddrsDataVecs,
utxo_cohorts: &mut UTXOCohorts,
addr_cohorts: &mut AddrCohorts,
) -> Result<RecoveredState> {
let stamp = Stamp::from(height);
let addr_indexes_rollback = any_addr_indexes.rollback_before(stamp);
let addr_data_rollback = addrs_data.rollback_before(stamp);
let consistent_height = rollback_states(
chain_state_rollback,
addr_indexes_rollback,
addr_data_rollback,
);
if consistent_height.is_zero() {
warn!("Rollback consistency check failed: inconsistent heights");
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
if consistent_height > height {
warn!(
"Rollback failed: still at {} but target was {}, falling back to fresh start",
consistent_height, height
);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
if consistent_height != height {
debug!(
"Rollback landed at {} instead of {}, will resume from there",
consistent_height, height
);
}
debug!(
"importing UTXO cohort states at height {}",
consistent_height
);
if !utxo_cohorts.import_separate_states(consistent_height) {
warn!(
"UTXO cohort state import failed at height {}",
consistent_height
);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
debug!("UTXO cohort states imported");
debug!(
"importing addr cohort states at height {}",
consistent_height
);
if !addr_cohorts.import_separate_states(consistent_height) {
warn!(
"Addr cohort state import failed at height {}",
consistent_height
);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
}
debug!("addr cohort states imported");
Ok(RecoveredState {
starting_height: consistent_height,
})
}
pub(crate) fn reset_state(
any_addr_indexes: &mut AnyAddrIndexesVecs,
addrs_data: &mut AddrsDataVecs,
utxo_cohorts: &mut UTXOCohorts,
addr_cohorts: &mut AddrCohorts,
) -> Result<RecoveredState> {
any_addr_indexes.reset()?;
addrs_data.reset()?;
utxo_cohorts.reset_separate_state_heights();
addr_cohorts.reset_separate_state_heights();
utxo_cohorts.reset_separate_cost_basis_data()?;
addr_cohorts.reset_separate_cost_basis_data()?;
utxo_cohorts.reset_caches();
Ok(RecoveredState {
starting_height: Height::ZERO,
})
}
pub(crate) fn determine_start_mode(min_available: Height, resume_target: Height) -> StartMode {
if resume_target.is_zero() {
return StartMode::Fresh;
}
match min_available.cmp(&resume_target) {
Ordering::Greater => unreachable!("min_available > resume_target"),
Ordering::Equal => StartMode::Resume(resume_target),
Ordering::Less => StartMode::Fresh,
}
}
pub enum StartMode {
Resume(Height),
Fresh,
}
fn rollback_states(
chain_state_rollback: vecdb::Result<Stamp>,
addr_indexes_rollbacks: Result<Vec<Stamp>>,
addr_data_rollbacks: Result<[Stamp; 2]>,
) -> Height {
let mut heights: BTreeSet<Height> = BTreeSet::new();
let Ok(s) = chain_state_rollback else {
warn!("chain_state rollback failed: {:?}", chain_state_rollback);
return Height::ZERO;
};
let chain_height = Height::from(s).incremented();
debug!(
"chain_state rolled back to stamp {:?}, height {}",
s, chain_height
);
heights.insert(chain_height);
let Ok(stamps) = addr_indexes_rollbacks else {
warn!("addr_indexes rollback failed: {:?}", addr_indexes_rollbacks);
return Height::ZERO;
};
for (i, s) in stamps.iter().enumerate() {
let h = Height::from(*s).incremented();
debug!(
"addr_indexes[{}] rolled back to stamp {:?}, height {}",
i, s, h
);
heights.insert(h);
}
let Ok(stamps) = addr_data_rollbacks else {
warn!("addr_data rollback failed: {:?}", addr_data_rollbacks);
return Height::ZERO;
};
for (i, s) in stamps.iter().enumerate() {
let h = Height::from(*s).incremented();
debug!(
"addr_data[{}] rolled back to stamp {:?}, height {}",
i, s, h
);
heights.insert(h);
}
if heights.len() == 1 {
heights.pop_first().unwrap()
} else {
warn!("Rollback heights inconsistent: {:?}", heights);
Height::ZERO
}
}