use chrono::prelude::*; use lazy_static::lazy_static; use std::convert::Into; use std::sync::RwLock; // TODO review whether a genesis block has a block height of 1 or 0 pub enum BlockchainSupported { BTC, BSV, XMR, ETH, } type BlockHeight = i64; #[derive(Clone, Copy)] pub struct BlockchainInfo { block_period: u64, // In seconds latest_block_height: BlockHeight, latest_block_time: DateTime, } lazy_static! { static ref BSV_BI: RwLock = RwLock::new(BlockchainInfo { block_period: 10 * 60, // 10 minutes, latest_block_height: 852792, latest_block_time: Utc.with_ymd_and_hms(2024, 7, 11, 19, 53, 7).unwrap(), }); static ref BTC_BI: RwLock = RwLock::new(BlockchainInfo { block_period: 10 * 60, // 10 minutes, latest_block_height: 851724, latest_block_time: Utc.with_ymd_and_hms(2024, 7, 11, 19, 38, 6).unwrap(), }); static ref XMR_BI: RwLock = RwLock::new(BlockchainInfo { block_period: 2 * 60, // 2 minutes, latest_block_height: 3190705, latest_block_time: Utc.with_ymd_and_hms(2024, 7, 11, 19, 54, 11).unwrap(), }); static ref ETH_BI: RwLock = RwLock::new(BlockchainInfo { block_period: 12, latest_block_height: 20285486, latest_block_time: Utc.with_ymd_and_hms(2024, 7, 11, 19, 53, 59).unwrap(), }); } impl From for BlockchainInfo { fn from(val: BlockchainSupported) -> Self { match val { BlockchainSupported::BTC => *BTC_BI.read().unwrap(), BlockchainSupported::BSV => *BSV_BI.read().unwrap(), BlockchainSupported::XMR => *XMR_BI.read().unwrap(), BlockchainSupported::ETH => *ETH_BI.read().unwrap(), } } } pub fn time_to_blockheight_specific_chain( time: DateTime, blockchain: BlockchainSupported, ) -> BlockHeight { let bi: BlockchainInfo = blockchain.into(); time_to_blockheight(time, bi) } pub fn time_to_blockheight(time: DateTime, bi: BlockchainInfo) -> BlockHeight { let time_difference = (time - bi.latest_block_time).num_seconds(); let num_blocks = time_difference / bi.block_period as i64; num_blocks + bi.latest_block_height } #[cfg(test)] mod tests { use super::*; use chrono::Duration; #[test] fn one_block_time_away() { let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 10, 0).unwrap(); let wanted_future_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 1).unwrap(); let blockchain_info = BlockchainInfo { block_period: 60 * 10, // 10 minutes latest_block_height: 1000, latest_block_time, }; let result = time_to_blockheight(wanted_future_block_time, blockchain_info); assert_eq!(result, 1001); } #[test] fn one_block_time_previous() { let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 1).unwrap(); let wanted_future_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 10, 0).unwrap(); let blockchain_info = BlockchainInfo { block_period: 60 * 10, // 10 minutes latest_block_height: 1000, latest_block_time, }; let result = time_to_blockheight(wanted_future_block_time, blockchain_info); assert_eq!(result, 999); } #[test] fn negative_block_time() { let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 30, 0).unwrap(); let wanted_negative_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 10, 0).unwrap(); let blockchain_info = BlockchainInfo { block_period: 60 * 10, // 10 minutes latest_block_height: 1, latest_block_time, }; let result = time_to_blockheight(wanted_negative_block_time, blockchain_info); assert_eq!(result, -1); } #[test] fn a_next_block_on_some_blockchains() { // Doesn't feel like an overly good test - but I can't think of anyother way that's "dynamic" // to me manually updating the times above let bi: BlockchainInfo = BlockchainSupported::BTC.into(); let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 1); let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BTC); assert_eq!(bh, bi.latest_block_height + 1); let bi: BlockchainInfo = BlockchainSupported::BSV.into(); let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 2); let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BSV); assert_eq!(bh, bi.latest_block_height + 1); let bi: BlockchainInfo = BlockchainSupported::XMR.into(); let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 3); let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR); assert_eq!(bh, bi.latest_block_height + 1); let bi: BlockchainInfo = BlockchainSupported::ETH.into(); let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 4); let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::ETH); assert_eq!(bh, bi.latest_block_height + 1); let bi: BlockchainInfo = BlockchainSupported::ETH.into(); let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 5); let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR); assert_ne!(bh, bi.latest_block_height + 1); } }