Compare commits

...

2 commits

Author SHA1 Message Date
hinto.janai
a359eed8be
/is_key_image_spent 2024-12-11 20:52:40 -05:00
hinto.janai
c3bc9b26ec
/get_transactions 2024-12-11 19:16:34 -05:00
18 changed files with 526 additions and 256 deletions

View file

@ -11,7 +11,7 @@ use cuprate_helper::{
cast::{u64_to_usize, usize_to_u64}, cast::{u64_to_usize, usize_to_u64},
map::split_u128_into_low_high_bits, 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 cuprate_types::HardFork;
use crate::{ use crate::{
@ -166,18 +166,3 @@ pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [
let height = chain_height.saturating_sub(1); let height = chain_height.saturating_sub(1);
Ok((height, hash)) 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<KeyImageSpentStatus, Error> {
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)
// }
}

View file

@ -1,18 +1,19 @@
//! RPC request handler functions (other JSON endpoints). //! RPC request handler functions (other JSON endpoints).
use std::collections::{HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use cuprate_constants::rpc::{ use cuprate_constants::rpc::{
MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT, RESTRICTED_SPENT_KEY_IMAGES_COUNT, MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT, RESTRICTED_SPENT_KEY_IMAGES_COUNT,
RESTRICTED_TRANSACTIONS_COUNT,
}; };
use cuprate_helper::cast::usize_to_u64; use cuprate_helper::cast::usize_to_u64;
use cuprate_hex::Hex; use cuprate_hex::Hex;
use cuprate_rpc_interface::RpcHandler; use cuprate_rpc_interface::RpcHandler;
use cuprate_rpc_types::{ use cuprate_rpc_types::{
base::{AccessResponseBase, ResponseBase}, base::{AccessResponseBase, ResponseBase},
misc::{KeyImageSpentStatus, Status}, misc::{Status, TxEntry, TxEntryType},
other::{ other::{
GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse, GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse,
GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest, GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest,
@ -31,15 +32,20 @@ use cuprate_rpc_types::{
UpdateRequest, UpdateResponse, UpdateRequest, UpdateResponse,
}, },
}; };
use cuprate_types::rpc::OutKey; use cuprate_types::{
rpc::{KeyImageSpentStatus, OutKey, PoolInfo, PoolTxInfo},
TxInPool,
};
use monero_serai::transaction::Transaction;
use crate::{ use crate::{
rpc::CupratedRpcHandler, rpc::CupratedRpcHandler,
rpc::{helper, request::blockchain}, rpc::{
helper,
request::{blockchain, blockchain_context, blockchain_manager, txpool},
},
}; };
use super::request::blockchain_manager;
/// Map a [`OtherRequest`] to the function that will lead to a [`OtherResponse`]. /// Map a [`OtherRequest`] to the function that will lead to a [`OtherResponse`].
pub(super) async fn map_request( pub(super) async fn map_request(
state: CupratedRpcHandler, state: CupratedRpcHandler,
@ -112,23 +118,148 @@ async fn get_height(
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L979-L1227> /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L979-L1227>
async fn get_transactions( async fn get_transactions(
state: CupratedRpcHandler, mut state: CupratedRpcHandler,
request: GetTransactionsRequest, request: GetTransactionsRequest,
) -> Result<GetTransactionsResponse, Error> { ) -> Result<GetTransactionsResponse, Error> {
if state.is_restricted() && request.txs_hashes.len() > RESTRICTED_TRANSACTIONS_COUNT {
return Err(anyhow!(
"Too many transactions requested in restricted mode"
));
}
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();
// Check the txpool for missed transactions.
let txs_in_pool = if missed_txs.is_empty() {
vec![]
} else {
let include_sensitive_txs = !state.is_restricted();
txpool::txs_by_hash(&mut state.txpool_read, missed_txs, include_sensitive_txs).await?
};
let (txs, txs_as_json) = {
// Prepare the final JSON output.
let len = txs_in_blockchain.len() + txs_in_pool.len();
let mut txs = Vec::with_capacity(len);
let mut txs_as_json = Vec::with_capacity(if request.decode_as_json { len } else { 0 });
// Map all blockchain transactions.
for tx in txs_in_blockchain {
let tx_hash = Hex(tx.tx_hash);
let pruned_as_hex = hex::encode(tx.pruned_blob);
let prunable_as_hex = hex::encode(tx.prunable_blob);
let prunable_hash = Hex(tx.prunable_hash);
let as_json = if request.decode_as_json {
let tx = Transaction::read(&mut tx.tx_blob.as_slice())?;
let json_type = cuprate_types::json::tx::Transaction::from(tx);
let json = serde_json::to_string(&json_type).unwrap();
txs_as_json.push(json.clone());
json
} else {
String::new()
};
let tx_entry_type = TxEntryType::Blockchain {
block_height: tx.block_height,
block_timestamp: tx.block_timestamp,
confirmations: tx.confirmations,
output_indices: tx.output_indices,
in_pool: false,
};
let tx = TxEntry {
as_hex: String::new(),
as_json,
double_spend_seen: false,
tx_hash,
prunable_as_hex,
prunable_hash,
pruned_as_hex,
tx_entry_type,
};
txs.push(tx);
}
// Map all txpool transactions.
for tx_in_pool in txs_in_pool {
let TxInPool {
tx_blob,
tx_hash,
double_spend_seen,
received_timestamp,
relayed,
} = tx_in_pool;
let tx_hash = Hex(tx_hash);
let tx = Transaction::read(&mut tx_blob.as_slice())?;
// TODO: pruned data.
let pruned_as_hex = String::new();
let prunable_as_hex = String::new();
let prunable_hash = Hex([0; 32]);
let as_json = if request.decode_as_json {
let json_type = cuprate_types::json::tx::Transaction::from(tx);
let json = serde_json::to_string(&json_type).unwrap();
txs_as_json.push(json.clone());
json
} else {
String::new()
};
let tx_entry_type = TxEntryType::Pool {
relayed,
received_timestamp,
in_pool: true,
};
let tx = TxEntry {
as_hex: String::new(),
as_json,
double_spend_seen,
tx_hash,
prunable_as_hex,
prunable_hash,
pruned_as_hex,
tx_entry_type,
};
txs.push(tx);
}
(txs, txs_as_json)
};
Ok(GetTransactionsResponse { Ok(GetTransactionsResponse {
base: AccessResponseBase::OK, base: AccessResponseBase::OK,
..todo!() txs_as_hex: vec![],
txs_as_json,
missed_tx,
txs,
}) })
} }
/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L790-L815> /// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L790-L815>
async fn get_alt_blocks_hashes( async fn get_alt_blocks_hashes(
state: CupratedRpcHandler, mut state: CupratedRpcHandler,
request: GetAltBlocksHashesRequest, request: GetAltBlocksHashesRequest,
) -> Result<GetAltBlocksHashesResponse, Error> { ) -> Result<GetAltBlocksHashesResponse, Error> {
let blks_hashes = blockchain::alt_chains(&mut state.blockchain_read)
.await?
.into_iter()
.map(|info| Hex(info.block_hash))
.collect();
Ok(GetAltBlocksHashesResponse { Ok(GetAltBlocksHashesResponse {
base: AccessResponseBase::OK, base: AccessResponseBase::OK,
..todo!() blks_hashes,
}) })
} }
@ -137,16 +268,41 @@ async fn is_key_image_spent(
mut state: CupratedRpcHandler, mut state: CupratedRpcHandler,
request: IsKeyImageSpentRequest, request: IsKeyImageSpentRequest,
) -> Result<IsKeyImageSpentResponse, Error> { ) -> Result<IsKeyImageSpentResponse, Error> {
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")); 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::<Vec<[u8; 32]>>();
for key_image in request.key_images { let mut spent_status = Vec::with_capacity(key_images.len());
let status = helper::key_image_spent(&mut state, key_image.0).await?;
spent_status.push(status.to_u8()); 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 { Ok(IsKeyImageSpentResponse {
base: AccessResponseBase::OK, base: AccessResponseBase::OK,

View file

@ -1,7 +1,7 @@
//! Functions to send [`BlockchainReadRequest`]s. //! Functions to send [`BlockchainReadRequest`]s.
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{BTreeSet, HashMap, HashSet},
ops::Range, ops::Range,
}; };
@ -13,8 +13,10 @@ use cuprate_blockchain::service::BlockchainReadHandle;
use cuprate_helper::cast::{u64_to_usize, usize_to_u64}; use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
use cuprate_types::{ use cuprate_types::{
blockchain::{BlockchainReadRequest, BlockchainResponse}, blockchain::{BlockchainReadRequest, BlockchainResponse},
rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput}, rpc::{
Chain, ExtendedBlockHeader, OutputOnChain, ChainInfo, CoinbaseTxSum, KeyImageSpentStatus, OutputHistogramEntry, OutputHistogramInput,
},
Chain, ExtendedBlockHeader, OutputOnChain, TxInBlockchain,
}; };
/// [`BlockchainReadRequest::Block`]. /// [`BlockchainReadRequest::Block`].
@ -221,9 +223,9 @@ pub(crate) async fn number_outputs_with_amount(
/// [`BlockchainReadRequest::KeyImagesSpent`] /// [`BlockchainReadRequest::KeyImagesSpent`]
pub(crate) async fn key_images_spent( pub(crate) async fn key_images_spent(
blockchain_read: &mut BlockchainReadHandle, blockchain_read: &mut BlockchainReadHandle,
key_images: HashSet<[u8; 32]>, key_images: Vec<[u8; 32]>,
) -> Result<bool, Error> { ) -> Result<Vec<bool>, Error> {
let BlockchainResponse::KeyImagesSpent(is_spent) = blockchain_read let BlockchainResponse::KeyImagesSpent(status) = blockchain_read
.ready() .ready()
.await? .await?
.call(BlockchainReadRequest::KeyImagesSpent(key_images)) .call(BlockchainReadRequest::KeyImagesSpent(key_images))
@ -232,7 +234,7 @@ pub(crate) async fn key_images_spent(
unreachable!(); unreachable!();
}; };
Ok(is_spent) Ok(status)
} }
/// [`BlockchainReadRequest::CompactChainHistory`] /// [`BlockchainReadRequest::CompactChainHistory`]
@ -375,3 +377,20 @@ pub(crate) async fn alt_chain_count(
Ok(usize_to_u64(count)) Ok(usize_to_u64(count))
} }
/// [`BlockchainReadRequest::Transactions`].
pub(crate) async fn transactions(
blockchain_read: &mut BlockchainReadHandle,
tx_hashes: HashSet<[u8; 32]>,
) -> Result<(Vec<TxInBlockchain>, Vec<[u8; 32]>), Error> {
let BlockchainResponse::Transactions { txs, missed_txs } = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::Transactions { tx_hashes })
.await?
else {
unreachable!();
};
Ok((txs, missed_txs))
}

View file

@ -13,7 +13,10 @@ use cuprate_txpool::{
}, },
TxEntry, TxEntry,
}; };
use cuprate_types::rpc::{PoolInfo, PoolInfoFull, PoolInfoIncremental, PoolTxInfo}; use cuprate_types::{
rpc::{PoolInfo, PoolInfoFull, PoolInfoIncremental, PoolTxInfo},
TxInPool,
};
// FIXME: use `anyhow::Error` over `tower::BoxError` in txpool. // FIXME: use `anyhow::Error` over `tower::BoxError` in txpool.
@ -60,8 +63,8 @@ pub(crate) async fn pool_info(
include_sensitive_txs: bool, include_sensitive_txs: bool,
max_tx_count: usize, max_tx_count: usize,
start_time: Option<NonZero<usize>>, start_time: Option<NonZero<usize>>,
) -> Result<Vec<PoolInfo>, Error> { ) -> Result<PoolInfo, Error> {
let TxpoolReadResponse::PoolInfo(vec) = txpool_read let TxpoolReadResponse::PoolInfo(pool_info) = txpool_read
.ready() .ready()
.await .await
.map_err(|e| anyhow!(e))? .map_err(|e| anyhow!(e))?
@ -76,7 +79,53 @@ pub(crate) async fn pool_info(
unreachable!(); unreachable!();
}; };
Ok(vec) Ok(pool_info)
}
/// TODO
pub(crate) async fn txs_by_hash(
txpool_read: &mut TxpoolReadHandle,
tx_hashes: Vec<[u8; 32]>,
include_sensitive_txs: bool,
) -> Result<Vec<TxInPool>, Error> {
let TxpoolReadResponse::TxsByHash(txs_in_pool) = txpool_read
.ready()
.await
.map_err(|e| anyhow!(e))?
.call(TxpoolReadRequest::TxsByHash {
tx_hashes,
include_sensitive_txs,
})
.await
.map_err(|e| anyhow!(e))?
else {
unreachable!();
};
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<Vec<bool>, 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 /// TODO

View file

@ -232,16 +232,18 @@ where
}) })
})?; })?;
let BlockchainResponse::KeyImagesSpent(kis_spent) = database let BlockchainResponse::KeyImagesSpent(kis_status) = database
.ready() .ready()
.await? .await?
.call(BlockchainReadRequest::KeyImagesSpent(spent_kis)) .call(BlockchainReadRequest::KeyImagesSpent(
spent_kis.into_iter().collect(),
))
.await? .await?
else { else {
panic!("Database sent incorrect response!"); panic!("Database sent incorrect response!");
}; };
if kis_spent { if kis_status.contains(&true) {
tracing::debug!("One or more key images in batch already spent."); tracing::debug!("One or more key images in batch already spent.");
return Err(ConsensusError::Transaction(TransactionError::KeyImageSpent).into()); return Err(ConsensusError::Transaction(TransactionError::KeyImageSpent).into());
} }

View file

@ -45,7 +45,9 @@ fn dummy_database(outputs: BTreeMap<u64, OutputOnChain>) -> impl Database + Clon
BlockchainResponse::Outputs(ret) BlockchainResponse::Outputs(ret)
} }
BlockchainReadRequest::KeyImagesSpent(_) => BlockchainResponse::KeyImagesSpent(false), BlockchainReadRequest::KeyImagesSpent(_) => {
BlockchainResponse::KeyImagesSpent(vec![false])
}
_ => panic!("Database request not needed for this test"), _ => panic!("Database request not needed for this test"),
})) }))
}) })

View file

@ -14,7 +14,6 @@
//---------------------------------------------------------------------------------------------------- Mod //---------------------------------------------------------------------------------------------------- Mod
mod binary_string; mod binary_string;
mod distribution; mod distribution;
mod key_image_spent_status;
mod requested_info; mod requested_info;
mod status; mod status;
mod tx_entry; mod tx_entry;
@ -22,10 +21,9 @@ mod types;
pub use binary_string::BinaryString; pub use binary_string::BinaryString;
pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed}; pub use distribution::{Distribution, DistributionCompressedBinary, DistributionUncompressed};
pub use key_image_spent_status::KeyImageSpentStatus;
pub use requested_info::RequestedInfo; pub use requested_info::RequestedInfo;
pub use status::Status; pub use status::Status;
pub use tx_entry::TxEntry; pub use tx_entry::{TxEntry, TxEntryType};
pub use types::{ pub use types::{
BlockHeader, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, HistogramEntry, OutKeyBin, BlockHeader, ChainInfo, ConnectionInfo, GetBan, GetOutputsOut, HistogramEntry, OutKeyBin,
SetBan, Span, SpentKeyImageInfo, SyncInfoPeer, TxInfo, SetBan, Span, SpentKeyImageInfo, SyncInfoPeer, TxInfo,

View file

@ -11,6 +11,8 @@ use cuprate_epee_encoding::{
EpeeObject, EpeeObjectBuilder, EpeeObject, EpeeObjectBuilder,
}; };
use cuprate_hex::Hex;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use crate::serde::{serde_false, serde_true}; use crate::serde::{serde_false, serde_true};
@ -28,100 +30,63 @@ use crate::serde::{serde_false, serde_true};
/// ///
/// It is only implemented to satisfy the RPC type generator /// It is only implemented to satisfy the RPC type generator
/// macro, which requires all objects to be serde + epee. /// macro, which requires all objects to be serde + epee.
/// #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// # Example #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
/// ```rust pub struct TxEntry {
/// use cuprate_rpc_types::misc::*; /// `cuprate_types::json::tx::Transaction` should be used
/// use serde_json::{json, from_value}; /// to create this JSON string in a type-safe manner.
/// pub as_json: String,
/// let json = json!({
/// "as_hex": String::default(), pub as_hex: String,
/// "as_json": String::default(), pub double_spend_seen: bool,
/// "block_height": u64::default(), pub tx_hash: Hex<32>,
/// "block_timestamp": u64::default(), pub prunable_as_hex: String,
/// "confirmations": u64::default(), pub prunable_hash: Hex<32>,
/// "double_spend_seen": bool::default(), pub pruned_as_hex: String,
/// "output_indices": Vec::<u64>::default(),
/// "prunable_as_hex": String::default(), #[cfg_attr(feature = "serde", serde(flatten))]
/// "prunable_hash": String::default(), pub tx_entry_type: TxEntryType,
/// "pruned_as_hex": String::default(), }
/// "tx_hash": String::default(),
/// "in_pool": bool::default(), /// Different fields in [`TxEntry`] variants.
/// });
/// let tx_entry = from_value::<TxEntry>(json).unwrap();
/// assert!(matches!(tx_entry, TxEntry::InPool { .. }));
///
/// let json = json!({
/// "as_hex": String::default(),
/// "as_json": String::default(),
/// "double_spend_seen": bool::default(),
/// "prunable_as_hex": String::default(),
/// "prunable_hash": String::default(),
/// "pruned_as_hex": String::default(),
/// "received_timestamp": u64::default(),
/// "relayed": bool::default(),
/// "tx_hash": String::default(),
/// "in_pool": bool::default(),
/// });
/// let tx_entry = from_value::<TxEntry>(json).unwrap();
/// assert!(matches!(tx_entry, TxEntry::NotInPool { .. }));
/// ```
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))] #[cfg_attr(feature = "serde", serde(untagged))]
pub enum TxEntry { pub enum TxEntryType {
/// This entry exists in the transaction pool. /// This transaction exists in the blockchain.
InPool { Blockchain {
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten).
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TxEntryPrefix,
block_height: u64, block_height: u64,
block_timestamp: u64, block_timestamp: u64,
confirmations: u64, confirmations: u64,
output_indices: Vec<u64>, output_indices: Vec<u64>,
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
/// Will always be serialized as `true`. /// Will always be serialized as `false`.
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))]
in_pool: bool, in_pool: bool,
}, },
/// This entry _does not_ exist in the transaction pool.
NotInPool { /// This transaction exists in the transaction pool.
/// This field is [flattened](https://serde.rs/field-attrs.html#flatten). Pool {
#[cfg_attr(feature = "serde", serde(flatten))]
prefix: TxEntryPrefix,
received_timestamp: u64, received_timestamp: u64,
relayed: bool, relayed: bool,
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_false"))]
/// Will always be serialized as `false`. /// Will always be serialized as `true`.
#[cfg_attr(feature = "serde", serde(serialize_with = "serde_true"))]
in_pool: bool, in_pool: bool,
}, },
} }
impl Default for TxEntry { impl Default for TxEntryType {
fn default() -> Self { fn default() -> Self {
Self::NotInPool { Self::Blockchain {
prefix: Default::default(), block_height: Default::default(),
received_timestamp: u64::default(), block_timestamp: Default::default(),
relayed: bool::default(), confirmations: Default::default(),
in_pool: false, output_indices: Default::default(),
in_pool: Default::default(),
} }
} }
} }
/// Common fields in all [`TxEntry`] variants.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TxEntryPrefix {
as_hex: String,
/// `cuprate_rpc_types::json::tx::Transaction` should be used
/// to create this JSON string in a type-safe manner.
as_json: String,
double_spend_seen: bool,
tx_hash: String,
prunable_as_hex: String,
prunable_hash: String,
pruned_as_hex: String,
}
//---------------------------------------------------------------------------------------------------- Epee //---------------------------------------------------------------------------------------------------- Epee
#[cfg(feature = "epee")] #[cfg(feature = "epee")]
impl EpeeObjectBuilder<TxEntry> for () { impl EpeeObjectBuilder<TxEntry> for () {

File diff suppressed because one or more lines are too long

View file

@ -134,6 +134,7 @@ fn map_request(
R::CoinbaseTxSum { height, count } => coinbase_tx_sum(env, height, count), R::CoinbaseTxSum { height, count } => coinbase_tx_sum(env, height, count),
R::AltChains => alt_chains(env), R::AltChains => alt_chains(env),
R::AltChainCount => alt_chain_count(env), R::AltChainCount => alt_chain_count(env),
R::Transactions { tx_hashes } => transactions(env, tx_hashes),
} }
/* SOMEDAY: post-request handling, run some code for each request? */ /* SOMEDAY: post-request handling, run some code for each request? */
@ -506,7 +507,7 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> Respon
/// [`BlockchainReadRequest::KeyImagesSpent`]. /// [`BlockchainReadRequest::KeyImagesSpent`].
#[inline] #[inline]
fn key_images_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> ResponseResult { fn key_images_spent(env: &ConcreteEnv, key_images: Vec<KeyImage>) -> ResponseResult {
// Prepare tx/tables in `ThreadLocal`. // Prepare tx/tables in `ThreadLocal`.
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let tx_ro = thread_local(env); let tx_ro = thread_local(env);
@ -519,26 +520,13 @@ fn key_images_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> Respons
key_image_exists(&key_image, tables.key_images()) key_image_exists(&key_image, tables.key_images())
}; };
// FIXME:
// Create/use `enum cuprate_types::Exist { Does, DoesNot }`
// or similar instead of `bool` for clarity.
// <https://github.com/Cuprate/cuprate/pull/113#discussion_r1581536526>
//
// Collect results using `rayon`. // Collect results using `rayon`.
match key_images Ok(BlockchainResponse::KeyImagesSpent(
.into_par_iter() key_images
.map(key_image_exists) .into_par_iter()
// If the result is either: .map(key_image_exists)
// `Ok(true)` => a key image was found, return early .collect::<DbResult<_>>()?,
// `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::CompactChainHistory`] /// [`BlockchainReadRequest::CompactChainHistory`]
@ -783,3 +771,11 @@ fn alt_chains(env: &ConcreteEnv) -> ResponseResult {
fn alt_chain_count(env: &ConcreteEnv) -> ResponseResult { fn alt_chain_count(env: &ConcreteEnv) -> ResponseResult {
Ok(BlockchainResponse::AltChainCount(todo!())) Ok(BlockchainResponse::AltChainCount(todo!()))
} }
/// [`BlockchainReadRequest::Transactions`]
fn transactions(env: &ConcreteEnv, tx_hashes: HashSet<[u8; 32]>) -> ResponseResult {
Ok(BlockchainResponse::Transactions {
txs: todo!(),
missed_txs: todo!(),
})
}

View file

@ -179,8 +179,8 @@ async fn test_template(
)); ));
// Contains a fake non-spent key-image. // Contains a fake non-spent key-image.
let ki_req = HashSet::from([[0; 32]]); let ki_req = vec![[0; 32]];
let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(false)); let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(vec![false]));
//----------------------------------------------------------------------- Assert expected response //----------------------------------------------------------------------- Assert expected response
// Assert read requests lead to the expected responses. // 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". // Assert each key image we inserted comes back as "spent".
for key_image in tables.key_images_iter().keys().unwrap() { for key_image in tables.key_images_iter().keys().unwrap() {
let key_image = key_image.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; let response = reader.clone().oneshot(request).await;
println!("response: {response:#?}, key_image: {key_image:#?}"); println!("response: {response:#?}, key_image: {key_image:#?}");
assert_eq!(response.unwrap(), BlockchainResponse::KeyImagesSpent(true)); assert_eq!(
response.unwrap(),
BlockchainResponse::KeyImagesSpent(vec![true])
);
} }
//----------------------------------------------------------------------- Output checks //----------------------------------------------------------------------- Output checks

View file

@ -7,7 +7,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use cuprate_types::{rpc::PoolInfo, TransactionVerificationData}; use cuprate_types::{rpc::PoolInfo, TransactionVerificationData, TxInPool};
use crate::{ use crate::{
tx::TxEntry, tx::TxEntry,
@ -52,6 +52,18 @@ pub enum TxpoolReadRequest {
/// TODO /// TODO
start_time: Option<NonZero<usize>>, start_time: Option<NonZero<usize>>,
}, },
/// TODO
TxsByHash {
tx_hashes: Vec<[u8; 32]>,
include_sensitive_txs: bool,
},
/// TODO
KeyImagesSpent {
key_images: Vec<[u8; 32]>,
include_sensitive_txs: bool,
},
} }
//---------------------------------------------------------------------------------------------------- TxpoolReadResponse //---------------------------------------------------------------------------------------------------- TxpoolReadResponse
@ -93,7 +105,13 @@ pub enum TxpoolReadResponse {
Size(usize), Size(usize),
/// Response to [`TxpoolReadRequest::PoolInfo`]. /// Response to [`TxpoolReadRequest::PoolInfo`].
PoolInfo(Vec<PoolInfo>), PoolInfo(PoolInfo),
/// Response to [`TxpoolReadRequest::TxsByHash`].
TxsByHash(Vec<TxInPool>),
/// Response to [`TxpoolReadRequest::KeyImagesSpent`].
KeyImagesSpent(Vec<bool>),
} }
//---------------------------------------------------------------------------------------------------- TxpoolWriteRequest //---------------------------------------------------------------------------------------------------- TxpoolWriteRequest

View file

@ -2,6 +2,7 @@
unreachable_code, unreachable_code,
unused_variables, unused_variables,
clippy::unnecessary_wraps, clippy::unnecessary_wraps,
clippy::needless_pass_by_value,
reason = "TODO: finish implementing the signatures from <https://github.com/Cuprate/cuprate/pull/297>" reason = "TODO: finish implementing the signatures from <https://github.com/Cuprate/cuprate/pull/297>"
)] )]
use std::{ use std::{
@ -79,7 +80,15 @@ fn map_request(
include_sensitive_txs, include_sensitive_txs,
max_tx_count, max_tx_count,
start_time, start_time,
} => pool_info(include_sensitive_txs, max_tx_count, start_time), } => pool_info(env, include_sensitive_txs, max_tx_count, start_time),
TxpoolReadRequest::TxsByHash {
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),
} }
} }
@ -215,9 +224,28 @@ fn size(env: &ConcreteEnv, include_sensitive_txs: bool) -> ReadResponseResult {
/// [`TxpoolReadRequest::PoolInfo`]. /// [`TxpoolReadRequest::PoolInfo`].
fn pool_info( fn pool_info(
env: &ConcreteEnv,
include_sensitive_txs: bool, include_sensitive_txs: bool,
max_tx_count: usize, max_tx_count: usize,
start_time: Option<NonZero<usize>>, start_time: Option<NonZero<usize>>,
) -> ReadResponseResult { ) -> ReadResponseResult {
Ok(TxpoolReadResponse::PoolInfo(todo!())) Ok(TxpoolReadResponse::PoolInfo(todo!()))
} }
/// [`TxpoolReadRequest::TxsByHash`].
fn txs_by_hash(
env: &ConcreteEnv,
tx_hashes: Vec<[u8; 32]>,
include_sensitive_txs: bool,
) -> 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!()))
}

View file

@ -11,12 +11,11 @@ use std::{
use monero_serai::block::Block; use monero_serai::block::Block;
use crate::{ use crate::{
rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput},
types::{Chain, ExtendedBlockHeader, OutputOnChain, TxsInBlock, VerifiedBlockInformation}, types::{Chain, ExtendedBlockHeader, OutputOnChain, TxsInBlock, VerifiedBlockInformation},
AltBlockInformation, BlockCompleteEntry, ChainId, AltBlockInformation, BlockCompleteEntry, ChainId, TxInBlockchain,
}; };
use crate::rpc::{ChainInfo, CoinbaseTxSum, OutputHistogramEntry, OutputHistogramInput};
//---------------------------------------------------------------------------------------------------- ReadRequest //---------------------------------------------------------------------------------------------------- ReadRequest
/// A read request to the blockchain database. /// A read request to the blockchain database.
/// ///
@ -94,10 +93,10 @@ pub enum BlockchainReadRequest {
/// The input is a list of output amounts. /// The input is a list of output amounts.
NumberOutputsWithAmount(Vec<u64>), NumberOutputsWithAmount(Vec<u64>),
/// 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. /// Input is a set of key images.
KeyImagesSpent(HashSet<[u8; 32]>), KeyImagesSpent(Vec<[u8; 32]>),
/// A request for the compact chain history. /// A request for the compact chain history.
CompactChainHistory, CompactChainHistory,
@ -161,6 +160,9 @@ pub enum BlockchainReadRequest {
/// Get the amount of alternative chains that exist. /// Get the amount of alternative chains that exist.
AltChainCount, AltChainCount,
/// TODO
Transactions { tx_hashes: HashSet<[u8; 32]> },
} }
//---------------------------------------------------------------------------------------------------- WriteRequest //---------------------------------------------------------------------------------------------------- WriteRequest
@ -268,11 +270,11 @@ pub enum BlockchainResponse {
/// Response to [`BlockchainReadRequest::KeyImagesSpent`]. /// Response to [`BlockchainReadRequest::KeyImagesSpent`].
/// ///
/// The inner value is `true` if _any_ of the key images /// Inner value is a `Vec` the same length as the input.
/// were spent (existed in the database already).
/// ///
/// The inner value is `false` if _none_ of the key images were spent. /// The index of each entry corresponds with the request.
KeyImagesSpent(bool), /// `true` means that the key image was spent.
KeyImagesSpent(Vec<bool>),
/// Response to [`BlockchainReadRequest::CompactChainHistory`]. /// Response to [`BlockchainReadRequest::CompactChainHistory`].
CompactChainHistory { CompactChainHistory {
@ -348,6 +350,14 @@ pub enum BlockchainResponse {
/// Response to [`BlockchainReadRequest::AltChainCount`]. /// Response to [`BlockchainReadRequest::AltChainCount`].
AltChainCount(usize), AltChainCount(usize),
/// Response to [`BlockchainReadRequest::Transactions`].
Transactions {
/// The transaction blobs found.
txs: Vec<TxInBlockchain>,
/// The hashes of any transactions that could not be found.
missed_txs: Vec<[u8; 32]>,
},
//------------------------------------------------------ Writes //------------------------------------------------------ Writes
/// A generic Ok response to indicate a request was successfully handled. /// A generic Ok response to indicate a request was successfully handled.
/// ///

View file

@ -25,7 +25,7 @@ pub use transaction_verification_data::{
}; };
pub use types::{ pub use types::{
AltBlockInformation, BlockTemplate, Chain, ChainId, ExtendedBlockHeader, OutputOnChain, AltBlockInformation, BlockTemplate, Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
TxsInBlock, VerifiedBlockInformation, VerifiedTransactionInformation, TxInBlockchain, TxInPool, TxsInBlock, VerifiedBlockInformation, VerifiedTransactionInformation,
}; };
//---------------------------------------------------------------------------------------------------- Feature-gated //---------------------------------------------------------------------------------------------------- Feature-gated

View file

@ -12,12 +12,7 @@ use cuprate_epee_encoding::{
}; };
//---------------------------------------------------------------------------------------------------- KeyImageSpentStatus //---------------------------------------------------------------------------------------------------- KeyImageSpentStatus
#[doc = crate::macros::monero_definition_link!( /// Used in RPC's `/is_key_image_spent`.
"cc73fe71162d564ffda8e549b79a350bca53c454",
"rpc/core_rpc_server_commands_defs.h",
456..=460
)]
/// Used in [`crate::other::IsKeyImageSpentResponse`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "u8", into = "u8"))] #[cfg_attr(feature = "serde", serde(try_from = "u8", into = "u8"))]
@ -32,7 +27,7 @@ impl KeyImageSpentStatus {
/// Convert [`Self`] to a [`u8`]. /// Convert [`Self`] to a [`u8`].
/// ///
/// ```rust /// ```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::Unspent.to_u8(), 0);
/// assert_eq!(K::SpentInBlockchain.to_u8(), 1); /// assert_eq!(K::SpentInBlockchain.to_u8(), 1);
@ -52,7 +47,7 @@ impl KeyImageSpentStatus {
/// This returns [`None`] if `u > 2`. /// This returns [`None`] if `u > 2`.
/// ///
/// ```rust /// ```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(0), Some(K::Unspent));
/// assert_eq!(K::from_u8(1), Some(K::SpentInBlockchain)); /// assert_eq!(K::from_u8(1), Some(K::SpentInBlockchain));

View file

@ -5,10 +5,12 @@
//! instead of `hash: String`, this module's types would use something like //! instead of `hash: String`, this module's types would use something like
//! `hash: [u8; 32]`. //! `hash: [u8; 32]`.
mod key_image_spent_status;
mod pool_info; mod pool_info;
mod pool_info_extent; mod pool_info_extent;
mod types; mod types;
pub use key_image_spent_status::KeyImageSpentStatus;
pub use pool_info::PoolInfo; pub use pool_info::PoolInfo;
pub use pool_info_extent::PoolInfoExtent; pub use pool_info_extent::PoolInfoExtent;
pub use types::{ pub use types::{

View file

@ -167,6 +167,30 @@ pub struct BlockTemplate {
pub next_seed_hash: [u8; 32], pub next_seed_hash: [u8; 32],
} }
/// TODO
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxInBlockchain {
pub block_height: u64,
pub block_timestamp: u64,
pub confirmations: u64,
pub output_indices: Vec<u64>,
pub tx_hash: [u8; 32],
pub tx_blob: Vec<u8>,
pub pruned_blob: Vec<u8>,
pub prunable_blob: Vec<u8>,
pub prunable_hash: [u8; 32],
}
/// TODO
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TxInPool {
pub tx_blob: Vec<u8>,
pub tx_hash: [u8; 32],
pub double_spend_seen: bool,
pub received_timestamp: u64,
pub relayed: bool,
}
//---------------------------------------------------------------------------------------------------- Tests //---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)] #[cfg(test)]
mod test { mod test {