diff --git a/binaries/cuprated/src/rpc.rs b/binaries/cuprated/src/rpc.rs index 1f06d0a..71ca192 100644 --- a/binaries/cuprated/src/rpc.rs +++ b/binaries/cuprated/src/rpc.rs @@ -9,5 +9,6 @@ mod helper; mod json; mod other; mod request; +mod shared; pub use handler::CupratedRpcHandler; diff --git a/binaries/cuprated/src/rpc/bin.rs b/binaries/cuprated/src/rpc/bin.rs index 84405fe..80bfec9 100644 --- a/binaries/cuprated/src/rpc/bin.rs +++ b/binaries/cuprated/src/rpc/bin.rs @@ -1,6 +1,7 @@ //! RPC request handler functions (binary endpoints). use anyhow::{anyhow, Error}; +use bytes::Bytes; use cuprate_constants::rpc::{RESTRICTED_BLOCK_COUNT, RESTRICTED_TRANSACTIONS_COUNT}; use cuprate_rpc_interface::RpcHandler; @@ -17,7 +18,7 @@ use cuprate_rpc_types::{ }; use cuprate_types::{rpc::PoolInfoExtent, BlockCompleteEntry}; -use crate::rpc::{helper, request::blockchain, CupratedRpcHandler}; +use crate::rpc::{helper, request::blockchain, shared, CupratedRpcHandler}; /// Map a [`BinRequest`] to the function that will lead to a [`BinResponse`]. pub(super) async fn map_request( @@ -122,18 +123,16 @@ async fn get_blocks( /// async fn get_blocks_by_height( - state: CupratedRpcHandler, + mut state: CupratedRpcHandler, request: GetBlocksByHeightRequest, ) -> Result { if state.is_restricted() && request.heights.len() > RESTRICTED_BLOCK_COUNT { return Err(anyhow!("Too many blocks requested in restricted mode")); } - let blocks = request - .heights - .into_iter() - .map(|height| Ok(todo!())) - .collect::, Error>>()?; + let blocks = + blockchain::block_complete_entries_by_height(&mut state.blockchain_read, request.heights) + .await?; Ok(GetBlocksByHeightResponse { base: helper::access_response_base(false), @@ -157,13 +156,6 @@ async fn get_hashes( request.block_ids[len - 1] }; - const GENESIS_BLOCK_HASH: [u8; 32] = [0; 32]; // TODO - if last != GENESIS_BLOCK_HASH { - return Err(anyhow!( - "genesis block mismatch, found: {last:?}, expected: {GENESIS_BLOCK_HASH:?}" - )); - } - let mut bytes = request.block_ids; let hashes: Vec<[u8; 32]> = (&bytes).into(); @@ -201,10 +193,7 @@ async fn get_outs( state: CupratedRpcHandler, request: GetOutsRequest, ) -> Result { - Ok(GetOutsResponse { - base: helper::access_response_base(false), - ..todo!() - }) + shared::get_outs(state, request).await } /// diff --git a/binaries/cuprated/src/rpc/other.rs b/binaries/cuprated/src/rpc/other.rs index dc113c8..7e74e1d 100644 --- a/binaries/cuprated/src/rpc/other.rs +++ b/binaries/cuprated/src/rpc/other.rs @@ -37,7 +37,7 @@ use cuprate_rpc_types::{ }, }; use cuprate_types::{ - rpc::{KeyImageSpentStatus, OutKey, PoolInfo, PoolTxInfo, PublicNode}, + rpc::{KeyImageSpentStatus, PoolInfo, PoolTxInfo, PublicNode}, TxInPool, TxRelayChecks, }; use monero_serai::transaction::{Input, Timelock, Transaction}; @@ -47,7 +47,7 @@ use crate::{ rpc::{ helper, request::{blockchain, blockchain_context, blockchain_manager, txpool}, - CupratedRpcHandler, + shared, CupratedRpcHandler, }, statics::START_INSTANT_UNIX, }; @@ -610,45 +610,21 @@ async fn get_net_stats( /// async fn get_outs( - mut state: CupratedRpcHandler, + state: CupratedRpcHandler, request: GetOutsRequest, ) -> Result { - if state.is_restricted() && request.outputs.len() > MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT { - return Err(anyhow!("Too many outs requested")); - } - - let outputs = { - let mut outputs = HashMap::>::with_capacity(request.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_iter().map(|(index, out)| OutKey { - key: out.key.map_or(Hex([0; 32]), |e| Hex(e.compress().0)), - mask: Hex(out.commitment.compress().0), - unlocked: matches!(out.time_lock, Timelock::None), - height: usize_to_u64(out.height), - txid: if request.get_txid { - hex::encode(out.txid) - } else { - String::new() - }, - }) - }) - .collect::>(); + let outs = shared::get_outs( + state, + cuprate_rpc_types::bin::GetOutsRequest { + outputs: request.outputs, + get_txid: request.get_txid, + }, + ) + .await? + .outs + .into_iter() + .map(Into::into) + .collect(); Ok(GetOutsResponse { base: helper::response_base(false), diff --git a/binaries/cuprated/src/rpc/request/blockchain.rs b/binaries/cuprated/src/rpc/request/blockchain.rs index e5c470f..58ee1b5 100644 --- a/binaries/cuprated/src/rpc/request/blockchain.rs +++ b/binaries/cuprated/src/rpc/request/blockchain.rs @@ -16,7 +16,7 @@ use cuprate_types::{ rpc::{ ChainInfo, CoinbaseTxSum, KeyImageSpentStatus, OutputHistogramEntry, OutputHistogramInput, }, - Chain, ExtendedBlockHeader, OutputOnChain, TxInBlockchain, + BlockCompleteEntry, Chain, ExtendedBlockHeader, OutputOnChain, TxInBlockchain, }; /// [`BlockchainReadRequest::Block`]. @@ -399,7 +399,7 @@ pub(crate) async fn transactions( pub(crate) async fn total_rct_outputs( blockchain_read: &mut BlockchainReadHandle, ) -> Result { - let BlockchainResponse::TotalRctOutputs(total_rct_outputs) = blockchain_read + let BlockchainResponse::TotalRctOutputs(n) = blockchain_read .ready() .await? .call(BlockchainReadRequest::TotalRctOutputs) @@ -408,5 +408,24 @@ pub(crate) async fn total_rct_outputs( unreachable!(); }; - Ok(total_rct_outputs) + Ok(n) +} + +/// [`BlockchainReadRequest::BlockCompleteEntriesByHeight`]. +pub(crate) async fn block_complete_entries_by_height( + blockchain_read: &mut BlockchainReadHandle, + block_heights: Vec, +) -> Result, Error> { + let BlockchainResponse::BlockCompleteEntriesByHeight(blocks) = blockchain_read + .ready() + .await? + .call(BlockchainReadRequest::BlockCompleteEntriesByHeight( + block_heights.into_iter().map(u64_to_usize).collect(), + )) + .await? + else { + unreachable!(); + }; + + Ok(blocks) } diff --git a/binaries/cuprated/src/rpc/shared.rs b/binaries/cuprated/src/rpc/shared.rs new file mode 100644 index 0000000..624a012 --- /dev/null +++ b/binaries/cuprated/src/rpc/shared.rs @@ -0,0 +1,65 @@ +//! RPC handler functions that are shared between different endpoint/methods. + +use std::collections::{HashMap, HashSet}; + +use anyhow::{anyhow, Error}; +use monero_serai::transaction::Timelock; + +use cuprate_constants::rpc::MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT; +use cuprate_helper::cast::usize_to_u64; +use cuprate_hex::Hex; +use cuprate_rpc_interface::RpcHandler; +use cuprate_rpc_types::{ + bin::{GetOutsRequest, GetOutsResponse}, + misc::OutKeyBin, +}; + +use crate::rpc::{helper, request::blockchain, CupratedRpcHandler}; + +/// +/// +/// Shared between: +/// - Other JSON's `/get_outs` +/// - Binary's `/get_outs.bin` +pub(super) async fn get_outs( + mut state: CupratedRpcHandler, + request: GetOutsRequest, +) -> Result { + if state.is_restricted() && request.outputs.len() > MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT { + return Err(anyhow!("Too many outs requested")); + } + + let outputs = { + let mut outputs = HashMap::>::with_capacity(request.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_iter().map(|(_, out)| OutKeyBin { + key: out.key.map_or([0; 32], |e| e.compress().0), + mask: out.commitment.compress().0, + unlocked: matches!(out.time_lock, Timelock::None), + height: usize_to_u64(out.height), + txid: if request.get_txid { out.txid } else { [0; 32] }, + }) + }) + .collect::>(); + + Ok(GetOutsResponse { + base: helper::access_response_base(false), + outs, + }) +} diff --git a/rpc/types/src/bin.rs b/rpc/types/src/bin.rs index 2934ca0..91ccea5 100644 --- a/rpc/types/src/bin.rs +++ b/rpc/types/src/bin.rs @@ -120,7 +120,6 @@ define_request_and_response! { Request { requested_info: u8 = default::(), "default", - // FIXME: This is a `std::list` in `monerod` because...? block_ids: ByteArrayVec<32> = default::>(), "default", start_height: u64, prune: bool, diff --git a/rpc/types/src/from.rs b/rpc/types/src/from.rs index b5d94af..f6a4b47 100644 --- a/rpc/types/src/from.rs +++ b/rpc/types/src/from.rs @@ -193,3 +193,15 @@ impl From for crate::misc::SpentKeyImageInfo { } } } + +impl From for crate::misc::OutKey { + fn from(x: crate::misc::OutKeyBin) -> Self { + Self { + key: Hex(x.key), + mask: Hex(x.mask), + unlocked: x.unlocked, + height: x.height, + txid: hex::encode(x.txid), + } + } +} diff --git a/rpc/types/src/misc/mod.rs b/rpc/types/src/misc/mod.rs index 2d4d39e..0843edf 100644 --- a/rpc/types/src/misc/mod.rs +++ b/rpc/types/src/misc/mod.rs @@ -25,6 +25,6 @@ pub use requested_info::RequestedInfo; pub use status::Status; pub use tx_entry::{TxEntry, TxEntryType}; pub use types::{ - BlockHeader, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, HistogramEntry, OutKeyBin, - SetBan, Span, SpentKeyImageInfo, SyncInfoPeer, TxInfo, + BlockHeader, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, HistogramEntry, OutKey, + OutKeyBin, SetBan, Span, SpentKeyImageInfo, SyncInfoPeer, TxInfo, }; diff --git a/rpc/types/src/misc/types.rs b/rpc/types/src/misc/types.rs index cc0c30b..3880e69 100644 --- a/rpc/types/src/misc/types.rs +++ b/rpc/types/src/misc/types.rs @@ -252,6 +252,23 @@ define_struct_and_impl_epee! { } } +define_struct_and_impl_epee! { + #[doc = monero_definition_link!( + "cc73fe71162d564ffda8e549b79a350bca53c454", + "rpc/core_rpc_server_commands_defs.h", + 582..=597 + )] + /// Used in [`crate::other::GetOutsRequest`]. + OutKey { + key: Hex<32>, + mask: Hex<32>, + unlocked: bool, + height: u64, + /// Optionally empty with `/get_outs`'s `"get_txid": false`. + txid: String, + } +} + define_struct_and_impl_epee! { #[doc = monero_definition_link!( "cc73fe71162d564ffda8e549b79a350bca53c454", diff --git a/rpc/types/src/other.rs b/rpc/types/src/other.rs index 777c18a..349ed6f 100644 --- a/rpc/types/src/other.rs +++ b/rpc/types/src/other.rs @@ -7,12 +7,12 @@ use serde::{Deserialize, Serialize}; use cuprate_hex::Hex; -use cuprate_types::rpc::{OutKey, Peer, PublicNode, TxpoolStats}; +use cuprate_types::rpc::{Peer, PublicNode, TxpoolStats}; use crate::{ base::{AccessResponseBase, ResponseBase}, macros::define_request_and_response, - misc::{GetOutputsOut, SpentKeyImageInfo, Status, TxEntry, TxInfo}, + misc::{GetOutputsOut, OutKey, SpentKeyImageInfo, Status, TxEntry, TxInfo}, RpcCallValue, }; @@ -1206,9 +1206,8 @@ mod test { mask: Hex(hex!( "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522" )), - txid: Hex(hex!( - "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601" - )), + txid: "9d651903b80fb70b9935b72081cd967f543662149aed3839222511acd9100601" + .into(), unlocked: true, }, OutKey { @@ -1219,9 +1218,8 @@ mod test { mask: Hex(hex!( "1738eb7a677c6149228a2beaa21bea9e3370802d72a3eec790119580e02bd522" )), - txid: Hex(hex!( - "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0" - )), + txid: "230bff732dc5f225df14fff82aadd1bf11b3fb7ad3a03413c396a617e843f7d0" + .into(), unlocked: true, }, ], diff --git a/storage/blockchain/src/ops/block.rs b/storage/blockchain/src/ops/block.rs index 2dc88aa..362b82a 100644 --- a/storage/blockchain/src/ops/block.rs +++ b/storage/blockchain/src/ops/block.rs @@ -254,7 +254,7 @@ pub fn get_block_blob_with_tx_indexes( Ok((block, miner_tx_idx, numb_txs)) } -//---------------------------------------------------------------------------------------------------- `get_block_extended_header_*` +//---------------------------------------------------------------------------------------------------- `get_block_complete_entry_*` /// Retrieve a [`BlockCompleteEntry`] from the database. /// #[doc = doc_error!()] @@ -263,6 +263,16 @@ pub fn get_block_complete_entry( tables: &impl TablesIter, ) -> Result { let block_height = tables.block_heights().get(block_hash)?; + get_block_complete_entry_from_height(&block_height, tables) +} + +/// Retrieve a [`BlockCompleteEntry`] from the database. +/// +#[doc = doc_error!()] +pub fn get_block_complete_entry_from_height( + block_height: &BlockHeight, + tables: &impl TablesIter, +) -> Result { let (block_blob, miner_tx_idx, numb_non_miner_txs) = get_block_blob_with_tx_indexes(&block_height, tables)?; diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 831dca2..6ef5a85 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -41,7 +41,8 @@ use crate::{ }, block::{ block_exists, get_block_blob_with_tx_indexes, get_block_complete_entry, - get_block_extended_header_from_height, get_block_height, get_block_info, + get_block_complete_entry_from_height, get_block_extended_header_from_height, + get_block_height, get_block_info, }, blockchain::{cumulative_generated_coins, find_split_point, top_block_height}, key_image::key_image_exists, @@ -108,6 +109,7 @@ fn map_request( match request { R::BlockCompleteEntries(block_hashes) => block_complete_entries(env, block_hashes), + R::BlockCompleteEntriesByHeight(heights) => block_complete_entries_by_height(env, heights), R::BlockExtendedHeader(block) => block_extended_header(env, block), R::BlockHash(block, chain) => block_hash(env, block, chain), R::FindBlock(block_hash) => find_block(env, block_hash), @@ -245,6 +247,32 @@ fn block_complete_entries(env: &ConcreteEnv, block_hashes: Vec) -> Re }) } +/// [`BlockchainReadRequest::BlockCompleteEntriesByHeight`]. +fn block_complete_entries_by_height( + env: &ConcreteEnv, + block_heights: Vec, +) -> ResponseResult { + // Prepare tx/tables in `ThreadLocal`. + let env_inner = env.env_inner(); + let tx_ro = thread_local(env); + let tables = thread_local(env); + + let blocks = block_heights + .into_par_iter() + .map(|height| { + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + Ok(get_block_complete_entry_from_height(&height, tables)?) + }) + .collect::>()?; + + let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?; + let tables = get_tables!(env_inner, tx_ro, tables)?.as_ref(); + + Ok(BlockchainResponse::BlockCompleteEntriesByHeight(blocks)) +} + /// [`BlockchainReadRequest::BlockExtendedHeader`]. #[inline] fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { diff --git a/types/types/src/blockchain.rs b/types/types/src/blockchain.rs index 143a7c2..089f4df 100644 --- a/types/types/src/blockchain.rs +++ b/types/types/src/blockchain.rs @@ -32,6 +32,11 @@ pub enum BlockchainReadRequest { /// The input is the block hashes. BlockCompleteEntries(Vec<[u8; 32]>), + /// Request [`BlockCompleteEntry`]s. + /// + /// The input is the block heights. + BlockCompleteEntriesByHeight(Vec), + /// Request a block's extended header. /// /// The input is the block's height. @@ -223,6 +228,9 @@ pub enum BlockchainResponse { blockchain_height: usize, }, + /// Response to [`BlockchainReadRequest::BlockCompleteEntriesByHeight`]. + BlockCompleteEntriesByHeight(Vec), + /// Response to [`BlockchainReadRequest::BlockExtendedHeader`]. /// /// Inner value is the extended headed of the requested block. diff --git a/types/types/src/rpc/mod.rs b/types/types/src/rpc/mod.rs index 35278e1..dd998d6 100644 --- a/types/types/src/rpc/mod.rs +++ b/types/types/src/rpc/mod.rs @@ -16,7 +16,7 @@ pub use pool_info_extent::PoolInfoExtent; pub use types::{ AddAuxPow, AuxPow, BlockHeader, BlockOutputIndices, ChainInfo, CoinbaseTxSum, ConnectionInfo, FeeEstimate, GetBan, GetMinerDataTxBacklogEntry, GetOutputsOut, HardForkEntry, HardForkInfo, - HistogramEntry, MinerData, MinerDataTxBacklogEntry, OutKey, OutKeyBin, OutputDistributionData, + HistogramEntry, MinerData, MinerDataTxBacklogEntry, OutputDistributionData, OutputHistogramEntry, OutputHistogramInput, Peer, PoolInfoFull, PoolInfoIncremental, PoolTxInfo, PublicNode, SetBan, Span, SpentKeyImageInfo, SyncInfoPeer, TxBacklogEntry, TxInfo, TxOutputIndices, TxpoolHisto, TxpoolStats, diff --git a/types/types/src/rpc/types.rs b/types/types/src/rpc/types.rs index 7a5c8d2..ce628b7 100644 --- a/types/types/src/rpc/types.rs +++ b/types/types/src/rpc/types.rs @@ -308,19 +308,6 @@ define_struct_and_impl_epee! { index: u64, } - #[doc = monero_definition_link!( - "cc73fe71162d564ffda8e549b79a350bca53c454", - "rpc/core_rpc_server_commands_defs.h", - 538..=553 - )] - OutKeyBin { - key: [u8; 32], - mask: [u8; 32], - unlocked: bool, - height: u64, - txid: [u8; 32], - } - #[doc = monero_definition_link!( "cc73fe71162d564ffda8e549b79a350bca53c454", "rpc/core_rpc_server_commands_defs.h", @@ -419,20 +406,6 @@ define_struct_and_impl_epee! { txs_total: u32, } - #[doc = monero_definition_link!( - "cc73fe71162d564ffda8e549b79a350bca53c454", - "rpc/core_rpc_server_commands_defs.h", - 582..=597 - )] - OutKey { - key: Hex<32>, - mask: Hex<32>, - unlocked: bool, - height: u64, - // Optionally empty with `/get_outs`'s `"get_txid": false`. - txid: String, - } - #[doc = monero_definition_link!( "893916ad091a92e765ce3241b94e706ad012b62a", "blockchain_db/lmdb/db_lmdb.cpp",