From a359eed8beccc9b8dd96187d4d80ec660b28766c Mon Sep 17 00:00:00 2001 From: "hinto.janai" Date: Wed, 11 Dec 2024 20:52:40 -0500 Subject: [PATCH] `/is_key_image_spent` --- binaries/cuprated/src/rpc/helper.rs | 17 +----- binaries/cuprated/src/rpc/other.rs | 59 ++++++++++++++----- .../cuprated/src/rpc/request/blockchain.rs | 14 +++-- binaries/cuprated/src/rpc/request/txpool.rs | 23 ++++++++ consensus/src/transactions.rs | 8 ++- consensus/tests/verify_correct_txs.rs | 4 +- rpc/types/src/misc/mod.rs | 2 - rpc/types/src/other.rs | 2 +- storage/blockchain/src/service/read.rs | 31 +++------- storage/blockchain/src/service/tests.rs | 11 ++-- storage/txpool/src/service/interface.rs | 11 +++- storage/txpool/src/service/read.rs | 13 ++++ types/types/src/blockchain.rs | 19 +++--- .../types/src/rpc}/key_image_spent_status.rs | 11 +--- types/types/src/rpc/mod.rs | 2 + 15 files changed, 139 insertions(+), 88 deletions(-) rename {rpc/types/src/misc => types/types/src/rpc}/key_image_spent_status.rs (88%) diff --git a/binaries/cuprated/src/rpc/helper.rs b/binaries/cuprated/src/rpc/helper.rs index bba2120..0d79316 100644 --- a/binaries/cuprated/src/rpc/helper.rs +++ b/binaries/cuprated/src/rpc/helper.rs @@ -11,7 +11,7 @@ use cuprate_helper::{ cast::{u64_to_usize, usize_to_u64}, map::split_u128_into_low_high_bits, }; -use cuprate_rpc_types::misc::{BlockHeader, KeyImageSpentStatus}; +use cuprate_rpc_types::misc::BlockHeader; use cuprate_types::HardFork; use crate::{ @@ -166,18 +166,3 @@ pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [ let height = chain_height.saturating_sub(1); Ok((height, hash)) } - -/// Check if a key image is spent. -pub(super) async fn key_image_spent( - state: &mut CupratedRpcHandler, - key_image: [u8; 32], -) -> Result { - todo!("impl key image vec check responding KeyImageSpentStatus") - // if blockchain::key_image_spent(state, key_image).await? { - // Ok(KeyImageSpentStatus::SpentInBlockchain) - // } else if todo!("key image is spent in tx pool") { - // Ok(KeyImageSpentStatus::SpentInPool) - // } else { - // Ok(KeyImageSpentStatus::Unspent) - // } -} diff --git a/binaries/cuprated/src/rpc/other.rs b/binaries/cuprated/src/rpc/other.rs index adcc528..8bdf727 100644 --- a/binaries/cuprated/src/rpc/other.rs +++ b/binaries/cuprated/src/rpc/other.rs @@ -13,7 +13,7 @@ use cuprate_hex::Hex; use cuprate_rpc_interface::RpcHandler; use cuprate_rpc_types::{ base::{AccessResponseBase, ResponseBase}, - misc::{KeyImageSpentStatus, Status, TxEntry, TxEntryType}, + misc::{Status, TxEntry, TxEntryType}, other::{ GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse, GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest, @@ -33,7 +33,7 @@ use cuprate_rpc_types::{ }, }; use cuprate_types::{ - rpc::{OutKey, PoolInfo, PoolTxInfo}, + rpc::{KeyImageSpentStatus, OutKey, PoolInfo, PoolTxInfo}, TxInPool, }; use monero_serai::transaction::Transaction; @@ -127,10 +127,10 @@ async fn get_transactions( )); } - let requested_txs = request.txs_hashes.into_iter().map(|tx| tx.0).collect(); - - let (txs_in_blockchain, missed_txs) = - blockchain::transactions(&mut state.blockchain_read, requested_txs).await?; + let (txs_in_blockchain, missed_txs) = { + let requested_txs = request.txs_hashes.into_iter().map(|tx| tx.0).collect(); + blockchain::transactions(&mut state.blockchain_read, requested_txs).await? + }; let missed_tx = missed_txs.clone().into_iter().map(Hex).collect(); @@ -248,12 +248,18 @@ async fn get_transactions( /// async fn get_alt_blocks_hashes( - state: CupratedRpcHandler, + mut state: CupratedRpcHandler, request: GetAltBlocksHashesRequest, ) -> Result { + let blks_hashes = blockchain::alt_chains(&mut state.blockchain_read) + .await? + .into_iter() + .map(|info| Hex(info.block_hash)) + .collect(); + Ok(GetAltBlocksHashesResponse { base: AccessResponseBase::OK, - ..todo!() + blks_hashes, }) } @@ -262,16 +268,41 @@ async fn is_key_image_spent( mut state: CupratedRpcHandler, request: IsKeyImageSpentRequest, ) -> Result { - if state.is_restricted() && request.key_images.len() > RESTRICTED_SPENT_KEY_IMAGES_COUNT { + let restricted = state.is_restricted(); + + if restricted && request.key_images.len() > RESTRICTED_SPENT_KEY_IMAGES_COUNT { return Err(anyhow!("Too many key images queried in restricted mode")); } - let mut spent_status = Vec::with_capacity(request.key_images.len()); + let key_images = request + .key_images + .into_iter() + .map(|k| k.0) + .collect::>(); - for key_image in request.key_images { - let status = helper::key_image_spent(&mut state, key_image.0).await?; - spent_status.push(status.to_u8()); - } + let mut spent_status = Vec::with_capacity(key_images.len()); + + blockchain::key_images_spent(&mut state.blockchain_read, key_images.clone()) + .await? + .into_iter() + .for_each(|ki| { + if ki { + spent_status.push(KeyImageSpentStatus::SpentInBlockchain.to_u8()); + } else { + spent_status.push(KeyImageSpentStatus::Unspent.to_u8()); + } + }); + + txpool::key_images_spent(&mut state.txpool_read, key_images, !restricted) + .await? + .into_iter() + .for_each(|ki| { + if ki { + spent_status.push(KeyImageSpentStatus::SpentInPool.to_u8()); + } else { + spent_status.push(KeyImageSpentStatus::Unspent.to_u8()); + } + }); Ok(IsKeyImageSpentResponse { base: AccessResponseBase::OK, diff --git a/binaries/cuprated/src/rpc/request/blockchain.rs b/binaries/cuprated/src/rpc/request/blockchain.rs index 0bd8ec6..1c0f4c0 100644 --- a/binaries/cuprated/src/rpc/request/blockchain.rs +++ b/binaries/cuprated/src/rpc/request/blockchain.rs @@ -13,7 +13,9 @@ use cuprate_blockchain::service::BlockchainReadHandle; use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; use cuprate_types::{ blockchain::{BlockchainReadRequest, BlockchainResponse}, - rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput}, + rpc::{ + ChainInfo, CoinbaseTxSum, KeyImageSpentStatus, OutputHistogramEntry, OutputHistogramInput, + }, Chain, ExtendedBlockHeader, OutputOnChain, TxInBlockchain, }; @@ -221,9 +223,9 @@ pub(crate) async fn number_outputs_with_amount( /// [`BlockchainReadRequest::KeyImagesSpent`] pub(crate) async fn key_images_spent( blockchain_read: &mut BlockchainReadHandle, - key_images: HashSet<[u8; 32]>, -) -> Result { - let BlockchainResponse::KeyImagesSpent(is_spent) = blockchain_read + key_images: Vec<[u8; 32]>, +) -> Result, Error> { + let BlockchainResponse::KeyImagesSpent(status) = blockchain_read .ready() .await? .call(BlockchainReadRequest::KeyImagesSpent(key_images)) @@ -232,7 +234,7 @@ pub(crate) async fn key_images_spent( unreachable!(); }; - Ok(is_spent) + Ok(status) } /// [`BlockchainReadRequest::CompactChainHistory`] @@ -379,7 +381,7 @@ pub(crate) async fn alt_chain_count( /// [`BlockchainReadRequest::Transactions`]. pub(crate) async fn transactions( blockchain_read: &mut BlockchainReadHandle, - tx_hashes: BTreeSet<[u8; 32]>, + tx_hashes: HashSet<[u8; 32]>, ) -> Result<(Vec, Vec<[u8; 32]>), Error> { let BlockchainResponse::Transactions { txs, missed_txs } = blockchain_read .ready() diff --git a/binaries/cuprated/src/rpc/request/txpool.rs b/binaries/cuprated/src/rpc/request/txpool.rs index 53b7d8c..f6ff279 100644 --- a/binaries/cuprated/src/rpc/request/txpool.rs +++ b/binaries/cuprated/src/rpc/request/txpool.rs @@ -105,6 +105,29 @@ pub(crate) async fn txs_by_hash( Ok(txs_in_pool) } +/// TODO +pub(crate) async fn key_images_spent( + txpool_read: &mut TxpoolReadHandle, + key_images: Vec<[u8; 32]>, + include_sensitive_txs: bool, +) -> Result, Error> { + let TxpoolReadResponse::KeyImagesSpent(status) = txpool_read + .ready() + .await + .map_err(|e| anyhow!(e))? + .call(TxpoolReadRequest::KeyImagesSpent { + key_images, + include_sensitive_txs, + }) + .await + .map_err(|e| anyhow!(e))? + else { + unreachable!(); + }; + + Ok(status) +} + /// TODO pub(crate) async fn flush( txpool_manager: &mut Infallible, diff --git a/consensus/src/transactions.rs b/consensus/src/transactions.rs index f29c852..f2d2349 100644 --- a/consensus/src/transactions.rs +++ b/consensus/src/transactions.rs @@ -232,16 +232,18 @@ where }) })?; - let BlockchainResponse::KeyImagesSpent(kis_spent) = database + let BlockchainResponse::KeyImagesSpent(kis_status) = database .ready() .await? - .call(BlockchainReadRequest::KeyImagesSpent(spent_kis)) + .call(BlockchainReadRequest::KeyImagesSpent( + spent_kis.into_iter().collect(), + )) .await? else { panic!("Database sent incorrect response!"); }; - if kis_spent { + if kis_status.contains(&true) { tracing::debug!("One or more key images in batch already spent."); return Err(ConsensusError::Transaction(TransactionError::KeyImageSpent).into()); } diff --git a/consensus/tests/verify_correct_txs.rs b/consensus/tests/verify_correct_txs.rs index 4d6c179..2c80fac 100644 --- a/consensus/tests/verify_correct_txs.rs +++ b/consensus/tests/verify_correct_txs.rs @@ -45,7 +45,9 @@ fn dummy_database(outputs: BTreeMap) -> impl Database + Clon BlockchainResponse::Outputs(ret) } - BlockchainReadRequest::KeyImagesSpent(_) => BlockchainResponse::KeyImagesSpent(false), + BlockchainReadRequest::KeyImagesSpent(_) => { + BlockchainResponse::KeyImagesSpent(vec![false]) + } _ => panic!("Database request not needed for this test"), })) }) diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs index 0747af3..2d4d39e 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -14,7 +14,6 @@ //---------------------------------------------------------------------------------------------------- Mod mod binary_string; mod distribution; -mod key_image_spent_status; mod requested_info; mod status; mod tx_entry; @@ -22,7 +21,6 @@ mod types; pub use binary_string::BinaryString; pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed}; -pub use key_image_spent_status::KeyImageSpentStatus; pub use requested_info::RequestedInfo; pub use status::Status; pub use tx_entry::{TxEntry, TxEntryType}; diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 1c8c49a..777c18a 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -83,7 +83,7 @@ define_request_and_response! { }, AccessResponseBase { - /// These [`u8`]s are [`crate::misc::KeyImageSpentStatus`]. + /// These [`u8`]s are [`cuprate_types::rpc::KeyImageSpentStatus`]. spent_status: Vec, } } diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 6752dd7..3efbb9a 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -11,7 +11,7 @@ //---------------------------------------------------------------------------------------------------- Import use std::{ cmp::min, - collections::{BTreeSet, HashMap, HashSet}, + collections::{HashMap, HashSet}, sync::Arc, }; @@ -507,7 +507,7 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon /// [`BlockchainReadRequest::KeyImagesSpent`]. #[inline] -fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> ResponseResult { +fn key_images_spent(env: &ConcreteEnv, key_images: Vec) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); let tx_ro = thread_local(env); @@ -520,26 +520,13 @@ fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> Respons 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. - } + Ok(BlockchainResponse::KeyImagesSpent( + key_images + .into_par_iter() + .map(key_image_exists) + .collect::>()?, + )) } /// [`BlockchainReadRequest::CompactChainHistory`] @@ -786,7 +773,7 @@ fn alt_chain_count(env: &ConcreteEnv) -> ResponseResult { } /// [`BlockchainReadRequest::Transactions`] -fn transactions(env: &ConcreteEnv, tx_hashes: BTreeSet<[u8; 32]>) -> ResponseResult { +fn transactions(env: &ConcreteEnv, tx_hashes: HashSet<[u8; 32]>) -> ResponseResult { Ok(BlockchainResponse::Transactions { txs: todo!(), missed_txs: todo!(), diff --git a/storage/blockchain/src/service/tests.rs b/storage/blockchain/src/service/tests.rs index 38db665..063b62d 100644 --- a/storage/blockchain/src/service/tests.rs +++ b/storage/blockchain/src/service/tests.rs @@ -179,8 +179,8 @@ async fn test_template( )); // Contains a fake non-spent key-image. - let ki_req = HashSet::from([[0; 32]]); - let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(false)); + let ki_req = vec![[0; 32]]; + let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(vec![false])); //----------------------------------------------------------------------- Assert expected response // Assert read requests lead to the expected responses. @@ -232,10 +232,13 @@ 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(HashSet::from([key_image])); + let request = BlockchainReadRequest::KeyImagesSpent(vec![key_image]); let response = reader.clone().oneshot(request).await; println!("response: {response:#?}, key_image: {key_image:#?}"); - assert_eq!(response.unwrap(), BlockchainResponse::KeyImagesSpent(true)); + assert_eq!( + response.unwrap(), + BlockchainResponse::KeyImagesSpent(vec![true]) + ); } //----------------------------------------------------------------------- Output checks diff --git a/storage/txpool/src/service/interface.rs b/storage/txpool/src/service/interface.rs index c3f3c27..a247923 100644 --- a/storage/txpool/src/service/interface.rs +++ b/storage/txpool/src/service/interface.rs @@ -5,7 +5,6 @@ use std::{ collections::{HashMap, HashSet}, num::NonZero, sync::Arc, - time::Instant, }; use cuprate_types::{rpc::PoolInfo, TransactionVerificationData, TxInPool}; @@ -54,10 +53,17 @@ pub enum TxpoolReadRequest { start_time: Option>, }, + /// TODO TxsByHash { tx_hashes: Vec<[u8; 32]>, include_sensitive_txs: bool, }, + + /// TODO + KeyImagesSpent { + key_images: Vec<[u8; 32]>, + include_sensitive_txs: bool, + }, } //---------------------------------------------------------------------------------------------------- TxpoolReadResponse @@ -103,6 +109,9 @@ pub enum TxpoolReadResponse { /// Response to [`TxpoolReadRequest::TxsByHash`]. TxsByHash(Vec), + + /// Response to [`TxpoolReadRequest::KeyImagesSpent`]. + KeyImagesSpent(Vec), } //---------------------------------------------------------------------------------------------------- TxpoolWriteRequest diff --git a/storage/txpool/src/service/read.rs b/storage/txpool/src/service/read.rs index 95e8892..1579808 100644 --- a/storage/txpool/src/service/read.rs +++ b/storage/txpool/src/service/read.rs @@ -85,6 +85,10 @@ fn map_request( tx_hashes, include_sensitive_txs, } => txs_by_hash(env, tx_hashes, include_sensitive_txs), + TxpoolReadRequest::KeyImagesSpent { + key_images, + include_sensitive_txs, + } => key_images_spent(env, key_images, include_sensitive_txs), } } @@ -236,3 +240,12 @@ fn txs_by_hash( ) -> ReadResponseResult { Ok(TxpoolReadResponse::TxsByHash(todo!())) } + +/// [`TxpoolReadRequest::KeyImagesSpent`]. +fn key_images_spent( + env: &ConcreteEnv, + key_images: Vec<[u8; 32]>, + include_sensitive_txs: bool, +) -> ReadResponseResult { + Ok(TxpoolReadResponse::KeyImagesSpent(todo!())) +} diff --git a/types/types/src/blockchain.rs b/types/types/src/blockchain.rs index 3c9c9af..3ce1efb 100644 --- a/types/types/src/blockchain.rs +++ b/types/types/src/blockchain.rs @@ -4,19 +4,18 @@ //! responses are also tested in Cuprate's blockchain database crate. //---------------------------------------------------------------------------------------------------- Import use std::{ - collections::{BTreeSet, HashMap, HashSet}, + collections::{HashMap, HashSet}, ops::Range, }; use monero_serai::block::Block; use crate::{ + rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput}, types::{Chain, ExtendedBlockHeader, OutputOnChain, TxsInBlock, VerifiedBlockInformation}, AltBlockInformation, BlockCompleteEntry, ChainId, TxInBlockchain, }; -use crate::rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput}; - //---------------------------------------------------------------------------------------------------- ReadRequest /// A read request to the blockchain database. /// @@ -94,10 +93,10 @@ pub enum BlockchainReadRequest { /// The input is a list of output amounts. NumberOutputsWithAmount(Vec), - /// Check that all key images within a set are not spent. + /// Check the spend status of key images. /// /// Input is a set of key images. - KeyImagesSpent(HashSet<[u8; 32]>), + KeyImagesSpent(Vec<[u8; 32]>), /// A request for the compact chain history. CompactChainHistory, @@ -163,7 +162,7 @@ pub enum BlockchainReadRequest { AltChainCount, /// TODO - Transactions { tx_hashes: BTreeSet<[u8; 32]> }, + Transactions { tx_hashes: HashSet<[u8; 32]> }, } //---------------------------------------------------------------------------------------------------- WriteRequest @@ -271,11 +270,11 @@ 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). + /// Inner value is a `Vec` the same length as the input. /// - /// The inner value is `false` if _none_ of the key images were spent. - KeyImagesSpent(bool), + /// The index of each entry corresponds with the request. + /// `true` means that the key image was spent. + KeyImagesSpent(Vec), /// Response to [`BlockchainReadRequest::CompactChainHistory`]. CompactChainHistory { diff --git a/rpc/types/src/misc/key_image_spent_status.rs b/types/types/src/rpc/key_image_spent_status.rs similarity index 88% rename from rpc/types/src/misc/key_image_spent_status.rs rename to types/types/src/rpc/key_image_spent_status.rs index 97c257a..1bf9e37 100644 --- a/rpc/types/src/misc/key_image_spent_status.rs +++ b/types/types/src/rpc/key_image_spent_status.rs @@ -12,12 +12,7 @@ use cuprate_epee_encoding::{ }; //---------------------------------------------------------------------------------------------------- KeyImageSpentStatus -#[doc = crate::macros::monero_definition_link!( - "cc73fe71162d564ffda8e549b79a350bca53c454", - "rpc/core_rpc_server_commands_defs.h", - 456..=460 -)] -/// Used in [`crate::other::IsKeyImageSpentResponse`]. +/// Used in RPC's `/is_key_image_spent`. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(try_from = "u8", into = "u8"))] @@ -32,7 +27,7 @@ impl KeyImageSpentStatus { /// Convert [`Self`] to a [`u8`]. /// /// ```rust - /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// use cuprate_types::rpc::KeyImageSpentStatus as K; /// /// assert_eq!(K::Unspent.to_u8(), 0); /// assert_eq!(K::SpentInBlockchain.to_u8(), 1); @@ -52,7 +47,7 @@ impl KeyImageSpentStatus { /// This returns [`None`] if `u > 2`. /// /// ```rust - /// use cuprate_rpc_types::misc::KeyImageSpentStatus as K; + /// use cuprate_types::rpc::KeyImageSpentStatus as K; /// /// assert_eq!(K::from_u8(0), Some(K::Unspent)); /// assert_eq!(K::from_u8(1), Some(K::SpentInBlockchain)); diff --git a/types/types/src/rpc/mod.rs b/types/types/src/rpc/mod.rs index c2dbd33..35278e1 100644 --- a/types/types/src/rpc/mod.rs +++ b/types/types/src/rpc/mod.rs @@ -5,10 +5,12 @@ //! instead of `hash: String`, this module's types would use something like //! `hash: [u8; 32]`. +mod key_image_spent_status; mod pool_info; mod pool_info_extent; mod types; +pub use key_image_spent_status::KeyImageSpentStatus; pub use pool_info::PoolInfo; pub use pool_info_extent::PoolInfoExtent; pub use types::{