Skip to content

Commit

Permalink
most of /send_raw_transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
hinto-janai committed Dec 13, 2024
1 parent c38daa4 commit d4b3033
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions binaries/cuprated/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub const VERSION_BUILD: &str = if cfg!(debug_assertions) {
pub const PANIC_CRITICAL_SERVICE_ERROR: &str =
"A service critical to Cuprate's function returned an unexpected error.";

/// The error message returned when an unsupported RPC call is requested.
pub const UNSUPPORTED_RPC_CALL: &str = "This RPC call is not supported by Cuprate.";

pub const EXAMPLE_CONFIG: &str = include_str!("../Cuprated.toml");

#[cfg(test)]
Expand Down
7 changes: 4 additions & 3 deletions binaries/cuprated/src/rpc/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use cuprate_types::{
};

use crate::{
constants::VERSION_BUILD,
constants::{UNSUPPORTED_RPC_CALL, VERSION_BUILD},
rpc::{
helper,
request::{address_book, blockchain, blockchain_context, blockchain_manager, txpool},
Expand Down Expand Up @@ -122,9 +122,10 @@ pub(super) async fn map_request(
Req::GetMinerData(r) => Resp::GetMinerData(get_miner_data(state, r).await?),
Req::PruneBlockchain(r) => Resp::PruneBlockchain(prune_blockchain(state, r).await?),
Req::CalcPow(r) => Resp::CalcPow(calc_pow(state, r).await?),
Req::FlushCache(r) => Resp::FlushCache(flush_cache(state, r).await?),
Req::AddAuxPow(r) => Resp::AddAuxPow(add_aux_pow(state, r).await?),
Req::GetTxIdsLoose(r) => Resp::GetTxIdsLoose(get_tx_ids_loose(state, r).await?),

// Unsupported RPC calls.
Req::GetTxIdsLoose(_) | Req::FlushCache(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
})
}

Expand Down
144 changes: 132 additions & 12 deletions binaries/cuprated/src/rpc/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,16 @@ use cuprate_rpc_types::{
};
use cuprate_types::{
rpc::{KeyImageSpentStatus, OutKey, PoolInfo, PoolTxInfo},
TxInPool,
TxInPool, TxRelayChecks,
};
use monero_serai::transaction::Transaction;
use monero_serai::transaction::{Input, Transaction};

use crate::{
rpc::CupratedRpcHandler,
constants::UNSUPPORTED_RPC_CALL,
rpc::{
helper,
request::{blockchain, blockchain_context, blockchain_manager, txpool},
CupratedRpcHandler,
},
};

Expand Down Expand Up @@ -84,20 +85,18 @@ pub(super) async fn map_request(
Req::InPeers(r) => Resp::InPeers(in_peers(state, r).await?),
Req::GetNetStats(r) => Resp::GetNetStats(get_net_stats(state, r).await?),
Req::GetOuts(r) => Resp::GetOuts(get_outs(state, r).await?),
Req::Update(r) => Resp::Update(update(state, r).await?),
Req::PopBlocks(r) => Resp::PopBlocks(pop_blocks(state, r).await?),
Req::GetTransactionPoolHashes(r) => {
Resp::GetTransactionPoolHashes(get_transaction_pool_hashes(state, r).await?)
}
Req::GetPublicNodes(r) => Resp::GetPublicNodes(get_public_nodes(state, r).await?),

// Unsupported requests.
Req::StartMining(_)
Req::Update(_)
| Req::StartMining(_)
| Req::StopMining(_)
| Req::MiningStatus(_)
| Req::SetLogHashRate(_) => {
return Err(anyhow!("Mining RPC calls are not supported by Cuprate"))
}
| Req::SetLogHashRate(_) => return Err(anyhow!(UNSUPPORTED_RPC_CALL)),
})
}

Expand Down Expand Up @@ -329,13 +328,134 @@ async fn is_key_image_spent(

/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1307-L1411>
async fn send_raw_transaction(
state: CupratedRpcHandler,
mut state: CupratedRpcHandler,
request: SendRawTransactionRequest,
) -> Result<SendRawTransactionResponse, Error> {
Ok(SendRawTransactionResponse {
let mut resp = SendRawTransactionResponse {
base: AccessResponseBase::OK,
..todo!()
})
double_spend: false,
fee_too_low: false,
invalid_input: false,
invalid_output: false,
low_mixin: false,
nonzero_unlock_time: false,
not_relayed: request.do_not_relay,
overspend: false,
reason: String::new(),
sanity_check_failed: false,
too_big: false,
too_few_outputs: false,
tx_extra_too_big: false,
};

let tx = {
let blob = hex::decode(request.tx_as_hex)?;
Transaction::read(&mut blob.as_slice())?
};

if request.do_sanity_checks {
// FIXME: these checks could be defined elsewhere.
//
// <https://github.com/monero-project/monero/blob/893916ad091a92e765ce3241b94e706ad012b62a/src/cryptonote_core/tx_sanity_check.cpp#L42>
fn tx_sanity_check(tx: &Transaction, rct_outs_available: u64) -> Result<(), &'static str> {
let Some(input) = tx.prefix().inputs.get(0) else {
return Err("No inputs");
};

let mut rct_indices = BTreeSet::new();
let n_indices: usize = 0;

for input in tx.prefix().inputs {
match input {
Input::Gen(_) => return Err("Transaction is coinbase"),
Input::ToKey {
amount,
key_offsets,
key_image,
} => {
let Some(amount) = amount else {
continue;
};

n_indices += key_offsets.len();
let absolute = todo!();
rct_indices.extend(absolute);
}
}
}

if n_indices <= 10 {
return Ok(());
}

if rct_outs_available < 10_000 {
return Ok(());
}

let rct_indices_len = rct_indices.len();
if rct_indices_len < n_indices * 8 / 10 {
return Err("amount of unique indices is too low (amount of rct indices is {rct_indices_len} out of total {n_indices} indices.");
}

let offsets = Vec::with_capacity(rct_indices_len);
let median = todo!();
if median < rct_outs_available * 6 / 10 {
return Err("median offset index is too low (median is {median} out of total {rct_outs_available} offsets). Transactions should contain a higher fraction of recent outputs.");
}

Ok(())
}

let rct_outs_available = blockchain::total_rct_outputs(&mut state.blockchain_read).await?;

if let Err(e) = tx_sanity_check(&tx, rct_outs_available) {
resp.base.response_base.status = Status::Failed;
resp.reason.push_str(&format!("Sanity check failed: {e}"));
resp.sanity_check_failed = true;
return Ok(resp);
}
}

let tx_relay_checks =
txpool::check_maybe_relay_local(&mut state.txpool_manager, tx, !request.do_not_relay)
.await?;

if tx_relay_checks.is_empty() {
return Ok(resp);
}

// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L124>
fn add_reason(reasons: &mut String, reason: &'static str) {
if !reasons.is_empty() {
reasons.push_str(", ");
}
reasons.push_str(reason);
}

let mut reasons = String::new();

#[rustfmt::skip]
let array = [
(&mut resp.double_spend, TxRelayChecks::DOUBLE_SPEND, "double spend"),
(&mut resp.fee_too_low, TxRelayChecks::FEE_TOO_LOW, "fee too low"),
(&mut resp.invalid_input, TxRelayChecks::INVALID_INPUT, "invalid input"),
(&mut resp.invalid_output, TxRelayChecks::INVALID_OUTPUT, "invalid output"),
(&mut resp.low_mixin, TxRelayChecks::LOW_MIXIN, "bad ring size"),
(&mut resp.nonzero_unlock_time, TxRelayChecks::NONZERO_UNLOCK_TIME, "tx unlock time is not zero"),
(&mut resp.overspend, TxRelayChecks::OVERSPEND, "overspend"),
(&mut resp.too_big, TxRelayChecks::TOO_BIG, "too big"),
(&mut resp.too_few_outputs, TxRelayChecks::TOO_FEW_OUTPUTS, "too few outputs"),
(&mut resp.tx_extra_too_big, TxRelayChecks::TX_EXTRA_TOO_BIG, "tx-extra too big"),
];

for (field, flag, reason) in array {
if tx_relay_checks.contains(flag) {
*field = true;
add_reason(&mut reasons, reason);
}
}

Ok(resp)
}

/// <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.cpp#L1413-L1462>
Expand Down
16 changes: 16 additions & 0 deletions binaries/cuprated/src/rpc/request/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,19 @@ pub(crate) async fn transactions(

Ok((txs, missed_txs))
}

/// [`BlockchainReadRequest::TotalRctOutputs`].
pub(crate) async fn total_rct_outputs(
blockchain_read: &mut BlockchainReadHandle,
) -> Result<u64, Error> {
let BlockchainResponse::TotalRctOutputs(total_rct_outputs) = blockchain_read
.ready()
.await?
.call(BlockchainReadRequest::TotalRctOutputs)
.await?
else {
unreachable!();
};

Ok(total_rct_outputs)
}
12 changes: 11 additions & 1 deletion binaries/cuprated/src/rpc/request/txpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use std::{convert::Infallible, num::NonZero};

use anyhow::{anyhow, Error};
use monero_serai::transaction::Transaction;
use tower::{Service, ServiceExt};

use cuprate_helper::cast::usize_to_u64;
Expand All @@ -15,7 +16,7 @@ use cuprate_txpool::{
};
use cuprate_types::{
rpc::{PoolInfo, PoolInfoFull, PoolInfoIncremental, PoolTxInfo},
TxInPool,
TxInPool, TxRelayChecks,
};

// FIXME: use `anyhow::Error` over `tower::BoxError` in txpool.
Expand Down Expand Up @@ -145,3 +146,12 @@ pub(crate) async fn relay(
todo!();
Ok(())
}

/// TODO
pub(crate) async fn check_maybe_relay_local(
txpool_manager: &mut Infallible,
tx: Transaction,
relay: bool,
) -> Result<TxRelayChecks, Error> {
Ok(todo!())
}
3 changes: 3 additions & 0 deletions rpc/types/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub const CORE_RPC_STATUS_NOT_MINING: &str = "NOT MINING";
#[doc = monero_definition_link!("cc73fe71162d564ffda8e549b79a350bca53c454", "/rpc/core_rpc_server_commands_defs.h", 81)]
pub const CORE_RPC_STATUS_PAYMENT_REQUIRED: &str = "PAYMENT REQUIRED";

/// Not defined in `monerod` although used frequently.
pub const CORE_RPC_STATUS_FAILED: &str = "Failed";

//---------------------------------------------------------------------------------------------------- Versions
#[doc = monero_definition_link!("cc73fe71162d564ffda8e549b79a350bca53c454", "/rpc/core_rpc_server_commands_defs.h", 90)]
/// RPC major version.
Expand Down
2 changes: 1 addition & 1 deletion rpc/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub mod misc;
pub mod other;

pub use constants::{
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_FAILED, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_PAYMENT_REQUIRED, CORE_RPC_VERSION, CORE_RPC_VERSION_MAJOR,
CORE_RPC_VERSION_MINOR,
};
Expand Down
12 changes: 11 additions & 1 deletion rpc/types/src/misc/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use cuprate_epee_encoding::{
};

use crate::constants::{
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_BUSY, CORE_RPC_STATUS_FAILED, CORE_RPC_STATUS_NOT_MINING, CORE_RPC_STATUS_OK,
CORE_RPC_STATUS_PAYMENT_REQUIRED,
};

Expand All @@ -40,24 +40,28 @@ use crate::constants::{
/// let other = Status::Other("OTHER".into());
///
/// assert_eq!(to_string(&Status::Ok).unwrap(), r#""OK""#);
/// assert_eq!(to_string(&Status::Failed).unwrap(), r#""Failed""#);
/// assert_eq!(to_string(&Status::Busy).unwrap(), r#""BUSY""#);
/// assert_eq!(to_string(&Status::NotMining).unwrap(), r#""NOT MINING""#);
/// assert_eq!(to_string(&Status::PaymentRequired).unwrap(), r#""PAYMENT REQUIRED""#);
/// assert_eq!(to_string(&other).unwrap(), r#""OTHER""#);
///
/// assert_eq!(Status::Ok.as_ref(), CORE_RPC_STATUS_OK);
/// assert_eq!(Status::Failed.as_ref(), CORE_RPC_STATUS_FAILED);
/// assert_eq!(Status::Busy.as_ref(), CORE_RPC_STATUS_BUSY);
/// assert_eq!(Status::NotMining.as_ref(), CORE_RPC_STATUS_NOT_MINING);
/// assert_eq!(Status::PaymentRequired.as_ref(), CORE_RPC_STATUS_PAYMENT_REQUIRED);
/// assert_eq!(other.as_ref(), "OTHER");
///
/// assert_eq!(format!("{}", Status::Ok), CORE_RPC_STATUS_OK);
/// assert_eq!(format!("{}", Status::Failed), CORE_RPC_STATUS_FAILED);
/// assert_eq!(format!("{}", Status::Busy), CORE_RPC_STATUS_BUSY);
/// assert_eq!(format!("{}", Status::NotMining), CORE_RPC_STATUS_NOT_MINING);
/// assert_eq!(format!("{}", Status::PaymentRequired), CORE_RPC_STATUS_PAYMENT_REQUIRED);
/// assert_eq!(format!("{}", other), "OTHER");
///
/// assert_eq!(format!("{:?}", Status::Ok), "Ok");
/// assert_eq!(format!("{:?}", Status::Failed), "Failed");
/// assert_eq!(format!("{:?}", Status::Busy), "Busy");
/// assert_eq!(format!("{:?}", Status::NotMining), "NotMining");
/// assert_eq!(format!("{:?}", Status::PaymentRequired), "PaymentRequired");
Expand All @@ -74,6 +78,10 @@ pub enum Status {
#[default]
Ok,

/// Generic request failure.
#[cfg_attr(feature = "serde", serde(rename = "Failed"))]
Failed,

/// The daemon is busy, try later; [`CORE_RPC_STATUS_BUSY`].
#[cfg_attr(feature = "serde", serde(rename = "BUSY"))]
Busy,
Expand Down Expand Up @@ -101,6 +109,7 @@ impl From<String> for Status {
CORE_RPC_STATUS_BUSY => Self::Busy,
CORE_RPC_STATUS_NOT_MINING => Self::NotMining,
CORE_RPC_STATUS_PAYMENT_REQUIRED => Self::PaymentRequired,
CORE_RPC_STATUS_FAILED => Self::Failed,
_ => Self::Other(s),
}
}
Expand All @@ -110,6 +119,7 @@ impl AsRef<str> for Status {
fn as_ref(&self) -> &str {
match self {
Self::Ok => CORE_RPC_STATUS_OK,
Self::Failed => CORE_RPC_STATUS_FAILED,
Self::Busy => CORE_RPC_STATUS_BUSY,
Self::NotMining => CORE_RPC_STATUS_NOT_MINING,
Self::PaymentRequired => CORE_RPC_STATUS_PAYMENT_REQUIRED,
Expand Down
15 changes: 14 additions & 1 deletion storage/blockchain/src/service/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ use crate::{
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
types::{BlockchainReadHandle, ResponseResult},
},
tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables, TablesIter},
tables::{
AltBlockHeights, BlockHeights, BlockInfos, OpenTables, RctOutputs, Tables, TablesIter,
},
types::{
AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId,
},
Expand Down Expand Up @@ -135,6 +137,7 @@ fn map_request(
R::AltChains => alt_chains(env),
R::AltChainCount => alt_chain_count(env),
R::Transactions { tx_hashes } => transactions(env, tx_hashes),
R::TotalRctOutputs => total_rct_outputs(env),
}

/* SOMEDAY: post-request handling, run some code for each request? */
Expand Down Expand Up @@ -779,3 +782,13 @@ fn transactions(env: &ConcreteEnv, tx_hashes: HashSet<[u8; 32]>) -> ResponseResu
missed_txs: todo!(),
})
}

/// [`BlockchainReadRequest::TotalRctOutputs`]
fn total_rct_outputs(env: &ConcreteEnv) -> ResponseResult {
// Single-threaded, no `ThreadLocal` required.
let env_inner = env.env_inner();
let tx_ro = env_inner.tx_ro()?;
let len = env_inner.open_db_ro::<RctOutputs>(&tx_ro)?.len()?;

Ok(BlockchainResponse::TotalRctOutputs(len))
}
1 change: 1 addition & 0 deletions types/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cuprate-helper = { workspace = true, optional = true, features = ["cast"]
cuprate-fixed-bytes = { workspace = true, features = ["std", "serde"] }
cuprate-hex = { workspace = true, optional = true }

bitflags = { workspace = true }
bytes = { workspace = true }
cfg-if = { workspace = true }
curve25519-dalek = { workspace = true }
Expand Down
Loading

0 comments on commit d4b3033

Please sign in to comment.