Skip to content

Commit

Permalink
xmr runner tests mine until outputs are unlocked
Browse files Browse the repository at this point in the history
  • Loading branch information
j-berman committed Sep 29, 2023
1 parent 6280b24 commit 33bc227
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 27 deletions.
4 changes: 4 additions & 0 deletions coins/monero/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub mod wallet;
#[cfg(test)]
mod tests;

pub const DEFAULT_LOCK_WINDOW: usize = 10;
pub const COINBASE_LOCK_WINDOW: usize = 60;
pub const BLOCK_TIME: usize = 120;

static INV_EIGHT_CELL: OnceLock<Scalar> = OnceLock::new();
#[allow(non_snake_case)]
pub(crate) fn INV_EIGHT() -> Scalar {
Expand Down
24 changes: 14 additions & 10 deletions coins/monero/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::{
transaction::{Input, Transaction},
block::Block,
wallet::{FeePriority, Fee},
DEFAULT_LOCK_WINDOW,
};

#[cfg(feature = "http_rpc")]
Expand Down Expand Up @@ -573,13 +574,13 @@ impl<R: RpcConnection> Rpc<R> {
pub async fn get_unlocked_outputs(
&self,
indexes: &[u64],
height: usize,
) -> Result<Vec<Option<[EdwardsPoint; 2]>>, RpcError> {
let outs: Vec<OutputResponse> = self.get_outs(indexes).await?;

outs
.iter()
.enumerate()
.map(|(_, out)| {
.map(|out| {
// Allow keys to be invalid, though if they are, return None to trigger selection of a new
// decoy
// Only valid keys can be used in CLSAG proofs, hence the need for re-selection, yet
Expand All @@ -594,7 +595,10 @@ impl<R: RpcConnection> Rpc<R> {
.decompress() else {
return Ok(None);
};
Ok(Some([key, rpc_point(&out.mask)?]).filter(|_| out.unlocked))
Ok(
Some([key, rpc_point(&out.mask)?])
.filter(|_| out.unlocked && height >= (out.height + DEFAULT_LOCK_WINDOW)),
)
})
.collect()
}
Expand Down Expand Up @@ -711,27 +715,27 @@ impl<R: RpcConnection> Rpc<R> {
&self,
address: &str,
block_count: usize,
) -> Result<Vec<[u8; 32]>, RpcError> {
) -> Result<(Vec<[u8; 32]>, usize), RpcError> {
#[derive(Debug, Deserialize)]
struct BlocksResponse {
blocks: Vec<String>,
height: usize,
}

let block_strs = self
let res = self
.json_rpc_call::<BlocksResponse>(
"generateblocks",
Some(json!({
"wallet_address": address,
"amount_of_blocks": block_count
})),
)
.await?
.blocks;
.await?;

let mut blocks = Vec::with_capacity(block_strs.len());
for block in block_strs {
let mut blocks = Vec::with_capacity(res.blocks.len());
for block in res.blocks {
blocks.push(rpc_hex(&block)?.try_into().map_err(|_| RpcError::InvalidNode)?);
}
Ok(blocks)
Ok((blocks, res.height))
}
}
16 changes: 8 additions & 8 deletions coins/monero/src/wallet/decoys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ use crate::{
serialize::varint_len,
wallet::SpendableOutput,
rpc::{RpcError, RpcConnection, Rpc},
DEFAULT_LOCK_WINDOW, COINBASE_LOCK_WINDOW, BLOCK_TIME,
};

pub const LOCK_WINDOW: usize = 10;
const COINBASE_LOCK_WINDOW: u64 = 60;
const RECENT_WINDOW: usize = 15;
const BLOCK_TIME: usize = 120;
const BLOCKS_PER_YEAR: usize = 365 * 24 * 60 * 60 / BLOCK_TIME;
const TIP_APPLICATION: f64 = (LOCK_WINDOW * BLOCK_TIME) as f64;
const TIP_APPLICATION: f64 = (DEFAULT_LOCK_WINDOW * BLOCK_TIME) as f64;

// TODO: Expose an API to reset this in case a reorg occurs/the RPC fails/returns garbage
// TODO: Update this when scanning a block, as possible
Expand Down Expand Up @@ -113,7 +111,7 @@ async fn select_n<'a, R: RngCore + CryptoRng, RPC: RpcConnection>(

// TODO: make sure that the real output is included in the response, and
// that mask and key are equal to expected
for (i, output) in rpc.get_unlocked_outputs(&candidates).await?.iter_mut().enumerate() {
for (i, output) in rpc.get_unlocked_outputs(&candidates, height).await?.iter_mut().enumerate() {
// Don't include the real spend as a decoy, despite requesting it
if real_indexes.contains(&i) {
continue;
Expand Down Expand Up @@ -198,7 +196,7 @@ impl Decoys {

if distribution.len() != height {
Err(RpcError::InternalError("unexpected rct out distribution len"))?;
} else if distribution.len() < LOCK_WINDOW {
} else if distribution.len() < DEFAULT_LOCK_WINDOW {
Err(RpcError::InternalError("not enough decoy candidates"))?;
}

Expand All @@ -215,8 +213,10 @@ impl Decoys {
}

// TODO: Create a TX with less than the target amount, as allowed by the protocol
let high = distribution[distribution.len() - LOCK_WINDOW];
if high.saturating_sub(COINBASE_LOCK_WINDOW) < u64::try_from(inputs.len() * ring_len).unwrap() {
let high = distribution[distribution.len() - DEFAULT_LOCK_WINDOW];
if high.saturating_sub(COINBASE_LOCK_WINDOW as u64) <
u64::try_from(inputs.len() * ring_len).unwrap()
{
Err(RpcError::InternalError("not enough coinbase candidates"))?;
}

Expand Down
6 changes: 3 additions & 3 deletions coins/monero/tests/decoys.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use monero_serai::{
transaction::Transaction,
wallet::{SpendableOutput, decoys::LOCK_WINDOW},
wallet::SpendableOutput,
rpc::{Rpc, OutputResponse},
Protocol,
Protocol, DEFAULT_LOCK_WINDOW,
};

mod runner;
Expand Down Expand Up @@ -55,7 +55,7 @@ test!(
// Make sure output from tx1 is in the block in which it unlocks
let out_tx1: OutputResponse =
rpc.get_outs(&[output_tx1.global_index]).await.unwrap().swap_remove(0);
assert_eq!(out_tx1.height, height - LOCK_WINDOW);
assert_eq!(out_tx1.height, height - DEFAULT_LOCK_WINDOW);
assert!(out_tx1.unlocked);

// Select decoys using spendable output from tx0 as the real, and make sure DSA selects
Expand Down
12 changes: 7 additions & 5 deletions coins/monero/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {

// TODO: Support transactions already on-chain
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions
// TODO: mine as much as needed instead of default 10 blocks
pub async fn mine_until_unlocked(rpc: &Rpc<HttpRpc>, addr: &str, tx_hash: [u8; 32]) {
// mine until tx is in a block
let mut height = rpc.get_height().await.unwrap();
Expand All @@ -46,15 +45,18 @@ pub async fn mine_until_unlocked(rpc: &Rpc<HttpRpc>, addr: &str, tx_hash: [u8; 3
found = match block.txs.iter().find(|&&x| x == tx_hash) {
Some(_) => true,
None => {
rpc.generate_blocks(addr, 1).await.unwrap();
height += 1;
height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1;
false
}
}
}

// mine 9 more blocks to unlock the tx
rpc.generate_blocks(addr, 9).await.unwrap();
// Mine until tx's outputs are unlocked
let o_indexes: Vec<u64> = rpc.get_o_indexes(tx_hash).await.unwrap();
while rpc.get_unlocked_outputs(&o_indexes, height).await.unwrap().into_iter().all(|o| o.is_none())
{
height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1;
}
}

// Mines 60 blocks and returns an unlocked miner TX output.
Expand Down
2 changes: 1 addition & 1 deletion tests/full-stack/src/tests/mint_and_burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ async fn mint_and_burn_test() {
let mut res = Vec::with_capacity(count);
for _ in 0 .. count {
let block =
rpc.get_block(rpc.generate_blocks(&addr, 1).await.unwrap()[0]).await.unwrap();
rpc.get_block(rpc.generate_blocks(&addr, 1).await.unwrap().0[0]).await.unwrap();

let mut txs = Vec::with_capacity(block.txs.len());
for tx in &block.txs {
Expand Down

0 comments on commit 33bc227

Please sign in to comment.