use crate::rpc::EthereumRpc;
#[derive(Clone, Debug)]
pub struct FinalityConfig {
pub confirmation_depth: u64,
pub prefer_checkpoint_finality: bool,
}
impl Default for FinalityConfig {
fn default() -> Self {
Self {
confirmation_depth: 15,
prefer_checkpoint_finality: true,
}
}
}
pub struct FinalityChecker {
config: FinalityConfig,
}
impl FinalityChecker {
pub fn new(config: FinalityConfig) -> Self {
Self { config }
}
pub fn is_finalized(
&self,
block_number: u64,
rpc: &dyn EthereumRpc,
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
if self.config.prefer_checkpoint_finality {
if let Some(finalized) = rpc.get_finalized_block_number()? {
if block_number <= finalized {
return Ok(true);
}
}
}
let current = rpc.block_number()?;
let confirmations = current.saturating_sub(block_number);
Ok(confirmations >= self.config.confirmation_depth)
}
pub fn get_confirmations(
&self,
block_number: u64,
rpc: &dyn EthereumRpc,
) -> Result<u64, Box<dyn std::error::Error + Send + Sync>> {
let current = rpc.block_number()?;
Ok(current.saturating_sub(block_number))
}
pub fn get_finality_info(
&self,
block_number: u64,
rpc: &dyn EthereumRpc,
) -> Result<FinalityInfo, Box<dyn std::error::Error + Send + Sync>> {
let current = rpc.block_number()?;
let confirmations = current.saturating_sub(block_number);
let checkpoint_finalized = rpc.get_finalized_block_number()?.map(|f| block_number <= f);
let is_final = checkpoint_finalized.unwrap_or(false)
|| confirmations >= self.config.confirmation_depth;
Ok(FinalityInfo {
current_block: current,
block_number,
confirmations,
required_depth: self.config.confirmation_depth,
checkpoint_finalized: checkpoint_finalized.unwrap_or(false),
is_final,
})
}
}
#[derive(Clone, Debug)]
pub struct FinalityInfo {
pub current_block: u64,
pub block_number: u64,
pub confirmations: u64,
pub required_depth: u64,
pub checkpoint_finalized: bool,
pub is_final: bool,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::MockEthereumRpc;
fn test_checker() -> FinalityChecker {
FinalityChecker::new(FinalityConfig::default())
}
#[test]
fn test_confirmations_below_depth() {
let rpc = MockEthereumRpc::new(100);
let checker = test_checker();
assert!(!checker.is_finalized(95, &rpc).unwrap());
}
#[test]
fn test_confirmations_at_depth() {
let rpc = MockEthereumRpc::new(100);
let mut config = FinalityConfig::default();
config.confirmation_depth = 5;
let checker = FinalityChecker::new(config);
assert!(checker.is_finalized(95, &rpc).unwrap());
}
#[test]
fn test_confirmations_above_depth() {
let rpc = MockEthereumRpc::new(100);
let checker = test_checker();
assert!(checker.is_finalized(80, &rpc).unwrap());
}
#[test]
fn test_checkpoint_finality() {
let rpc = MockEthereumRpc::new(1000);
let checker = test_checker();
assert!(checker.is_finalized(900, &rpc).unwrap());
assert!(!checker.is_finalized(990, &rpc).unwrap());
}
#[test]
fn test_get_confirmations() {
let rpc = MockEthereumRpc::new(100);
let checker = test_checker();
assert_eq!(checker.get_confirmations(90, &rpc).unwrap(), 10);
}
#[test]
fn test_finality_info() {
let rpc = MockEthereumRpc::new(100);
let checker = test_checker();
let info = checker.get_finality_info(90, &rpc).unwrap();
assert_eq!(info.current_block, 100);
assert_eq!(info.block_number, 90);
assert_eq!(info.confirmations, 10);
assert_eq!(info.required_depth, 15);
assert!(!info.is_final); }
}