diff --git a/binaries/cuprated/src/rpc/handlers/helper.rs b/binaries/cuprated/src/rpc/handlers/helper.rs index 5b005f8..6d78d6c 100644 --- a/binaries/cuprated/src/rpc/handlers/helper.rs +++ b/binaries/cuprated/src/rpc/handlers/helper.rs @@ -16,6 +16,7 @@ use cuprate_rpc_types::{ misc::BlockHeader, }; use cuprate_types::HardFork; +use monero_serai::transaction::Timelock; use crate::rpc::{ service::{blockchain, blockchain_context}, @@ -170,6 +171,23 @@ pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [ Ok((height, hash)) } +/// Returns `true` if the `timelock` is unlocked. +pub(super) async fn timelock_is_unlocked( + state: &mut CupratedRpcHandler, + timelock: Timelock, +) -> Result { + let unlocked = match timelock { + Timelock::None => true, + Timelock::Block(height) => { + let (top_height, _) = top_height(state).await?; + top_height > usize_to_u64(height) + } + Timelock::Time(timestamp) => cuprate_helper::time::current_unix_timestamp() > timestamp, + }; + + Ok(unlocked) +} + /// TODO: impl bootstrap pub const fn response_base(is_bootstrap: bool) -> ResponseBase { if is_bootstrap { diff --git a/binaries/cuprated/src/rpc/handlers/json_rpc.rs b/binaries/cuprated/src/rpc/handlers/json_rpc.rs index 99e425a..ad6ff94 100644 --- a/binaries/cuprated/src/rpc/handlers/json_rpc.rs +++ b/binaries/cuprated/src/rpc/handlers/json_rpc.rs @@ -467,8 +467,10 @@ async fn get_info( blockchain::alt_chain_count(&mut state.blockchain_read).await? }; - let block_weight_limit = usize_to_u64(c.effective_median_weight); - let block_weight_median = usize_to_u64(c.median_weight_for_block_reward); + // TODO: these values are incorrectly calculated in `monerod` and copied in `cuprated` + // + let block_weight_limit = usize_to_u64(c.effective_median_weight * 2); + let block_weight_median = usize_to_u64(c.effective_median_weight); let block_size_limit = block_weight_limit; let block_size_median = block_weight_median; @@ -969,9 +971,11 @@ async fn get_miner_data( let major_version = c.current_hf.as_u8(); let height = usize_to_u64(c.chain_height); let prev_id = Hex(c.top_hash); - let seed_hash = Hex(c.top_hash); + // TODO: RX seed hash . + let seed_hash = Hex([0; 32]); let difficulty = c.next_difficulty.hex_prefix(); - let median_weight = usize_to_u64(c.median_weight_for_block_reward); + // TODO: + let median_weight = usize_to_u64(c.effective_median_weight); let already_generated_coins = c.already_generated_coins; let tx_backlog = txpool::backlog(&mut state.txpool_read) .await? @@ -1061,7 +1065,7 @@ async fn add_aux_pow( tokio::time::sleep(Duration::from_millis(100)).await; } - tokio::task::spawn_blocking(|| add_aux_pow_inner(state, request)).await? + cuprate_helper::asynch::rayon_spawn_async(|| add_aux_pow_inner(state, request)).await } /// diff --git a/binaries/cuprated/src/rpc/handlers/shared.rs b/binaries/cuprated/src/rpc/handlers/shared.rs index 2f19dc8..08d8134 100644 --- a/binaries/cuprated/src/rpc/handlers/shared.rs +++ b/binaries/cuprated/src/rpc/handlers/shared.rs @@ -45,34 +45,22 @@ pub(super) async fn get_outs( return Err(anyhow!("Too many outs requested")); } - let outputs = { - let mut outputs = HashMap::>::with_capacity(request.outputs.len()); + let outputs = blockchain::outputs_vec(&mut state.blockchain_read, request.outputs).await?; + let mut outs = Vec::::with_capacity(outputs.len()); - for out in request.outputs { - outputs - .entry(out.amount) - .and_modify(|set| { - set.insert(out.index); - }) - .or_insert_with(|| HashSet::from([out.index])); - } - - outputs - }; - - let outs = blockchain::outputs(&mut state.blockchain_read, outputs) - .await? - .into_iter() - .flat_map(|(amount, index_map)| { - index_map.into_values().map(|out| OutKeyBin { + for (_, index_vec) in outputs { + for (_, out) in index_vec { + let out_key = OutKeyBin { key: out.key.map_or([0; 32], |e| e.compress().0), mask: out.commitment.compress().0, - unlocked: matches!(out.time_lock, Timelock::None), + unlocked: helper::timelock_is_unlocked(&mut state, out.time_lock).await?, height: usize_to_u64(out.height), txid: if request.get_txid { out.txid } else { [0; 32] }, - }) - }) - .collect::>(); + }; + + outs.push(out_key); + } + } Ok(GetOutsResponse { base: helper::access_response_base(false), diff --git a/binaries/cuprated/src/rpc/service/blockchain.rs b/binaries/cuprated/src/rpc/service/blockchain.rs index 46d8415..0912ee7 100644 --- a/binaries/cuprated/src/rpc/service/blockchain.rs +++ b/binaries/cuprated/src/rpc/service/blockchain.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Error; +use cuprate_rpc_types::misc::GetOutputsOut; use monero_serai::block::Block; use tower::{Service, ServiceExt}; @@ -203,6 +204,28 @@ pub async fn outputs( Ok(outputs) } +/// [`BlockchainReadRequest::OutputsVec`] +pub async fn outputs_vec( + blockchain_read: &mut BlockchainReadHandle, + outputs: Vec, +) -> Result)>, Error> { + let outputs = outputs + .into_iter() + .map(|output| (output.amount, output.index)) + .collect(); + + let BlockchainResponse::OutputsVec(outputs) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::OutputsVec(outputs)) + .await? + else { + unreachable!(); + }; + + Ok(outputs) +} + /// [`BlockchainReadRequest::NumberOutputsWithAmount`] pub async fn number_outputs_with_amount( blockchain_read: &mut BlockchainReadHandle, diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 387e2e2..41fe982 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -119,8 +119,10 @@ fn map_request( R::ChainHeight => chain_height(env), R::GeneratedCoins(height) => generated_coins(env, height), R::Outputs(map) => outputs(env, map), + R::OutputsVec(vec) => outputs_vec(env, vec), R::NumberOutputsWithAmount(vec) => number_outputs_with_amount(env, vec), R::KeyImagesSpent(set) => key_images_spent(env, set), + R::KeyImagesSpentVec(set) => key_images_spent_vec(env, set), R::CompactChainHistory => compact_chain_history(env), R::NextChainEntry(block_hashes, amount) => next_chain_entry(env, &block_hashes, amount), R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids), @@ -487,6 +489,12 @@ fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> Ok(BlockchainResponse::Outputs(map)) } +/// [`BlockchainReadRequest::OutputsVec`]. +#[inline] +fn outputs_vec(env: &ConcreteEnv, outputs: Vec<(Amount, AmountIndex)>) -> ResponseResult { + Ok(BlockchainResponse::OutputsVec(todo!())) +} + /// [`BlockchainReadRequest::NumberOutputsWithAmount`]. #[inline] fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> ResponseResult { @@ -538,7 +546,43 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon /// [`BlockchainReadRequest::KeyImagesSpent`]. #[inline] -fn key_images_spent(env: &ConcreteEnv, key_images: Vec) -> ResponseResult { +fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> ResponseResult { + // Prepare tx/tables in `ThreadLocal`. + let env_inner = env.env_inner(); + let tx_ro = thread_local(env); + let tables = thread_local(env); + + // Key image check function. + let key_image_exists = |key_image| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + key_image_exists(&key_image, tables.key_images()) + }; + + // FIXME: + // Create/use `enum cuprate_types::Exist { Does, DoesNot }` + // or similar instead of `bool` for clarity. + // + // + // Collect results using `rayon`. + match key_images + .into_par_iter() + .map(key_image_exists) + // If the result is either: + // `Ok(true)` => a key image was found, return early + // `Err` => an error was found, return early + // + // Else, `Ok(false)` will continue the iterator. + .find_any(|result| !matches!(result, Ok(false))) + { + None | Some(Ok(false)) => Ok(BlockchainResponse::KeyImagesSpent(false)), // Key image was NOT found. + Some(Ok(true)) => Ok(BlockchainResponse::KeyImagesSpent(true)), // Key image was found. + Some(Err(e)) => Err(e), // A database error occurred. + } +} + +/// [`BlockchainReadRequest::KeyImagesSpentVec`]. +fn key_images_spent_vec(env: &ConcreteEnv, key_images: Vec) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); let tx_ro = thread_local(env); @@ -552,7 +596,7 @@ fn key_images_spent(env: &ConcreteEnv, key_images: Vec) -> ResponseRes }; // Collect results using `rayon`. - Ok(BlockchainResponse::KeyImagesSpent( + Ok(BlockchainResponse::KeyImagesSpentVec( key_images .into_par_iter() .map(key_image_exists) diff --git a/storage/blockchain/src/service/tests.rs b/storage/blockchain/src/service/tests.rs index 063b62d..8dc49e4 100644 --- a/storage/blockchain/src/service/tests.rs +++ b/storage/blockchain/src/service/tests.rs @@ -180,7 +180,7 @@ async fn test_template( // Contains a fake non-spent key-image. let ki_req = vec![[0; 32]]; - let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(vec![false])); + let ki_resp = Ok(BlockchainResponse::KeyImagesSpentVec(vec![false])); //----------------------------------------------------------------------- Assert expected response // Assert read requests lead to the expected responses. @@ -218,7 +218,7 @@ async fn test_template( BlockchainReadRequest::NumberOutputsWithAmount(num_req), num_resp, ), - (BlockchainReadRequest::KeyImagesSpent(ki_req), ki_resp), + (BlockchainReadRequest::KeyImagesSpentVec(ki_req), ki_resp), ] { let response = reader.clone().oneshot(request).await; println!("response: {response:#?}, expected_response: {expected_response:#?}"); @@ -232,12 +232,12 @@ async fn test_template( // Assert each key image we inserted comes back as "spent". for key_image in tables.key_images_iter().keys().unwrap() { let key_image = key_image.unwrap(); - let request = BlockchainReadRequest::KeyImagesSpent(vec![key_image]); + let request = BlockchainReadRequest::KeyImagesSpentVec(vec![key_image]); let response = reader.clone().oneshot(request).await; println!("response: {response:#?}, key_image: {key_image:#?}"); assert_eq!( response.unwrap(), - BlockchainResponse::KeyImagesSpent(vec![true]) + BlockchainResponse::KeyImagesSpentVec(vec![true]) ); } diff --git a/types/types/src/blockchain.rs b/types/types/src/blockchain.rs index 2556dac..20df823 100644 --- a/types/types/src/blockchain.rs +++ b/types/types/src/blockchain.rs @@ -96,6 +96,13 @@ pub enum BlockchainReadRequest { /// RCT output indices. Outputs(HashMap>), + /// This is the same as [`BlockchainReadRequest::Outputs`] but with a [`Vec`] container. + /// + /// The input [`Vec`] values are `(amount, amount_index)`. + /// + /// The response will be in the same order as the request. + OutputsVec(Vec<(u64, u64)>), + /// Request the amount of outputs with a certain amount. /// /// The input is a list of output amounts. @@ -104,7 +111,12 @@ pub enum BlockchainReadRequest { /// Check the spend status of key images. /// /// Input is a set of key images. - KeyImagesSpent(Vec<[u8; 32]>), + KeyImagesSpent(HashSet<[u8; 32]>), + + /// Same as [`BlockchainReadRequest::KeyImagesSpent`] but with a [`Vec`]. + /// + /// The response will be in the same order as the request. + KeyImagesSpentVec(Vec<[u8; 32]>), /// A request for the compact chain history. CompactChainHistory, @@ -284,6 +296,9 @@ pub enum BlockchainResponse { /// associated with their amount and amount index. Outputs(HashMap>), + /// Response to [`BlockchainReadRequest::OutputsVec`]. + OutputsVec(Vec<(u64, Vec<(u64, OutputOnChain)>)>), + /// Response to [`BlockchainReadRequest::NumberOutputsWithAmount`]. /// /// Inner value is a `HashMap` of all the outputs requested where: @@ -293,11 +308,19 @@ pub enum BlockchainResponse { /// Response to [`BlockchainReadRequest::KeyImagesSpent`]. /// + /// The inner value is `true` if _any_ of the key images + /// were spent (existed in the database already). + /// + /// The inner value is `false` if _none_ of the key images were spent. + KeyImagesSpent(bool), + + /// Response to [`BlockchainReadRequest::KeyImagesSpentVec`]. + /// /// Inner value is a `Vec` the same length as the input. /// /// The index of each entry corresponds with the request. /// `true` means that the key image was spent. - KeyImagesSpent(Vec), + KeyImagesSpentVec(Vec), /// Response to [`BlockchainReadRequest::CompactChainHistory`]. CompactChainHistory {