From 5b227dbe4fe67f1f96315c6364d7300a65a13b17 Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Sun, 28 Apr 2024 15:55:18 -0400 Subject: [PATCH] read: rct outputs --- database/src/free.rs | 44 ++++++++++++++++++++++++-- database/src/service/read.rs | 60 ++++++++++++++++++++++++++---------- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/database/src/free.rs b/database/src/free.rs index 8982984f..0158794c 100644 --- a/database/src/free.rs +++ b/database/src/free.rs @@ -5,16 +5,17 @@ use cuprate_helper::map::u64_to_timelock; use cuprate_types::OutputOnChain; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, Scalar}; -use monero_serai::{transaction::Timelock, H}; +use monero_serai::{transaction::Timelock, Commitment, H}; use crate::{ tables::{Tables, TxUnlockTime}, - types::{Amount, Output, OutputFlags}, + types::{Amount, Output, OutputFlags, RctOutput}, DatabaseRo, RuntimeError, }; //---------------------------------------------------------------------------------------------------- Free functions -/// Map a [`crate::types::Output`] to a [`cuprate_types::OutputOnChain`]. +/// Map an [`Output`] to a [`cuprate_types::OutputOnChain`]. +#[inline] pub(crate) fn output_to_output_on_chain( output: &Output, amount: Amount, @@ -45,6 +46,43 @@ pub(crate) fn output_to_output_on_chain( }) } +/// Map an [`RctOutput`] to a [`cuprate_types::OutputOnChain`]. +/// +/// # Panics +/// This function will panic if `rct_output`'s `commitment` fails to decompress into a valid [`EdwardsPoint`]. +#[inline] +pub(crate) fn rct_output_to_output_on_chain( + rct_output: &RctOutput, + amount: Amount, + table_tx_unlock_time: &impl DatabaseRo, +) -> Result { + // INVARIANT: Commitments stored are valid when stored by the database. + let commitment = CompressedEdwardsY::from_slice(&rct_output.commitment) + .unwrap() + .decompress() + .unwrap(); + + let time_lock = if rct_output + .output_flags + .contains(OutputFlags::NON_ZERO_UNLOCK_TIME) + { + u64_to_timelock(table_tx_unlock_time.get(&rct_output.tx_idx)?) + } else { + Timelock::None + }; + + let key = CompressedEdwardsY::from_slice(&rct_output.key) + .map(|y| y.decompress()) + .unwrap_or(None); + + Ok(OutputOnChain { + height: u64::from(rct_output.height), + time_lock, + key, + commitment, + }) +} + //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] mod test { diff --git a/database/src/service/read.rs b/database/src/service/read.rs index d7bc5b75..f20516bf 100644 --- a/database/src/service/read.rs +++ b/database/src/service/read.rs @@ -29,12 +29,15 @@ use crate::{ config::ReaderThreads, constants::DATABASE_CORRUPT_MSG, error::RuntimeError, - free::output_to_output_on_chain, + free::{ + output_to_output_on_chain, output_v1_or_v2_to_output_on_chain, + rct_output_to_output_on_chain, + }, ops::{ block::{get_block_extended_header_from_height, get_block_info}, blockchain::{cumulative_generated_coins, top_block_height}, key_image::key_image_exists, - output::get_output, + output::{get_output, get_rct_output}, }, service::types::{ResponseReceiver, ResponseResult, ResponseSender}, tables::{BlockHeights, BlockInfos, KeyImages, NumOutputs, Outputs, Tables}, @@ -431,15 +434,26 @@ fn outputs(env: &ConcreteEnv, map: HashMap>) -> Res let inner_map = |amount, amount_index| { let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); - let pre_rct_output_id = PreRctOutputId { - amount, - amount_index, - }; + if amount == 0 { + // v2 transactions. + let rct_output = get_rct_output(&amount_index, tables.rct_outputs())?; + let output_on_chain = + rct_output_to_output_on_chain(&rct_output, amount, tables.tx_unlock_time())?; - let output = get_output(&pre_rct_output_id, tables.outputs())?; - let output_on_chain = output_to_output_on_chain(&output, amount, tables.tx_unlock_time())?; + Ok((amount_index, output_on_chain)) + } else { + // v1 transactions. + let pre_rct_output_id = PreRctOutputId { + amount, + amount_index, + }; - Ok((amount_index, output_on_chain)) + let output = get_output(&pre_rct_output_id, tables.outputs())?; + let output_on_chain = + output_to_output_on_chain(&output, amount, tables.tx_unlock_time())?; + + Ok((amount_index, output_on_chain)) + } }; let map = map @@ -465,19 +479,31 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon let tx_ro = set_tx_ro!(env, env_inner); let tables = set_tables!(env, env_inner, tx_ro); + // Cache the amount of RCT outputs once. + let (_, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); + // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` + #[allow(clippy::cast_possible_truncation)] + let num_rct_outputs = tables.rct_outputs().len()? as usize; + let map = amounts .into_par_iter() .map(|amount| { let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); - match tables.num_outputs().get(&amount) { - // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` - #[allow(clippy::cast_possible_truncation)] - Ok(count) => Ok((amount, count as usize)), - // If we get a request for an `amount` that doesn't exist, - // we return `0` instead of an error. - Err(RuntimeError::KeyNotFound) => Ok((amount, 0)), - Err(e) => Err(e), + if amount == 0 { + // v2 transactions. + Ok((amount, num_rct_outputs)) + } else { + // v1 transactions. + match tables.num_outputs().get(&amount) { + // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` + #[allow(clippy::cast_possible_truncation)] + Ok(count) => Ok((amount, count as usize)), + // If we get a request for an `amount` that doesn't exist, + // we return `0` instead of an error. + Err(RuntimeError::KeyNotFound) => Ok((amount, 0)), + Err(e) => Err(e), + } } }) .collect::, RuntimeError>>()?;