diff --git a/processor/monero/src/lib.rs b/processor/monero/src/lib.rs index 46ce16d3..1cde1414 100644 --- a/processor/monero/src/lib.rs +++ b/processor/monero/src/lib.rs @@ -54,64 +54,6 @@ impl SignableTransactionTrait for SignableTransaction { } } -#[async_trait] -impl BlockTrait for Block { - type Id = [u8; 32]; - fn id(&self) -> Self::Id { - self.hash() - } - - fn parent(&self) -> Self::Id { - self.header.previous - } - - async fn time(&self, rpc: &Monero) -> u64 { - // Constant from Monero - const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: usize = 60; - - // If Monero doesn't have enough blocks to build a window, it doesn't define a network time - if (self.number().unwrap() + 1) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { - // Use the block number as the time - return u64::try_from(self.number().unwrap()).unwrap(); - } - - let mut timestamps = vec![self.header.timestamp]; - let mut parent = self.parent(); - while timestamps.len() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { - let mut parent_block; - while { - parent_block = rpc.rpc.get_block(parent).await; - parent_block.is_err() - } { - log::error!("couldn't get parent block when trying to get block time: {parent_block:?}"); - sleep(Duration::from_secs(5)).await; - } - let parent_block = parent_block.unwrap(); - timestamps.push(parent_block.header.timestamp); - parent = parent_block.parent(); - - if parent_block.number().unwrap() == 0 { - break; - } - } - timestamps.sort(); - - // Because 60 has two medians, Monero's epee picks the in-between value, calculated by the - // following formula (from the "get_mid" function) - let n = timestamps.len() / 2; - let a = timestamps[n - 1]; - let b = timestamps[n]; - #[rustfmt::skip] // Enables Ctrl+F'ing for everything after the `= ` - let res = (a/2) + (b/2) + ((a - 2*(a/2)) + (b - 2*(b/2)))/2; - // Technically, res may be 1 if all prior blocks had a timestamp by 0, which would break - // monotonicity with our above definition of height as time - // Monero also solely requires the block's time not be less than the median, it doesn't ensure - // it advances the median forward - // Ensure monotonicity despite both these issues by adding the block number to the median time - res + u64::try_from(self.number().unwrap()).unwrap() - } -} - enum MakeSignableTransactionResult { Fee(u64), SignableTransaction(MSignableTransaction), @@ -330,9 +272,6 @@ impl Network for Monero { const MAX_OUTPUTS: usize = 16; - // 0.01 XMR - const DUST: u64 = 10000000000; - // TODO const COST_TO_AGGREGATE: u64 = 0; diff --git a/processor/monero/src/primitives/block.rs b/processor/monero/src/primitives/block.rs index 62715f8c..130e5ac8 100644 --- a/processor/monero/src/primitives/block.rs +++ b/processor/monero/src/primitives/block.rs @@ -5,8 +5,8 @@ use zeroize::Zeroizing; use ciphersuite::{Ciphersuite, Ed25519}; use monero_wallet::{ - block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, - ViewPairError, GuaranteedViewPair, ScanError, GuaranteedScanner, + block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, ViewPairError, + GuaranteedViewPair, ScanError, GuaranteedScanner, }; use serai_client::networks::monero::Address; @@ -58,8 +58,12 @@ impl primitives::Block for Block { scanner.register_subaddress(FORWARDED_SUBADDRESS.unwrap()); match scanner.scan(self.0.clone()) { Ok(outputs) => outputs.not_additionally_locked().into_iter().map(Output).collect(), - Err(ScanError::UnsupportedProtocol(version)) => panic!("Monero unexpectedly hard-forked (version {version})"), - Err(ScanError::InvalidScannableBlock(reason)) => panic!("fetched an invalid scannable block from the RPC: {reason}"), + Err(ScanError::UnsupportedProtocol(version)) => { + panic!("Monero unexpectedly hard-forked (version {version})") + } + Err(ScanError::InvalidScannableBlock(reason)) => { + panic!("fetched an invalid scannable block from the RPC: {reason}") + } } } @@ -76,10 +80,7 @@ impl primitives::Block for Block { for (hash, tx) in self.0.block.transactions.iter().zip(&self.0.transactions) { if let Some(eventuality) = eventualities.active_eventualities.get(&tx.prefix().extra) { if eventuality.eventuality.matches(tx) { - res.insert( - *hash, - eventualities.active_eventualities.remove(&tx.prefix().extra).unwrap(), - ); + res.insert(*hash, eventualities.active_eventualities.remove(&tx.prefix().extra).unwrap()); } } } diff --git a/processor/monero/src/rpc.rs b/processor/monero/src/rpc.rs index d826802b..9244b23f 100644 --- a/processor/monero/src/rpc.rs +++ b/processor/monero/src/rpc.rs @@ -54,7 +54,38 @@ impl ScannerFeed for Rpc { &self, number: u64, ) -> impl Send + Future> { - async move { todo!("TODO") } + async move { + // Constant from Monero + const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60; + + // If Monero doesn't have enough blocks to build a window, it doesn't define a network time + if (number + 1) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { + return Ok(0); + } + + // Fetch all the timestamps within the window + let block_for_time_of = self.rpc.get_block_by_number(number.try_into().unwrap()).await?; + let mut timestamps = vec![block_for_time_of.header.timestamp]; + let mut parent = block_for_time_of.header.previous; + for _ in 1 .. BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW { + let parent_block = self.rpc.get_block(parent).await?; + timestamps.push(parent_block.header.timestamp); + parent = parent_block.header.previous; + } + timestamps.sort(); + + // Because there are two timestamps equidistance from the ends, Monero's epee picks the + // in-between value, calculated by the following formula (from the "get_mid" function) + let n = timestamps.len() / 2; + let a = timestamps[n - 1]; + let b = timestamps[n]; + #[rustfmt::skip] // Enables Ctrl+F'ing for everything after the `= ` + let res = (a/2) + (b/2) + ((a - 2*(a/2)) + (b - 2*(b/2)))/2; + + // Monero does check that the new block's time is greater than the median, causing the median + // to be monotonic + Ok(res) + } } fn unchecked_block_header_by_number( @@ -66,17 +97,21 @@ impl ScannerFeed for Rpc { async move { Ok(BlockHeader(self.rpc.get_block_by_number(number.try_into().unwrap()).await?)) } } + #[rustfmt::skip] // It wants to improperly format the `async move` to a single line fn unchecked_block_by_number( &self, number: u64, ) -> impl Send + Future> { - async move { todo!("TODO") } + async move { + Ok(Block(self.rpc.get_scannable_block_by_number(number.try_into().unwrap()).await?)) + } } fn dust(coin: Coin) -> Amount { assert_eq!(coin, Coin::Monero); - todo!("TODO") + // 0.01 XMR + Amount(10_000_000_000) } fn cost_to_aggregate(