Document the RPC

This commit is contained in:
Luke Parker
2024-06-16 19:59:25 -04:00
parent d740bd2924
commit 08b95abdd8
20 changed files with 289 additions and 197 deletions

View File

@@ -1,6 +1,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
// #![deny(missing_docs)] // TODO #![deny(missing_docs)]
use std::{sync::Arc, io::Read, time::Duration}; use std::{sync::Arc, io::Read, time::Duration};
@@ -14,7 +14,7 @@ use simple_request::{
Response, Client, Response, Client,
}; };
use monero_rpc::{RpcError, RpcConnection, Rpc}; use monero_rpc::{RpcError, Rpc};
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
@@ -64,7 +64,7 @@ impl SimpleRequestRpc {
/// ///
/// A daemon requiring authentication can be used via including the username and password in the /// A daemon requiring authentication can be used via including the username and password in the
/// URL. /// URL.
pub async fn new(url: String) -> Result<Rpc<SimpleRequestRpc>, RpcError> { pub async fn new(url: String) -> Result<SimpleRequestRpc, RpcError> {
Self::with_custom_timeout(url, DEFAULT_TIMEOUT).await Self::with_custom_timeout(url, DEFAULT_TIMEOUT).await
} }
@@ -75,7 +75,7 @@ impl SimpleRequestRpc {
pub async fn with_custom_timeout( pub async fn with_custom_timeout(
mut url: String, mut url: String,
request_timeout: Duration, request_timeout: Duration,
) -> Result<Rpc<SimpleRequestRpc>, RpcError> { ) -> Result<SimpleRequestRpc, RpcError> {
let authentication = if url.contains('@') { let authentication = if url.contains('@') {
// Parse out the username and password // Parse out the username and password
let url_clone = url; let url_clone = url;
@@ -123,7 +123,7 @@ impl SimpleRequestRpc {
Authentication::Unauthenticated(Client::with_connection_pool()) Authentication::Unauthenticated(Client::with_connection_pool())
}; };
Ok(Rpc::new(SimpleRequestRpc { authentication, url, request_timeout })) Ok(SimpleRequestRpc { authentication, url, request_timeout })
} }
} }
@@ -281,7 +281,7 @@ impl SimpleRequestRpc {
} }
#[async_trait] #[async_trait]
impl RpcConnection for SimpleRequestRpc { impl Rpc for SimpleRequestRpc {
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> { async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError> {
tokio::time::timeout(self.request_timeout, self.inner_post(route, body)) tokio::time::timeout(self.request_timeout, self.inner_post(route, body))
.await .await

View File

@@ -1,6 +1,6 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
// #![deny(missing_docs)] // TODO #![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
use core::fmt::Debug; use core::fmt::Debug;
@@ -33,35 +33,63 @@ use monero_serai::{
// src/wallet/wallet2.cpp#L121 // src/wallet/wallet2.cpp#L121
const GRACE_BLOCKS_FOR_FEE_ESTIMATE: u64 = 10; const GRACE_BLOCKS_FOR_FEE_ESTIMATE: u64 = 10;
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes. /// A struct containing a fee rate.
///
/// The fee rate is defined as a per-weight cost, along with a mask for rounding purposes.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Fee { pub struct FeeRate {
/// The fee per-weight of the transaction.
pub per_weight: u64, pub per_weight: u64,
/// The mask to round with.
pub mask: u64, pub mask: u64,
} }
impl Fee { impl FeeRate {
/// Construct a new fee rate.
pub fn new(per_weight: u64, mask: u64) -> Result<FeeRate, RpcError> {
if (per_weight == 0) || (mask == 0) {
Err(RpcError::InvalidFee)?;
}
Ok(FeeRate { per_weight, mask })
}
/// Calculate the fee to use from the weight.
///
/// This function may panic if any of the `FeeRate`'s fields are zero.
pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 { pub fn calculate_fee_from_weight(&self, weight: usize) -> u64 {
let fee = (((self.per_weight * u64::try_from(weight).unwrap()) + self.mask - 1) / self.mask) * let fee = self.per_weight * u64::try_from(weight).unwrap();
self.mask; let fee = ((fee + self.mask - 1) / self.mask) * self.mask;
debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee"); debug_assert_eq!(weight, self.calculate_weight_from_fee(fee), "Miscalculated weight from fee");
fee fee
} }
/// Calculate the weight from the fee.
///
/// This function may panic if any of the `FeeRate`'s fields are zero.
pub fn calculate_weight_from_fee(&self, fee: u64) -> usize { pub fn calculate_weight_from_fee(&self, fee: u64) -> usize {
usize::try_from(fee / self.per_weight).unwrap() usize::try_from(fee / self.per_weight).unwrap()
} }
} }
/// Fee priority, determining how quickly a transaction is included in a block. /// The priority for the fee.
///
/// Higher-priority transactions will be included in blocks earlier.
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub enum FeePriority { pub enum FeePriority {
/// The `Unimportant` priority, as defined by Monero.
Unimportant, Unimportant,
/// The `Normal` priority, as defined by Monero.
Normal, Normal,
/// The `Elevated` priority, as defined by Monero.
Elevated, Elevated,
/// The `Priority` priority, as defined by Monero.
Priority, Priority,
Custom { priority: u32 }, /// A custom priority.
Custom {
/// The numeric representation of the priority, as used within the RPC.
priority: u32,
},
} }
/// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/ /// https://github.com/monero-project/monero/blob/ac02af92867590ca80b2779a7bbeafa99ff94dcb/
@@ -79,9 +107,9 @@ impl FeePriority {
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct EmptyResponse {} struct EmptyResponse {}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct JsonRpcResponse<T> { struct JsonRpcResponse<T> {
result: T, result: T,
} }
@@ -98,36 +126,52 @@ struct TransactionsResponse {
txs: Vec<TransactionResponse>, txs: Vec<TransactionResponse>,
} }
/// The response to an output query.
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct OutputResponse { pub struct OutputResponse {
/// The height of the block this output was added to the chain in.
pub height: usize, pub height: usize,
/// If the output is unlocked, per the node's local view.
pub unlocked: bool, pub unlocked: bool,
key: String, /// The output's key.
mask: String, pub key: String,
txid: String, /// The output's commitment.
pub mask: String,
/// The transaction which created this output.
pub txid: String,
} }
/// An error from the RPC.
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "std", derive(thiserror::Error))] #[cfg_attr(feature = "std", derive(thiserror::Error))]
pub enum RpcError { pub enum RpcError {
/// An internal error.
#[cfg_attr(feature = "std", error("internal error ({0})"))] #[cfg_attr(feature = "std", error("internal error ({0})"))]
InternalError(&'static str), InternalError(&'static str),
/// A connection error with the node.
#[cfg_attr(feature = "std", error("connection error ({0})"))] #[cfg_attr(feature = "std", error("connection error ({0})"))]
ConnectionError(String), ConnectionError(String),
/// The node is invalid per the expected protocol.
#[cfg_attr(feature = "std", error("invalid node ({0})"))] #[cfg_attr(feature = "std", error("invalid node ({0})"))]
InvalidNode(String), InvalidNode(String),
/// The node is running an unsupported protocol.
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))] #[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
UnsupportedProtocol(usize), UnsupportedProtocol(usize),
/// Requested transactions weren't found.
#[cfg_attr(feature = "std", error("transactions not found"))] #[cfg_attr(feature = "std", error("transactions not found"))]
TransactionsNotFound(Vec<[u8; 32]>), TransactionsNotFound(Vec<[u8; 32]>),
#[cfg_attr(feature = "std", error("invalid point ({0})"))] /// The transaction was pruned.
InvalidPoint(String), ///
/// Pruned transactions are not supported at this time.
#[cfg_attr(feature = "std", error("pruned transaction"))] #[cfg_attr(feature = "std", error("pruned transaction"))]
PrunedTransaction, PrunedTransaction,
/// A transaction (sent or received) was invalid.
#[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))] #[cfg_attr(feature = "std", error("invalid transaction ({0:?})"))]
InvalidTransaction([u8; 32]), InvalidTransaction([u8; 32]),
/// The returned fee was unusable.
#[cfg_attr(feature = "std", error("unexpected fee response"))] #[cfg_attr(feature = "std", error("unexpected fee response"))]
InvalidFee, InvalidFee,
/// The priority intended for use wasn't usable.
#[cfg_attr(feature = "std", error("invalid priority"))] #[cfg_attr(feature = "std", error("invalid priority"))]
InvalidPriority, InvalidPriority,
} }
@@ -142,9 +186,11 @@ fn hash_hex(hash: &str) -> Result<[u8; 32], RpcError> {
fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> { fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
decompress_point( decompress_point(
rpc_hex(point)?.try_into().map_err(|_| RpcError::InvalidPoint(point.to_string()))?, rpc_hex(point)?
.try_into()
.map_err(|_| RpcError::InvalidNode(format!("invalid point: {point}")))?,
) )
.ok_or_else(|| RpcError::InvalidPoint(point.to_string())) .ok_or_else(|| RpcError::InvalidNode(format!("invalid point: {point}")))
} }
// Read an EPEE VarInt, distinct from the VarInts used throughout the rest of the protocol // Read an EPEE VarInt, distinct from the VarInts used throughout the rest of the protocol
@@ -164,33 +210,63 @@ fn read_epee_vi<R: io::Read>(reader: &mut R) -> io::Result<u64> {
Ok(vi) Ok(vi)
} }
async fn get_fee_rate_v14(rpc: &impl Rpc, priority: FeePriority) -> Result<FeeRate, RpcError> {
#[derive(Deserialize, Debug)]
struct FeeResponseV14 {
status: String,
fee: u64,
quantization_mask: u64,
}
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/wallet/wallet2.cpp#L7569-L7584
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/wallet/wallet2.cpp#L7660-L7661
let priority_idx =
usize::try_from(if priority.fee_priority() == 0 { 1 } else { priority.fee_priority() - 1 })
.map_err(|_| RpcError::InvalidPriority)?;
let multipliers = [1, 5, 25, 1000];
if priority_idx >= multipliers.len() {
// though not an RPC error, it seems sensible to treat as such
Err(RpcError::InvalidPriority)?;
}
let fee_multiplier = multipliers[priority_idx];
let res: FeeResponseV14 = rpc
.json_rpc_call(
"get_fee_estimate",
Some(json!({ "grace_blocks": GRACE_BLOCKS_FOR_FEE_ESTIMATE })),
)
.await?;
if res.status != "OK" {
Err(RpcError::InvalidFee)?;
}
FeeRate::new(res.fee * fee_multiplier, res.quantization_mask)
}
/// An RPC connection to a Monero daemon.
///
/// This is abstract such that users can use an HTTP library (which being their choice), a
/// Tor/i2p-based transport, or even a memory buffer an external service somehow routes.
#[async_trait] #[async_trait]
pub trait RpcConnection: Clone + Debug { pub trait Rpc: Sync + Clone + Debug {
/// Perform a POST request to the specified route with the specified body. /// Perform a POST request to the specified route with the specified body.
/// ///
/// The implementor is left to handle anything such as authentication. /// The implementor is left to handle anything such as authentication.
async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>; async fn post(&self, route: &str, body: Vec<u8>) -> Result<Vec<u8>, RpcError>;
}
// TODO: Make this provided methods for RpcConnection?
#[derive(Clone, Debug)]
pub struct Rpc<R: RpcConnection>(R);
impl<R: RpcConnection> Rpc<R> {
pub fn new(connection: R) -> Self {
Self(connection)
}
/// Perform a RPC call to the specified route with the provided parameters. /// Perform a RPC call to the specified route with the provided parameters.
/// ///
/// This is NOT a JSON-RPC call. They use a route of "json_rpc" and are available via /// This is NOT a JSON-RPC call. They use a route of "json_rpc" and are available via
/// `json_rpc_call`. /// `json_rpc_call`.
pub async fn rpc_call<Params: Serialize + Debug, Response: DeserializeOwned + Debug>( async fn rpc_call<Params: Send + Serialize + Debug, Response: DeserializeOwned + Debug>(
&self, &self,
route: &str, route: &str,
params: Option<Params>, params: Option<Params>,
) -> Result<Response, RpcError> { ) -> Result<Response, RpcError> {
let res = self let res = self
.0
.post( .post(
route, route,
if let Some(params) = params { if let Some(params) = params {
@@ -206,8 +282,8 @@ impl<R: RpcConnection> Rpc<R> {
.map_err(|_| RpcError::InvalidNode(format!("response wasn't json: {res_str}"))) .map_err(|_| RpcError::InvalidNode(format!("response wasn't json: {res_str}")))
} }
/// Perform a JSON-RPC call with the specified method with the provided parameters /// Perform a JSON-RPC call with the specified method with the provided parameters.
pub async fn json_rpc_call<Response: DeserializeOwned + Debug>( async fn json_rpc_call<Response: DeserializeOwned + Debug>(
&self, &self,
method: &str, method: &str,
params: Option<Value>, params: Option<Value>,
@@ -220,12 +296,12 @@ impl<R: RpcConnection> Rpc<R> {
} }
/// Perform a binary call to the specified route with the provided parameters. /// Perform a binary call to the specified route with the provided parameters.
pub async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> { async fn bin_call(&self, route: &str, params: Vec<u8>) -> Result<Vec<u8>, RpcError> {
self.0.post(route, params).await self.post(route, params).await
} }
/// Get the active blockchain protocol version. /// Get the active blockchain protocol version.
pub async fn get_protocol(&self) -> Result<Protocol, RpcError> { async fn get_protocol(&self) -> Result<Protocol, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct ProtocolResponse { struct ProtocolResponse {
major_version: usize, major_version: usize,
@@ -250,7 +326,11 @@ impl<R: RpcConnection> Rpc<R> {
) )
} }
pub async fn get_height(&self) -> Result<usize, RpcError> { /// Get the height of the Monero blockchain.
///
/// The height is defined as the amount of blocks on the blockchain. For a blockchain with only
/// its genesis block, the height will be 1.
async fn get_height(&self) -> Result<usize, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct HeightResponse { struct HeightResponse {
height: usize, height: usize,
@@ -258,7 +338,11 @@ impl<R: RpcConnection> Rpc<R> {
Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height) Ok(self.rpc_call::<Option<()>, HeightResponse>("get_height", None).await?.height)
} }
pub async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> { /// Get the specified transactions.
///
/// The received transactions will be hashed in order to verify the correct transactions were
/// returned.
async fn get_transactions(&self, hashes: &[[u8; 32]]) -> Result<Vec<Transaction>, RpcError> {
if hashes.is_empty() { if hashes.is_empty() {
return Ok(vec![]); return Ok(vec![]);
} }
@@ -322,13 +406,19 @@ impl<R: RpcConnection> Rpc<R> {
.collect() .collect()
} }
pub async fn get_transaction(&self, tx: [u8; 32]) -> Result<Transaction, RpcError> { /// Get the specified transaction.
///
/// The received transaction will be hashed in order to verify the correct transaction was
/// returned.
async fn get_transaction(&self, tx: [u8; 32]) -> Result<Transaction, RpcError> {
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
} }
/// Get the hash of a block from the node by the block's numbers. /// Get the hash of a block from the node.
/// This function does not verify the returned block hash is actually for the number in question. ///
pub async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> { /// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
/// `height - 1` for the latest block).
async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct BlockHeaderResponse { struct BlockHeaderResponse {
hash: String, hash: String,
@@ -344,8 +434,9 @@ impl<R: RpcConnection> Rpc<R> {
} }
/// Get a block from the node by its hash. /// Get a block from the node by its hash.
/// This function does not verify the returned block actually has the hash in question. ///
pub async fn get_block(&self, hash: [u8; 32]) -> Result<Block, RpcError> { /// The received block will be hashed in order to verify the correct block was returned.
async fn get_block(&self, hash: [u8; 32]) -> Result<Block, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct BlockResponse { struct BlockResponse {
blob: String, blob: String,
@@ -362,7 +453,11 @@ impl<R: RpcConnection> Rpc<R> {
Ok(block) Ok(block)
} }
pub async fn get_block_by_number(&self, number: usize) -> Result<Block, RpcError> { /// Get a block from the node by its number.
///
/// `number` is the block's zero-indexed position on the blockchain (`0` for the genesis block,
/// `height - 1` for the latest block).
async fn get_block_by_number(&self, number: usize) -> Result<Block, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct BlockResponse { struct BlockResponse {
blob: String, blob: String,
@@ -389,14 +484,26 @@ impl<R: RpcConnection> Rpc<R> {
} }
} }
pub async fn get_block_transactions(&self, hash: [u8; 32]) -> Result<Vec<Transaction>, RpcError> { /// Get the transactions within a block.
///
/// This function returns all transactions in the block, including the miner's transaction.
///
/// This function does not verify the returned transactions are the ones committed to by the
/// block's header.
async fn get_block_transactions(&self, hash: [u8; 32]) -> Result<Vec<Transaction>, RpcError> {
let block = self.get_block(hash).await?; let block = self.get_block(hash).await?;
let mut res = vec![block.miner_tx]; let mut res = vec![block.miner_tx];
res.extend(self.get_transactions(&block.txs).await?); res.extend(self.get_transactions(&block.txs).await?);
Ok(res) Ok(res)
} }
pub async fn get_block_transactions_by_number( /// Get the transactions within a block.
///
/// This function returns all transactions in the block, including the miner's transaction.
///
/// This function does not verify the returned transactions are the ones committed to by the
/// block's header.
async fn get_block_transactions_by_number(
&self, &self,
number: usize, number: usize,
) -> Result<Vec<Transaction>, RpcError> { ) -> Result<Vec<Transaction>, RpcError> {
@@ -404,7 +511,7 @@ impl<R: RpcConnection> Rpc<R> {
} }
/// Get the output indexes of the specified transaction. /// Get the output indexes of the specified transaction.
pub async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> { async fn get_o_indexes(&self, hash: [u8; 32]) -> Result<Vec<u64>, RpcError> {
/* /*
TODO: Use these when a suitable epee serde lib exists TODO: Use these when a suitable epee serde lib exists
@@ -558,13 +665,10 @@ impl<R: RpcConnection> Rpc<R> {
.map_err(|_| RpcError::InvalidNode("invalid binary response".to_string())) .map_err(|_| RpcError::InvalidNode("invalid binary response".to_string()))
} }
/// Get the output distribution, from the specified height to the specified height (both /// Get the output distribution.
/// inclusive). ///
pub async fn get_output_distribution( /// `from` and `to` are heights, not block numbers, and inclusive.
&self, async fn get_output_distribution(&self, from: usize, to: usize) -> Result<Vec<u64>, RpcError> {
from: usize,
to: usize,
) -> Result<Vec<u64>, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct Distribution { struct Distribution {
distribution: Vec<u64>, distribution: Vec<u64>,
@@ -591,8 +695,8 @@ impl<R: RpcConnection> Rpc<R> {
Ok(distributions.distributions.swap_remove(0).distribution) Ok(distributions.distributions.swap_remove(0).distribution)
} }
/// Get the specified outputs from the RingCT (zero-amount) pool /// Get the specified outputs from the RingCT (zero-amount) pool.
pub async fn get_outs(&self, indexes: &[u64]) -> Result<Vec<OutputResponse>, RpcError> { async fn get_outs(&self, indexes: &[u64]) -> Result<Vec<OutputResponse>, RpcError> {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct OutsResponse { struct OutsResponse {
status: String, status: String,
@@ -624,7 +728,13 @@ impl<R: RpcConnection> Rpc<R> {
/// ///
/// The timelock being satisfied is distinct from being free of the 10-block lock applied to all /// The timelock being satisfied is distinct from being free of the 10-block lock applied to all
/// Monero transactions. /// Monero transactions.
pub async fn get_unlocked_outputs( ///
/// The node is trusted for if the output is unlocked unless `fingerprintable_canonical` is set
/// to true. If `fingerprintable_canonical` is set to true, the node's local view isn't used, yet
/// the transaction's timelock is checked to be unlocked at the specified `height`. This offers a
/// canonical decoy selection, yet is fingerprintable as time-based timelocks aren't evaluated
/// (and considered locked, preventing their selection).
async fn get_unlocked_outputs(
&self, &self,
indexes: &[u64], indexes: &[u64],
height: usize, height: usize,
@@ -671,47 +781,15 @@ impl<R: RpcConnection> Rpc<R> {
.collect() .collect()
} }
async fn get_fee_v14(&self, priority: FeePriority) -> Result<Fee, RpcError> { /// Get the currently estimated fee rate from the node.
#[derive(Deserialize, Debug)]
struct FeeResponseV14 {
status: String,
fee: u64,
quantization_mask: u64,
}
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/wallet/wallet2.cpp#L7569-L7584
// https://github.com/monero-project/monero/blob/94e67bf96bbc010241f29ada6abc89f49a81759c/
// src/wallet/wallet2.cpp#L7660-L7661
let priority_idx =
usize::try_from(if priority.fee_priority() == 0 { 1 } else { priority.fee_priority() - 1 })
.map_err(|_| RpcError::InvalidPriority)?;
let multipliers = [1, 5, 25, 1000];
if priority_idx >= multipliers.len() {
// though not an RPC error, it seems sensible to treat as such
Err(RpcError::InvalidPriority)?;
}
let fee_multiplier = multipliers[priority_idx];
let res: FeeResponseV14 = self
.json_rpc_call(
"get_fee_estimate",
Some(json!({ "grace_blocks": GRACE_BLOCKS_FOR_FEE_ESTIMATE })),
)
.await?;
if res.status != "OK" {
Err(RpcError::InvalidFee)?;
}
Ok(Fee { per_weight: res.fee * fee_multiplier, mask: res.quantization_mask })
}
/// Get the currently estimated fee from the node.
/// ///
/// This may be manipulated to unsafe levels and MUST be sanity checked. /// This may be manipulated to unsafe levels and MUST be sanity checked.
// TODO: Take a sanity check argument // TODO: Take a sanity check argument
pub async fn get_fee(&self, protocol: Protocol, priority: FeePriority) -> Result<Fee, RpcError> { async fn get_fee_rate(
&self,
protocol: Protocol,
priority: FeePriority,
) -> Result<FeeRate, RpcError> {
// TODO: Implement wallet2's adjust_priority which by default automatically uses a lower // TODO: Implement wallet2's adjust_priority which by default automatically uses a lower
// priority than provided depending on the backlog in the pool // priority than provided depending on the backlog in the pool
if protocol.v16_fee() { if protocol.v16_fee() {
@@ -743,14 +821,15 @@ impl<R: RpcConnection> Rpc<R> {
} else if priority_idx >= res.fees.len() { } else if priority_idx >= res.fees.len() {
Err(RpcError::InvalidPriority) Err(RpcError::InvalidPriority)
} else { } else {
Ok(Fee { per_weight: res.fees[priority_idx], mask: res.quantization_mask }) FeeRate::new(res.fees[priority_idx], res.quantization_mask)
} }
} else { } else {
self.get_fee_v14(priority).await get_fee_rate_v14(self, priority).await
} }
} }
pub async fn publish_transaction(&self, tx: &Transaction) -> Result<(), RpcError> { /// Publish a transaction.
async fn publish_transaction(&self, tx: &Transaction) -> Result<(), RpcError> {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct SendRawResponse { struct SendRawResponse {
@@ -778,8 +857,11 @@ impl<R: RpcConnection> Rpc<R> {
Ok(()) Ok(())
} }
/// Generate blocks, with the specified address receiving the block reward.
///
/// Returns the hashes of the generated blocks and the last block's number.
// TODO: Take &Address, not &str? // TODO: Take &Address, not &str?
pub async fn generate_blocks( async fn generate_blocks(
&self, &self,
address: &str, address: &str,
block_count: usize, block_count: usize,

View File

@@ -20,7 +20,7 @@ mod binaries {
pub(crate) use tokio::task::JoinHandle; pub(crate) use tokio::task::JoinHandle;
pub(crate) async fn check_block(rpc: Arc<Rpc<SimpleRequestRpc>>, block_i: usize) { pub(crate) async fn check_block(rpc: Arc<SimpleRequestRpc>, block_i: usize) {
let hash = loop { let hash = loop {
match rpc.get_block_hash(block_i).await { match rpc.get_block_hash(block_i).await {
Ok(hash) => break hash, Ok(hash) => break hash,
@@ -158,7 +158,7 @@ mod binaries {
} }
async fn get_outs( async fn get_outs(
rpc: &Rpc<SimpleRequestRpc>, rpc: &SimpleRequestRpc,
amount: u64, amount: u64,
indexes: &[u64], indexes: &[u64],
) -> Vec<[EdwardsPoint; 2]> { ) -> Vec<[EdwardsPoint; 2]> {

View File

@@ -10,7 +10,7 @@ use rand_distr::num_traits::Float;
use curve25519_dalek::edwards::EdwardsPoint; use curve25519_dalek::edwards::EdwardsPoint;
use monero_serai::{DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME}; use monero_serai::{DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME};
use monero_rpc::{RpcError, RpcConnection, Rpc}; use monero_rpc::{RpcError, Rpc};
use crate::SpendableOutput; use crate::SpendableOutput;
const RECENT_WINDOW: usize = 15; const RECENT_WINDOW: usize = 15;
@@ -19,9 +19,9 @@ const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
const TIP_APPLICATION: f64 = (DEFAULT_LOCK_WINDOW * BLOCK_TIME) as f64; const TIP_APPLICATION: f64 = (DEFAULT_LOCK_WINDOW * BLOCK_TIME) as f64;
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>( async fn select_n<'a, R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
distribution: &[u64], distribution: &[u64],
height: usize, height: usize,
high: u64, high: u64,
@@ -129,9 +129,9 @@ fn offset(ring: &[u64]) -> Vec<u64> {
res res
} }
async fn select_decoys<R: RngCore + CryptoRng, RPC: RpcConnection>( async fn select_decoys<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
ring_len: usize, ring_len: usize,
height: usize, height: usize,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
@@ -278,20 +278,17 @@ pub use monero_serai::primitives::Decoys;
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait DecoySelection { pub trait DecoySelection {
async fn select<R: Send + Sync + RngCore + CryptoRng, RPC: Send + Sync + RpcConnection>( async fn select<R: Send + Sync + RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
ring_len: usize, ring_len: usize,
height: usize, height: usize,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
) -> Result<Vec<Decoys>, RpcError>; ) -> Result<Vec<Decoys>, RpcError>;
async fn fingerprintable_canonical_select< async fn fingerprintable_canonical_select<R: Send + Sync + RngCore + CryptoRng>(
R: Send + Sync + RngCore + CryptoRng,
RPC: Send + Sync + RpcConnection,
>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
ring_len: usize, ring_len: usize,
height: usize, height: usize,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
@@ -303,9 +300,9 @@ pub trait DecoySelection {
impl DecoySelection for Decoys { impl DecoySelection for Decoys {
/// Select decoys using the same distribution as Monero. Relies on the monerod RPC /// Select decoys using the same distribution as Monero. Relies on the monerod RPC
/// response for an output's unlocked status, minimizing trips to the daemon. /// response for an output's unlocked status, minimizing trips to the daemon.
async fn select<R: Send + Sync + RngCore + CryptoRng, RPC: Send + Sync + RpcConnection>( async fn select<R: Send + Sync + RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
ring_len: usize, ring_len: usize,
height: usize, height: usize,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],
@@ -321,12 +318,9 @@ impl DecoySelection for Decoys {
/// ///
/// TODO: upstream change to monerod get_outs RPC to accept a height param for checking /// TODO: upstream change to monerod get_outs RPC to accept a height param for checking
/// output's unlocked status and remove all usage of fingerprintable_canonical /// output's unlocked status and remove all usage of fingerprintable_canonical
async fn fingerprintable_canonical_select< async fn fingerprintable_canonical_select<R: Send + Sync + RngCore + CryptoRng>(
R: Send + Sync + RngCore + CryptoRng,
RPC: Send + Sync + RpcConnection,
>(
rng: &mut R, rng: &mut R,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
ring_len: usize, ring_len: usize,
height: usize, height: usize,
inputs: &[SpendableOutput], inputs: &[SpendableOutput],

View File

@@ -47,7 +47,7 @@ pub mod decoys {
pub use decoys::{DecoySelection, Decoys}; pub use decoys::{DecoySelection, Decoys};
mod send; mod send;
pub use send::{FeePriority, Fee, TransactionError, Change, SignableTransaction, Eventuality}; pub use send::{FeePriority, FeeRate, TransactionError, Change, SignableTransaction, Eventuality};
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use send::SignableTransactionBuilder; pub use send::SignableTransactionBuilder;
#[cfg(feature = "multisig")] #[cfg(feature = "multisig")]

View File

@@ -9,7 +9,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint}; use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar, edwards::EdwardsPoint};
use monero_rpc::{RpcError, RpcConnection, Rpc}; use monero_rpc::{RpcError, Rpc};
use monero_serai::{ use monero_serai::{
io::*, io::*,
primitives::Commitment, primitives::Commitment,
@@ -240,10 +240,7 @@ pub struct SpendableOutput {
impl SpendableOutput { impl SpendableOutput {
/// Update the spendable output's global index. This is intended to be called if a /// Update the spendable output's global index. This is intended to be called if a
/// re-organization occurred. /// re-organization occurred.
pub async fn refresh_global_index<RPC: RpcConnection>( pub async fn refresh_global_index(&mut self, rpc: &impl Rpc) -> Result<(), RpcError> {
&mut self,
rpc: &Rpc<RPC>,
) -> Result<(), RpcError> {
self.global_index = *rpc self.global_index = *rpc
.get_o_indexes(self.output.absolute.tx) .get_o_indexes(self.output.absolute.tx)
.await? .await?
@@ -254,10 +251,7 @@ impl SpendableOutput {
Ok(()) Ok(())
} }
pub async fn from<RPC: RpcConnection>( pub async fn from(rpc: &impl Rpc, output: ReceivedOutput) -> Result<SpendableOutput, RpcError> {
rpc: &Rpc<RPC>,
output: ReceivedOutput,
) -> Result<SpendableOutput, RpcError> {
let mut output = SpendableOutput { output, global_index: 0 }; let mut output = SpendableOutput { output, global_index: 0 };
output.refresh_global_index(rpc).await?; output.refresh_global_index(rpc).await?;
Ok(output) Ok(output)
@@ -466,9 +460,9 @@ impl Scanner {
/// transactions is a dead giveaway for which transactions you successfully scanned. This /// transactions is a dead giveaway for which transactions you successfully scanned. This
/// function obtains the output indexes for the miner transaction, incrementing from there /// function obtains the output indexes for the miner transaction, incrementing from there
/// instead. /// instead.
pub async fn scan<RPC: RpcConnection>( pub async fn scan(
&mut self, &mut self,
rpc: &Rpc<RPC>, rpc: &impl Rpc,
block: &Block, block: &Block,
) -> Result<Vec<Timelocked<SpendableOutput>>, RpcError> { ) -> Result<Vec<Timelocked<SpendableOutput>>, RpcError> {
let mut index = rpc.get_o_indexes(block.miner_tx.hash()).await?[0]; let mut index = rpc.get_o_indexes(block.miner_tx.hash()).await?[0];

View File

@@ -4,14 +4,14 @@ use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use monero_serai::Protocol; use monero_serai::Protocol;
use crate::{ use crate::{
address::MoneroAddress, Fee, SpendableOutput, Change, Decoys, SignableTransaction, address::MoneroAddress, FeeRate, SpendableOutput, Change, Decoys, SignableTransaction,
TransactionError, extra::MAX_ARBITRARY_DATA_SIZE, TransactionError, extra::MAX_ARBITRARY_DATA_SIZE,
}; };
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
struct SignableTransactionBuilderInternal { struct SignableTransactionBuilderInternal {
protocol: Protocol, protocol: Protocol,
fee_rate: Fee, fee_rate: FeeRate,
r_seed: Option<Zeroizing<[u8; 32]>>, r_seed: Option<Zeroizing<[u8; 32]>>,
inputs: Vec<(SpendableOutput, Decoys)>, inputs: Vec<(SpendableOutput, Decoys)>,
@@ -23,7 +23,7 @@ struct SignableTransactionBuilderInternal {
impl SignableTransactionBuilderInternal { impl SignableTransactionBuilderInternal {
// Takes in the change address so users don't miss that they have to manually set one // Takes in the change address so users don't miss that they have to manually set one
// If they don't, all leftover funds will become part of the fee // If they don't, all leftover funds will become part of the fee
fn new(protocol: Protocol, fee_rate: Fee, change_address: Change) -> Self { fn new(protocol: Protocol, fee_rate: FeeRate, change_address: Change) -> Self {
Self { Self {
protocol, protocol,
fee_rate, fee_rate,
@@ -88,7 +88,7 @@ impl SignableTransactionBuilder {
Self(self.0.clone()) Self(self.0.clone())
} }
pub fn new(protocol: Protocol, fee_rate: Fee, change_address: Change) -> Self { pub fn new(protocol: Protocol, fee_rate: FeeRate, change_address: Change) -> Self {
Self(Arc::new(RwLock::new(SignableTransactionBuilderInternal::new( Self(Arc::new(RwLock::new(SignableTransactionBuilderInternal::new(
protocol, protocol,
fee_rate, fee_rate,

View File

@@ -22,7 +22,7 @@ use curve25519_dalek::{
use frost::FrostError; use frost::FrostError;
use monero_rpc::RpcError; use monero_rpc::RpcError;
pub use monero_rpc::{Fee, FeePriority}; pub use monero_rpc::{FeePriority, FeeRate};
use monero_serai::{ use monero_serai::{
io::*, io::*,
primitives::{Commitment, keccak256}, primitives::{Commitment, keccak256},
@@ -216,7 +216,7 @@ fn calculate_weight_and_fee(
decoy_weights: &[usize], decoy_weights: &[usize],
n_outputs: usize, n_outputs: usize,
extra: usize, extra: usize,
fee_rate: Fee, fee_rate: FeeRate,
) -> (usize, u64) { ) -> (usize, u64) {
// Starting the fee at 0 here is different than core Monero's wallet2.cpp, which starts its fee // Starting the fee at 0 here is different than core Monero's wallet2.cpp, which starts its fee
// calculation with an estimate. // calculation with an estimate.
@@ -291,7 +291,7 @@ pub struct SignableTransaction {
payments: Vec<InternalPayment>, payments: Vec<InternalPayment>,
data: Vec<Vec<u8>>, data: Vec<Vec<u8>>,
fee: u64, fee: u64,
fee_rate: Fee, fee_rate: FeeRate,
} }
/// Specification for a change output. /// Specification for a change output.
@@ -398,7 +398,7 @@ impl SignableTransaction {
payments: Vec<(MoneroAddress, u64)>, payments: Vec<(MoneroAddress, u64)>,
change: &Change, change: &Change,
data: Vec<Vec<u8>>, data: Vec<Vec<u8>>,
fee_rate: Fee, fee_rate: FeeRate,
) -> Result<SignableTransaction, TransactionError> { ) -> Result<SignableTransaction, TransactionError> {
// Make sure there's only one payment ID // Make sure there's only one payment ID
let mut has_payment_id = { let mut has_payment_id = {
@@ -559,7 +559,7 @@ impl SignableTransaction {
self.fee self.fee
} }
pub fn fee_rate(&self) -> Fee { pub fn fee_rate(&self) -> FeeRate {
self.fee_rate self.fee_rate
} }

View File

@@ -1,5 +1,5 @@
use monero_serai::transaction::Transaction; use monero_serai::transaction::Transaction;
use monero_wallet::{TransactionError, extra::MAX_ARBITRARY_DATA_SIZE}; use monero_wallet::{rpc::Rpc, TransactionError, extra::MAX_ARBITRARY_DATA_SIZE};
mod runner; mod runner;

View File

@@ -1,6 +1,9 @@
use monero_rpc::{Rpc, OutputResponse}; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_serai::{transaction::Transaction, Protocol, DEFAULT_LOCK_WINDOW}; use monero_wallet::{
use monero_wallet::SpendableOutput; monero::{transaction::Transaction, Protocol, DEFAULT_LOCK_WINDOW},
rpc::{OutputResponse, Rpc},
SpendableOutput,
};
mod runner; mod runner;
@@ -12,7 +15,7 @@ test!(
builder.add_payment(addr, 2000000000000); builder.add_payment(addr, 2000000000000);
(builder.build().unwrap(), ()) (builder.build().unwrap(), ())
}, },
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, ()| async move { |rpc: SimpleRequestRpc, tx: Transaction, mut scanner: Scanner, ()| async move {
let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0); let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0);
assert_eq!(output.commitment().amount, 2000000000000); assert_eq!(output.commitment().amount, 2000000000000);
SpendableOutput::from(&rpc, output).await.unwrap() SpendableOutput::from(&rpc, output).await.unwrap()
@@ -20,7 +23,7 @@ test!(
), ),
( (
// Then make a second tx1 // Then make a second tx1
|protocol: Protocol, rpc: Rpc<_>, mut builder: Builder, addr, state: _| async move { |protocol: Protocol, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
let output_tx0: SpendableOutput = state; let output_tx0: SpendableOutput = state;
let decoys = Decoys::fingerprintable_canonical_select( let decoys = Decoys::fingerprintable_canonical_select(
&mut OsRng, &mut OsRng,
@@ -39,7 +42,7 @@ test!(
(builder.build().unwrap(), (protocol, output_tx0)) (builder.build().unwrap(), (protocol, output_tx0))
}, },
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy // Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move { |rpc: SimpleRequestRpc, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move {
use rand_core::OsRng; use rand_core::OsRng;
let height = rpc.get_height().await.unwrap(); let height = rpc.get_height().await.unwrap();
@@ -89,7 +92,7 @@ test!(
builder.add_payment(addr, 2000000000000); builder.add_payment(addr, 2000000000000);
(builder.build().unwrap(), ()) (builder.build().unwrap(), ())
}, },
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, ()| async move { |rpc: SimpleRequestRpc, tx: Transaction, mut scanner: Scanner, ()| async move {
let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0); let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0);
assert_eq!(output.commitment().amount, 2000000000000); assert_eq!(output.commitment().amount, 2000000000000);
SpendableOutput::from(&rpc, output).await.unwrap() SpendableOutput::from(&rpc, output).await.unwrap()
@@ -97,7 +100,7 @@ test!(
), ),
( (
// Then make a second tx1 // Then make a second tx1
|protocol: Protocol, rpc: Rpc<_>, mut builder: Builder, addr, state: _| async move { |protocol: Protocol, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
let output_tx0: SpendableOutput = state; let output_tx0: SpendableOutput = state;
let decoys = Decoys::select( let decoys = Decoys::select(
&mut OsRng, &mut OsRng,
@@ -116,7 +119,7 @@ test!(
(builder.build().unwrap(), (protocol, output_tx0)) (builder.build().unwrap(), (protocol, output_tx0))
}, },
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy // Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|rpc: Rpc<_>, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move { |rpc: SimpleRequestRpc, tx: Transaction, mut scanner: Scanner, state: (_, _)| async move {
use rand_core::OsRng; use rand_core::OsRng;
let height = rpc.get_height().await.unwrap(); let height = rpc.get_height().await.unwrap();

View File

@@ -2,6 +2,7 @@ use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use monero_serai::transaction::Transaction; use monero_serai::transaction::Transaction;
use monero_wallet::{ use monero_wallet::{
rpc::Rpc,
Eventuality, Eventuality,
address::{AddressType, AddressMeta, MoneroAddress}, address::{AddressType, AddressMeta, MoneroAddress},
}; };

View File

@@ -8,13 +8,13 @@ use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use monero_rpc::Rpc;
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_serai::{transaction::Transaction, DEFAULT_LOCK_WINDOW};
use monero_wallet::{ use monero_wallet::{
monero::transaction::Transaction,
rpc::Rpc,
ViewPair, Scanner, ViewPair, Scanner,
address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress}, address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress},
SpendableOutput, Fee, SpendableOutput, FeeRate,
}; };
pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) { pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
@@ -34,7 +34,7 @@ pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
// TODO: Support transactions already on-chain // TODO: Support transactions already on-chain
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions // TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions
pub async fn mine_until_unlocked(rpc: &Rpc<SimpleRequestRpc>, addr: &str, tx_hash: [u8; 32]) { pub async fn mine_until_unlocked(rpc: &SimpleRequestRpc, addr: &str, tx_hash: [u8; 32]) {
// mine until tx is in a block // mine until tx is in a block
let mut height = rpc.get_height().await.unwrap(); let mut height = rpc.get_height().await.unwrap();
let mut found = false; let mut found = false;
@@ -52,11 +52,11 @@ pub async fn mine_until_unlocked(rpc: &Rpc<SimpleRequestRpc>, addr: &str, tx_has
// Mine until tx's outputs are unlocked // Mine until tx's outputs are unlocked
let o_indexes: Vec<u64> = rpc.get_o_indexes(tx_hash).await.unwrap(); let o_indexes: Vec<u64> = rpc.get_o_indexes(tx_hash).await.unwrap();
while rpc while rpc
.get_outs(&o_indexes) .get_unlocked_outputs(&o_indexes, height, false)
.await .await
.unwrap() .unwrap()
.into_iter() .into_iter()
.all(|o| (!(o.unlocked && height >= (o.height + DEFAULT_LOCK_WINDOW)))) .all(|output| output.is_some())
{ {
height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1; height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1;
} }
@@ -64,7 +64,7 @@ pub async fn mine_until_unlocked(rpc: &Rpc<SimpleRequestRpc>, addr: &str, tx_has
// Mines 60 blocks and returns an unlocked miner TX output. // Mines 60 blocks and returns an unlocked miner TX output.
#[allow(dead_code)] #[allow(dead_code)]
pub async fn get_miner_tx_output(rpc: &Rpc<SimpleRequestRpc>, view: &ViewPair) -> SpendableOutput { pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> SpendableOutput {
let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new())); let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
// Mine 60 blocks to unlock a miner TX // Mine 60 blocks to unlock a miner TX
@@ -79,7 +79,7 @@ pub async fn get_miner_tx_output(rpc: &Rpc<SimpleRequestRpc>, view: &ViewPair) -
} }
/// Make sure the weight and fee match the expected calculation. /// Make sure the weight and fee match the expected calculation.
pub fn check_weight_and_fee(tx: &Transaction, fee_rate: Fee) { pub fn check_weight_and_fee(tx: &Transaction, fee_rate: FeeRate) {
let fee = tx.rct_signatures.base.fee; let fee = tx.rct_signatures.base.fee;
let weight = tx.weight(); let weight = tx.weight();
@@ -90,7 +90,7 @@ pub fn check_weight_and_fee(tx: &Transaction, fee_rate: Fee) {
assert_eq!(fee, expected_fee); assert_eq!(fee, expected_fee);
} }
pub async fn rpc() -> Rpc<SimpleRequestRpc> { pub async fn rpc() -> SimpleRequestRpc {
let rpc = let rpc =
SimpleRequestRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap(); SimpleRequestRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap();
@@ -216,7 +216,7 @@ macro_rules! test {
let builder = SignableTransactionBuilder::new( let builder = SignableTransactionBuilder::new(
protocol, protocol,
rpc.get_fee(protocol, FeePriority::Unimportant).await.unwrap(), rpc.get_fee_rate(protocol, FeePriority::Unimportant).await.unwrap(),
Change::new( Change::new(
&ViewPair::new( &ViewPair::new(
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, &Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,

View File

@@ -1,7 +1,7 @@
use rand_core::RngCore; use rand_core::RngCore;
use monero_serai::transaction::Transaction; use monero_serai::transaction::Transaction;
use monero_wallet::{address::SubaddressIndex, extra::PaymentId}; use monero_wallet::{rpc::Rpc, address::SubaddressIndex, extra::PaymentId};
mod runner; mod runner;

View File

@@ -1,11 +1,12 @@
use rand_core::OsRng; use rand_core::OsRng;
use monero_serai::{transaction::Transaction, Protocol};
use monero_rpc::Rpc;
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{ use monero_wallet::{
extra::Extra, address::SubaddressIndex, ReceivedOutput, SpendableOutput, DecoySelection, Decoys, monero::{transaction::Transaction, Protocol},
SignableTransactionBuilder, rpc::Rpc,
extra::Extra,
address::SubaddressIndex,
ReceivedOutput, SpendableOutput, DecoySelection, Decoys, SignableTransactionBuilder,
}; };
mod runner; mod runner;
@@ -13,7 +14,7 @@ mod runner;
// Set up inputs, select decoys, then add them to the TX builder // Set up inputs, select decoys, then add them to the TX builder
async fn add_inputs( async fn add_inputs(
protocol: Protocol, protocol: Protocol,
rpc: &Rpc<SimpleRequestRpc>, rpc: &SimpleRequestRpc,
outputs: Vec<ReceivedOutput>, outputs: Vec<ReceivedOutput>,
builder: &mut SignableTransactionBuilder, builder: &mut SignableTransactionBuilder,
) { ) {
@@ -98,7 +99,7 @@ test!(
}, },
), ),
( (
|protocol, rpc: Rpc<_>, _, _, outputs: Vec<ReceivedOutput>| async move { |protocol, rpc: SimpleRequestRpc, _, _, outputs: Vec<ReceivedOutput>| async move {
use monero_wallet::FeePriority; use monero_wallet::FeePriority;
let change_view = ViewPair::new( let change_view = ViewPair::new(
@@ -108,7 +109,7 @@ test!(
let mut builder = SignableTransactionBuilder::new( let mut builder = SignableTransactionBuilder::new(
protocol, protocol,
rpc.get_fee(protocol, FeePriority::Unimportant).await.unwrap(), rpc.get_fee_rate(protocol, FeePriority::Unimportant).await.unwrap(),
Change::new(&change_view, false), Change::new(&change_view, false),
); );
add_inputs(protocol, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await; add_inputs(protocol, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await;
@@ -287,12 +288,12 @@ test!(
}, },
), ),
( (
|protocol, rpc: Rpc<_>, _, addr, outputs: Vec<ReceivedOutput>| async move { |protocol, rpc: SimpleRequestRpc, _, addr, outputs: Vec<ReceivedOutput>| async move {
use monero_wallet::FeePriority; use monero_wallet::FeePriority;
let mut builder = SignableTransactionBuilder::new( let mut builder = SignableTransactionBuilder::new(
protocol, protocol,
rpc.get_fee(protocol, FeePriority::Unimportant).await.unwrap(), rpc.get_fee_rate(protocol, FeePriority::Unimportant).await.unwrap(),
Change::fingerprintable(None), Change::fingerprintable(None),
); );
add_inputs(protocol, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await; add_inputs(protocol, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await;

View File

@@ -5,10 +5,10 @@ use rand_core::{OsRng, RngCore};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use monero_serai::transaction::Transaction;
use monero_rpc::{EmptyResponse, Rpc};
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{ use monero_wallet::{
monero::transaction::Transaction,
rpc::Rpc,
address::{Network, AddressSpec, SubaddressIndex, MoneroAddress}, address::{Network, AddressSpec, SubaddressIndex, MoneroAddress},
extra::{MAX_TX_EXTRA_NONCE_SIZE, Extra, PaymentId}, extra::{MAX_TX_EXTRA_NONCE_SIZE, Extra, PaymentId},
Scanner, Scanner,
@@ -16,7 +16,10 @@ use monero_wallet::{
mod runner; mod runner;
async fn make_integrated_address(rpc: &Rpc<SimpleRequestRpc>, payment_id: [u8; 8]) -> String { #[derive(Deserialize, Debug)]
struct EmptyResponse {}
async fn make_integrated_address(rpc: &SimpleRequestRpc, payment_id: [u8; 8]) -> String {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct IntegratedAddressResponse { struct IntegratedAddressResponse {
integrated_address: String, integrated_address: String,
@@ -33,7 +36,7 @@ async fn make_integrated_address(rpc: &Rpc<SimpleRequestRpc>, payment_id: [u8; 8
res.integrated_address res.integrated_address
} }
async fn initialize_rpcs() -> (Rpc<SimpleRequestRpc>, Rpc<SimpleRequestRpc>, String) { async fn initialize_rpcs() -> (SimpleRequestRpc, SimpleRequestRpc, String) {
let wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap(); let wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap();
let daemon_rpc = runner::rpc().await; let daemon_rpc = runner::rpc().await;
@@ -89,7 +92,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) {
// TODO: Needs https://github.com/monero-project/monero/pull/9260 // TODO: Needs https://github.com/monero-project/monero/pull/9260
// let fee_rate = daemon_rpc // let fee_rate = daemon_rpc
// .get_fee(daemon_rpc.get_protocol().await.unwrap(), FeePriority::Unimportant) // .get_fee_rate(daemon_rpc.get_protocol().await.unwrap(), FeePriority::Unimportant)
// .await // .await
// .unwrap(); // .unwrap();
@@ -173,7 +176,7 @@ test!(
.add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000); .add_payment(MoneroAddress::from_str(Network::Mainnet, &wallet_rpc_addr).unwrap(), 1000000);
(builder.build().unwrap(), wallet_rpc) (builder.build().unwrap(), wallet_rpc)
}, },
|_, tx: Transaction, _, data: Rpc<SimpleRequestRpc>| async move { |_, tx: Transaction, _, data: SimpleRequestRpc| async move {
// confirm receipt // confirm receipt
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap(); let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data let transfer: TransfersResponse = data
@@ -207,7 +210,7 @@ test!(
.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr.address).unwrap(), 1000000); .add_payment(MoneroAddress::from_str(Network::Mainnet, &addr.address).unwrap(), 1000000);
(builder.build().unwrap(), (wallet_rpc, addr.account_index)) (builder.build().unwrap(), (wallet_rpc, addr.account_index))
}, },
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, u32)| async move { |_, tx: Transaction, _, data: (SimpleRequestRpc, u32)| async move {
// confirm receipt // confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap(); let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data let transfer: TransfersResponse = data
@@ -259,7 +262,7 @@ test!(
]); ]);
(builder.build().unwrap(), (wallet_rpc, daemon_rpc, addrs.address_index)) (builder.build().unwrap(), (wallet_rpc, daemon_rpc, addrs.address_index))
}, },
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, Rpc<SimpleRequestRpc>, u32)| async move { |_, tx: Transaction, _, data: (SimpleRequestRpc, SimpleRequestRpc, u32)| async move {
// confirm receipt // confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap(); let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data let transfer: TransfersResponse = data
@@ -304,7 +307,7 @@ test!(
builder.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr).unwrap(), 1000000); builder.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr).unwrap(), 1000000);
(builder.build().unwrap(), (wallet_rpc, payment_id)) (builder.build().unwrap(), (wallet_rpc, payment_id))
}, },
|_, tx: Transaction, _, data: (Rpc<SimpleRequestRpc>, [u8; 8])| async move { |_, tx: Transaction, _, data: (SimpleRequestRpc, [u8; 8])| async move {
// confirm receipt // confirm receipt
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap(); let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data let transfer: TransfersResponse = data
@@ -339,7 +342,7 @@ test!(
(builder.build().unwrap(), wallet_rpc) (builder.build().unwrap(), wallet_rpc)
}, },
|_, tx: Transaction, _, data: Rpc<SimpleRequestRpc>| async move { |_, tx: Transaction, _, data: SimpleRequestRpc| async move {
// confirm receipt // confirm receipt
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap(); let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
let transfer: TransfersResponse = data let transfer: TransfersResponse = data

View File

@@ -19,7 +19,7 @@ use monero_wallet::{
rpc::{RpcError, Rpc}, rpc::{RpcError, Rpc},
ViewPair, Scanner, ViewPair, Scanner,
address::{Network as MoneroNetwork, SubaddressIndex, AddressSpec}, address::{Network as MoneroNetwork, SubaddressIndex, AddressSpec},
Fee, SpendableOutput, Change, DecoySelection, Decoys, TransactionError, FeeRate, SpendableOutput, Change, DecoySelection, Decoys, TransactionError,
SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine, SignableTransaction as MSignableTransaction, Eventuality, TransactionMachine,
}; };
@@ -222,7 +222,7 @@ impl BlockTrait<Monero> for Block {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Monero { pub struct Monero {
rpc: Rpc<SimpleRequestRpc>, rpc: SimpleRequestRpc,
} }
// Shim required for testing/debugging purposes due to generic arguments also necessitating trait // Shim required for testing/debugging purposes due to generic arguments also necessitating trait
// bounds // bounds
@@ -275,7 +275,7 @@ impl Monero {
scanner scanner
} }
async fn median_fee(&self, block: &Block) -> Result<Fee, NetworkError> { async fn median_fee(&self, block: &Block) -> Result<FeeRate, NetworkError> {
let mut fees = vec![]; let mut fees = vec![];
for tx_hash in &block.txs { for tx_hash in &block.txs {
let tx = let tx =
@@ -294,7 +294,7 @@ impl Monero {
let fee = fees.get(fees.len() / 2).copied().unwrap_or(0); let fee = fees.get(fees.len() / 2).copied().unwrap_or(0);
// TODO: Set a sane minimum fee // TODO: Set a sane minimum fee
Ok(Fee { per_weight: fee.max(1500000), mask: 10000 }) Ok(FeeRate::new(fee.max(1500000), 10000).unwrap())
} }
async fn make_signable_transaction( async fn make_signable_transaction(
@@ -795,7 +795,7 @@ impl Network for Monero {
vec![(address.into(), amount - fee)], vec![(address.into(), amount - fee)],
&Change::fingerprintable(Some(Self::test_address().into())), &Change::fingerprintable(Some(Self::test_address().into())),
vec![], vec![],
self.rpc.get_fee(protocol, FeePriority::Unimportant).await.unwrap(), self.rpc.get_fee_rate(protocol, FeePriority::Unimportant).await.unwrap(),
) )
.unwrap() .unwrap()
.sign(&mut OsRng, &Zeroizing::new(Scalar::ONE.0)) .sign(&mut OsRng, &Zeroizing::new(Scalar::ONE.0))

View File

@@ -53,8 +53,9 @@ impl Handles {
pub async fn monero( pub async fn monero(
&self, &self,
ops: &DockerOperations, ops: &DockerOperations,
) -> monero_wallet::rpc::Rpc<monero_simple_request_rpc::SimpleRequestRpc> { ) -> monero_simple_request_rpc::SimpleRequestRpc {
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
let rpc = ops.handle(&self.monero.0).host_port(self.monero.1).unwrap(); let rpc = ops.handle(&self.monero.0).host_port(self.monero.1).unwrap();
let rpc = format!("http://{RPC_USER}:{RPC_PASS}@{}:{}", rpc.0, rpc.1); let rpc = format!("http://{RPC_USER}:{RPC_PASS}@{}:{}", rpc.0, rpc.1);

View File

@@ -89,6 +89,7 @@ async fn mint_and_burn_test() {
let monero_blocks = { let monero_blocks = {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_wallet::{ use monero_wallet::{
rpc::Rpc,
ViewPair, ViewPair,
address::{Network, AddressSpec}, address::{Network, AddressSpec},
}; };
@@ -128,6 +129,8 @@ async fn mint_and_burn_test() {
} }
{ {
use monero_wallet::rpc::Rpc;
let rpc = handles.monero(ops).await; let rpc = handles.monero(ops).await;
for (block, txs) in &monero_blocks { for (block, txs) in &monero_blocks {
@@ -347,6 +350,7 @@ async fn mint_and_burn_test() {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_wallet::{ use monero_wallet::{
monero::{io::decompress_point, Protocol, transaction::Timelock}, monero::{io::decompress_point, Protocol, transaction::Timelock},
rpc::Rpc,
ViewPair, Scanner, DecoySelection, Decoys, Change, FeePriority, SignableTransaction, ViewPair, Scanner, DecoySelection, Decoys, Change, FeePriority, SignableTransaction,
address::{Network, AddressType, AddressMeta, MoneroAddress}, address::{Network, AddressType, AddressMeta, MoneroAddress},
}; };
@@ -393,7 +397,7 @@ async fn mint_and_burn_test() {
)], )],
&Change::new(&view_pair, false), &Change::new(&view_pair, false),
vec![Shorthand::transfer(None, serai_addr).encode()], vec![Shorthand::transfer(None, serai_addr).encode()],
rpc.get_fee(Protocol::v16, FeePriority::Unimportant).await.unwrap(), rpc.get_fee_rate(Protocol::v16, FeePriority::Unimportant).await.unwrap(),
) )
.unwrap() .unwrap()
.sign(&mut OsRng, &Zeroizing::new(Scalar::ONE)) .sign(&mut OsRng, &Zeroizing::new(Scalar::ONE))
@@ -482,7 +486,10 @@ async fn mint_and_burn_test() {
// Get the current blocks // Get the current blocks
let mut start_bitcoin_block = let mut start_bitcoin_block =
handles[0].bitcoin(&ops).await.get_latest_block_number().await.unwrap(); handles[0].bitcoin(&ops).await.get_latest_block_number().await.unwrap();
let mut start_monero_block = handles[0].monero(&ops).await.get_height().await.unwrap(); let mut start_monero_block = {
use monero_wallet::rpc::Rpc;
handles[0].monero(&ops).await.get_height().await.unwrap()
};
// Burn the sriBTC/sriXMR // Burn the sriBTC/sriXMR
{ {
@@ -574,7 +581,7 @@ async fn mint_and_burn_test() {
// Verify the received Monero TX // Verify the received Monero TX
{ {
use monero_wallet::{ViewPair, Scanner}; use monero_wallet::{rpc::Rpc, ViewPair, Scanner};
let rpc = handles[0].monero(&ops).await; let rpc = handles[0].monero(&ops).await;
let mut scanner = Scanner::from_view( let mut scanner = Scanner::from_view(
ViewPair::new(monero_spend, Zeroizing::new(monero_view)), ViewPair::new(monero_spend, Zeroizing::new(monero_view)),

View File

@@ -275,6 +275,7 @@ impl Coordinator {
} }
NetworkId::Monero => { NetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
// Monero's won't, so call get_height // Monero's won't, so call get_height
if handle if handle
@@ -405,6 +406,7 @@ impl Coordinator {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{ use monero_wallet::{
rpc::Rpc,
ViewPair, ViewPair,
address::{Network, AddressSpec}, address::{Network, AddressSpec},
}; };
@@ -516,6 +518,7 @@ impl Coordinator {
} }
NetworkId::Monero => { NetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC"); let rpc = SimpleRequestRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
let to = rpc.get_height().await.unwrap(); let to = rpc.get_height().await.unwrap();
@@ -576,7 +579,7 @@ impl Coordinator {
} }
NetworkId::Monero => { NetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::monero::transaction::Transaction; use monero_wallet::{rpc::Rpc, monero::transaction::Transaction};
let rpc = SimpleRequestRpc::new(rpc_url) let rpc = SimpleRequestRpc::new(rpc_url)
.await .await
@@ -676,6 +679,7 @@ impl Coordinator {
} }
NetworkId::Monero => { NetworkId::Monero => {
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::rpc::Rpc;
let rpc = SimpleRequestRpc::new(rpc_url) let rpc = SimpleRequestRpc::new(rpc_url)
.await .await

View File

@@ -191,6 +191,7 @@ impl Wallet {
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar};
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{ use monero_wallet::{
rpc::Rpc,
ViewPair, Scanner, ViewPair, Scanner,
address::{Network, AddressSpec}, address::{Network, AddressSpec},
}; };
@@ -436,6 +437,7 @@ impl Wallet {
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
use monero_simple_request_rpc::SimpleRequestRpc; use monero_simple_request_rpc::SimpleRequestRpc;
use monero_wallet::{ use monero_wallet::{
rpc::Rpc,
monero::{Protocol, io::decompress_point}, monero::{Protocol, io::decompress_point},
address::{Network, AddressType, AddressMeta, Address}, address::{Network, AddressType, AddressMeta, Address},
SpendableOutput, DecoySelection, Decoys, Change, FeePriority, Scanner, SpendableOutput, DecoySelection, Decoys, Change, FeePriority, Scanner,
@@ -490,7 +492,7 @@ impl Wallet {
vec![(to_addr, AMOUNT)], vec![(to_addr, AMOUNT)],
&Change::new(view_pair, false), &Change::new(view_pair, false),
data, data,
rpc.get_fee(Protocol::v16, FeePriority::Unimportant).await.unwrap(), rpc.get_fee_rate(Protocol::v16, FeePriority::Unimportant).await.unwrap(),
) )
.unwrap() .unwrap()
.sign(&mut OsRng, spend_key) .sign(&mut OsRng, spend_key)