test-utils: add data/ (#107)

* test-utils: add `data/` + add lints + `clippy --fix`

* data: assert tx_hash is correct

* data: add `BLOCK_202612` doctest

* lib.rs: remove lints

* data: add more tx data, fix names

* revert `Cargo.toml` formatting

* data: add more block data

* free: add serialization tests

* remove clippy allows
This commit is contained in:
hinto-janai 2024-04-12 21:13:46 -04:00 committed by GitHub
parent eb3227ef2d
commit e6465ec613
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 328 additions and 27 deletions

2
Cargo.lock generated
View file

@ -633,7 +633,9 @@ dependencies = [
"bytes", "bytes",
"bzip2", "bzip2",
"futures", "futures",
"hex",
"monero-p2p", "monero-p2p",
"monero-serai",
"monero-wire", "monero-wire",
"reqwest", "reqwest",
"tar", "tar",

View file

@ -9,6 +9,7 @@ authors = ["Boog900"]
monero-wire = {path = "../net/monero-wire"} monero-wire = {path = "../net/monero-wire"}
monero-p2p = {path = "../p2p/monero-p2p", features = ["borsh"] } monero-p2p = {path = "../p2p/monero-p2p", features = ["borsh"] }
monero-serai = { workspace = true }
futures = { workspace = true, features = ["std"] } futures = { workspace = true, features = ["std"] }
async-trait = { workspace = true } async-trait = { workspace = true }
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
@ -25,3 +26,6 @@ bzip2 = "0.4.4"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
zip = "0.6" zip = "0.6"
[dev-dependencies]
hex = { workspace = true }

View file

@ -3,5 +3,6 @@
This crate contains code that can be shared across multiple Cuprate crates tests, this crate should not be included in any This crate contains code that can be shared across multiple Cuprate crates tests, this crate should not be included in any
Cuprate crate, only in tests. Cuprate crate, only in tests.
It currently contains code to spawn monerod instances and a testing network zone. It currently contains:
- Code to spawn monerod instances and a testing network zone
- Real raw and typed Monero data, e.g. `Block, Transaction`

View file

@ -0,0 +1,14 @@
# Data
This module contains:
- Raw binary, hex, or JSON data for testing purposes
- Functions to access that data, either raw or typed
- `.bin` is a data blob, directly deserializable into types, e.g. `monero_serai::block::Block::read::<&[u8]>(&mut blob)`
- `.hex` is just a hex string of the blob
- `.json` is just the data in regular JSON form (as it would be from a JSON-RPC response)
# Actual data
| Directory | File naming scheme | Example |
|-----------|------------------------------|---------|
| `block/` | `$block_hash.{bin,hex,json}` | `bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin`
| `tx/` | `$tx_hash.{bin,hex,json}` | `84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66.bin`

View file

@ -0,0 +1,150 @@
//! Constants holding raw Monero data.
//---------------------------------------------------------------------------------------------------- Import
//---------------------------------------------------------------------------------------------------- Block
/// Block with height `202612` and hash `bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_BBD604
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 1);
/// assert_eq!(block.header.minor_version, 0);
/// assert_eq!(block.header.timestamp, 1409804570);
/// assert_eq!(block.header.nonce, 1073744198);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(202612)));
/// assert_eq!(block.txs.len(), 513);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698",
/// );
/// ```
pub const BLOCK_BBD604: &[u8] =
include_bytes!("block/bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin");
/// Block with height `2751506` and hash `f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_F91043
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 9);
/// assert_eq!(block.header.minor_version, 9);
/// assert_eq!(block.header.timestamp, 1545423190);
/// assert_eq!(block.header.nonce, 4123173351);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(1731606)));
/// assert_eq!(block.txs.len(), 3);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4",
/// );
/// ```
pub const BLOCK_F91043: &[u8] =
include_bytes!("block/f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4.bin");
/// Block with height `2751506` and hash `43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428`.
///
/// ```rust
/// use monero_serai::{block::Block, transaction::Input};
///
/// let block = Block::read(&mut
/// cuprate_test_utils::data::BLOCK_43BD1F
/// ).unwrap();
///
/// assert_eq!(block.header.major_version, 16);
/// assert_eq!(block.header.minor_version, 16);
/// assert_eq!(block.header.timestamp, 1667941829);
/// assert_eq!(block.header.nonce, 4110909056);
/// assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(2751506)));
/// assert_eq!(block.txs.len(), 0);
///
/// assert_eq!(
/// hex::encode(block.hash()),
/// "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428",
/// );
/// ```
pub const BLOCK_43BD1F: &[u8] =
include_bytes!("block/43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428.bin");
//---------------------------------------------------------------------------------------------------- Transaction
/// Transaction with hash `3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_3BC7FF
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 1);
/// assert_eq!(tx.prefix.timelock, Timelock::Block(100_081));
/// assert_eq!(tx.prefix.inputs.len(), 1);
/// assert_eq!(tx.prefix.outputs.len(), 5);
/// assert_eq!(tx.signatures.len(), 0);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1",
/// );
/// ```
pub const TX_3BC7FF: &[u8] =
include_bytes!("tx/3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1.bin");
/// Transaction with hash `9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_9E3F73
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 1);
/// assert_eq!(tx.prefix.timelock, Timelock::None);
/// assert_eq!(tx.prefix.inputs.len(), 2);
/// assert_eq!(tx.prefix.outputs.len(), 5);
/// assert_eq!(tx.signatures.len(), 2);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34",
/// );
/// ```
pub const TX_9E3F73: &[u8] =
include_bytes!("tx/9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34.bin");
/// Transaction with hash `84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66`.
///
/// ```rust
/// use monero_serai::transaction::{Transaction, Timelock};
///
/// let tx = Transaction::read(&mut
/// cuprate_test_utils::data::TX_84D48D
/// ).unwrap();
///
/// assert_eq!(tx.prefix.version, 2);
/// assert_eq!(tx.prefix.timelock, Timelock::None);
/// assert_eq!(tx.prefix.inputs.len(), 2);
/// assert_eq!(tx.prefix.outputs.len(), 2);
/// assert_eq!(tx.signatures.len(), 0);
///
/// assert_eq!(
/// hex::encode(tx.hash()),
/// "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66",
/// );
/// ```
pub const TX_84D48D: &[u8] =
include_bytes!("tx/84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66.bin");
//---------------------------------------------------------------------------------------------------- Tests
#[cfg(test)]
mod test {}

110
test-utils/src/data/free.rs Normal file
View file

@ -0,0 +1,110 @@
//! Free functions to access data.
#![allow(
const_item_mutation, // `R: Read` needs `&mut self`
clippy::missing_panics_doc, // These functions shouldn't panic
)]
//---------------------------------------------------------------------------------------------------- Import
use std::sync::OnceLock;
use monero_serai::{block::Block, transaction::Transaction};
use crate::data::constants::{
BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73,
};
//---------------------------------------------------------------------------------------------------- Blocks
/// Return [`BLOCK_BBD604`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v1_tx513().serialize(),
/// cuprate_test_utils::data::BLOCK_BBD604
/// );
/// ```
pub fn block_v1_tx513() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_BBD604).unwrap())
.clone()
}
/// Return [`BLOCK_F91043`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v9_tx3().serialize(),
/// cuprate_test_utils::data::BLOCK_F91043
/// );
/// ```
pub fn block_v9_tx3() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_F91043).unwrap())
.clone()
}
/// Return [`BLOCK_43BD1F`] as a [`Block`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::block_v16_tx0().serialize(),
/// cuprate_test_utils::data::BLOCK_43BD1F
/// );
/// ```
pub fn block_v16_tx0() -> Block {
/// `OnceLock` holding the data.
static BLOCK: OnceLock<Block> = OnceLock::new();
BLOCK
.get_or_init(|| Block::read(&mut BLOCK_43BD1F).unwrap())
.clone()
}
//---------------------------------------------------------------------------------------------------- Transactions
/// Return [`TX_3BC7FF`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v1_sig0().serialize(),
/// cuprate_test_utils::data::TX_3BC7FF
/// );
/// ```
pub fn tx_v1_sig0() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_3BC7FF).unwrap())
.clone()
}
/// Return [`TX_9E3F73`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v1_sig2().serialize(),
/// cuprate_test_utils::data::TX_9E3F73
/// );
/// ```
pub fn tx_v1_sig2() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_9E3F73).unwrap())
.clone()
}
/// Return [`TX_84D48D`] as a [`Transaction`].
///
/// ```rust
/// assert_eq!(
/// &cuprate_test_utils::data::tx_v2_rct3().serialize(),
/// cuprate_test_utils::data::TX_84D48D
/// );
/// ```
pub fn tx_v2_rct3() -> Transaction {
/// `OnceLock` holding the data.
static TX: OnceLock<Transaction> = OnceLock::new();
TX.get_or_init(|| Transaction::read(&mut TX_84D48D).unwrap())
.clone()
}

View file

@ -0,0 +1,9 @@
//! Testing data and utilities.
//!
//! Raw data is found in `data/`.
mod constants;
pub use constants::{BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73};
mod free;
pub use free::{block_v16_tx0, block_v1_tx513, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3};

View file

@ -1,2 +1,7 @@
//! Cuprate testing utilities.
//!
//! See the `README.md` for more info.
pub mod data;
pub mod monerod; pub mod monerod;
pub mod test_netzone; pub mod test_netzone;

View file

@ -17,11 +17,17 @@ use tokio::{task::yield_now, time::timeout};
mod download; mod download;
/// IPv4 local host.
const LOCALHOST: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); const LOCALHOST: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
/// The `monerod` version to use.
const MONEROD_VERSION: &str = "v0.18.3.1"; const MONEROD_VERSION: &str = "v0.18.3.1";
/// The log line `monerod` emits indicated it has successfully started up.
const MONEROD_STARTUP_TEXT: &str = const MONEROD_STARTUP_TEXT: &str =
"The daemon will start synchronizing with the network. This may take a long time to complete."; "The daemon will start synchronizing with the network. This may take a long time to complete.";
/// The log line `monerod` emits indicated it has stopped.
const MONEROD_SHUTDOWN_TEXT: &str = "Stopping cryptonote protocol"; const MONEROD_SHUTDOWN_TEXT: &str = "Stopping cryptonote protocol";
/// Spawns monerod and returns [`SpawnedMoneroD`]. /// Spawns monerod and returns [`SpawnedMoneroD`].
@ -66,9 +72,7 @@ pub async fn monerod<T: AsRef<OsStr>>(flags: impl IntoIterator<Item = T>) -> Spa
if logs.contains(MONEROD_SHUTDOWN_TEXT) { if logs.contains(MONEROD_SHUTDOWN_TEXT) {
panic!("Failed to start monerod, logs: \n {logs}"); panic!("Failed to start monerod, logs: \n {logs}");
} } else if logs.contains(MONEROD_STARTUP_TEXT) {
if logs.contains(MONEROD_STARTUP_TEXT) {
break; break;
} }
// this is blocking code but as this is for tests performance isn't a priority. However we should still yield so // this is blocking code but as this is for tests performance isn't a priority. However we should still yield so
@ -88,6 +92,7 @@ pub async fn monerod<T: AsRef<OsStr>>(flags: impl IntoIterator<Item = T>) -> Spa
} }
} }
/// Fetch an available TCP port on the machine for `monerod` to bind to.
fn get_available_port(already_taken: &[u16]) -> u16 { fn get_available_port(already_taken: &[u16]) -> u16 {
loop { loop {
// Using `0` makes the OS return a random available port. // Using `0` makes the OS return a random available port.
@ -119,12 +124,12 @@ pub struct SpawnedMoneroD {
impl SpawnedMoneroD { impl SpawnedMoneroD {
/// Returns the p2p port of the spawned monerod /// Returns the p2p port of the spawned monerod
pub fn p2p_addr(&self) -> SocketAddr { pub const fn p2p_addr(&self) -> SocketAddr {
SocketAddr::new(LOCALHOST, self.p2p_port) SocketAddr::new(LOCALHOST, self.p2p_port)
} }
/// Returns the RPC port of the spawned monerod /// Returns the RPC port of the spawned monerod
pub fn rpc_port(&self) -> SocketAddr { pub const fn rpc_port(&self) -> SocketAddr {
SocketAddr::new(LOCALHOST, self.rpc_port) SocketAddr::new(LOCALHOST, self.rpc_port)
} }
} }
@ -135,7 +140,7 @@ impl Drop for SpawnedMoneroD {
if self.process.kill().is_err() { if self.process.kill().is_err() {
error = true; error = true;
println!("Failed to kill monerod, process id: {}", self.process.id()) println!("Failed to kill monerod, process id: {}", self.process.id());
} }
if panicking() { if panicking() {

View file

@ -24,23 +24,23 @@ static DOWNLOAD_MONEROD_MUTEX: Mutex<()> = Mutex::const_new(());
/// Returns the file name to download and the expected extracted folder name. /// Returns the file name to download and the expected extracted folder name.
fn file_name(version: &str) -> (String, String) { fn file_name(version: &str) -> (String, String) {
let download_file = match (OS, ARCH) { let download_file = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => format!("monero-win-x64-{}.zip", version), ("windows", "x64" | "x86_64") => format!("monero-win-x64-{version}.zip"),
("windows", "x86") => format!("monero-win-x86-{}.zip", version), ("windows", "x86") => format!("monero-win-x86-{version}.zip"),
("linux", "x64") | ("linux", "x86_64") => format!("monero-linux-x64-{}.tar.bz2", version), ("linux", "x64" | "x86_64") => format!("monero-linux-x64-{version}.tar.bz2"),
("linux", "x86") => format!("monero-linux-x86-{}.tar.bz2", version), ("linux", "x86") => format!("monero-linux-x86-{version}.tar.bz2"),
("macos", "x64") | ("macos", "x86_64") => format!("monero-mac-x64-{}.tar.bz2", version), ("macos", "x64" | "x86_64") => format!("monero-mac-x64-{version}.tar.bz2"),
_ => panic!("Can't get monerod for {OS}, {ARCH}."), _ => panic!("Can't get monerod for {OS}, {ARCH}."),
}; };
let extracted_dir = match (OS, ARCH) { let extracted_dir = match (OS, ARCH) {
("windows", "x64") | ("windows", "x86_64") => { ("windows", "x64" | "x86_64") => {
format!("monero-x86_64-w64-mingw32-{}", version) format!("monero-x86_64-w64-mingw32-{version}")
} }
("windows", "x86") => format!("monero-i686-w64-mingw32-{}", version), ("windows", "x86") => format!("monero-i686-w64-mingw32-{version}"),
("linux", "x64") | ("linux", "x86_64") => format!("monero-x86_64-linux-gnu-{}", version), ("linux", "x64" | "x86_64") => format!("monero-x86_64-linux-gnu-{version}"),
("linux", "x86") => format!("monero-i686-linux-gnu-{}", version), ("linux", "x86") => format!("monero-i686-linux-gnu-{version}"),
("macos", "x64") | ("macos", "x86_64") => { ("macos", "x64" | "x86_64") => {
format!("monero-x86_64-apple-darwin11-{}", version) format!("monero-x86_64-apple-darwin11-{version}")
} }
_ => panic!("Can't get monerod for {OS}, {ARCH}."), _ => panic!("Can't get monerod for {OS}, {ARCH}."),
}; };
@ -50,7 +50,7 @@ fn file_name(version: &str) -> (String, String) {
/// Downloads the monerod file provided, extracts it and puts the extracted folder into `path_to_store`. /// Downloads the monerod file provided, extracts it and puts the extracted folder into `path_to_store`.
async fn download_monerod(file_name: &str, path_to_store: &Path) -> Result<(), ReqError> { async fn download_monerod(file_name: &str, path_to_store: &Path) -> Result<(), ReqError> {
let res = get(format!("https://downloads.getmonero.org/cli/{}", file_name)).await?; let res = get(format!("https://downloads.getmonero.org/cli/{file_name}")).await?;
let monerod_archive = res.bytes().await.unwrap(); let monerod_archive = res.bytes().await.unwrap();
#[cfg(unix)] #[cfg(unix)]
@ -83,7 +83,7 @@ fn find_target() -> PathBuf {
} }
/// Checks if we have monerod or downloads it if we don't and then returns the path to it. /// Checks if we have monerod or downloads it if we don't and then returns the path to it.
pub async fn check_download_monerod() -> Result<PathBuf, ReqError> { pub(crate) async fn check_download_monerod() -> Result<PathBuf, ReqError> {
// make sure no other threads are downloading monerod at the same time. // make sure no other threads are downloading monerod at the same time.
let _guard = DOWNLOAD_MONEROD_MUTEX.lock().await; let _guard = DOWNLOAD_MONEROD_MUTEX.lock().await;

View file

@ -1,4 +1,4 @@
//! Test NetZone //! Test net zone.
//! //!
//! This module contains a test network zone, this network zone use channels as the network layer to simulate p2p //! This module contains a test network zone, this network zone use channels as the network layer to simulate p2p
//! communication. //! communication.
@ -48,7 +48,7 @@ impl std::fmt::Display for TestNetZoneAddr {
impl From<TestNetZoneAddr> for NetworkAddress { impl From<TestNetZoneAddr> for NetworkAddress {
fn from(value: TestNetZoneAddr) -> Self { fn from(value: TestNetZoneAddr) -> Self {
NetworkAddress::Clear(SocketAddr::new(Ipv4Addr::from(value.0).into(), 18080)) Self::Clear(SocketAddr::new(Ipv4Addr::from(value.0).into(), 18080))
} }
} }
@ -58,13 +58,14 @@ impl TryFrom<NetworkAddress> for TestNetZoneAddr {
fn try_from(value: NetworkAddress) -> Result<Self, Self::Error> { fn try_from(value: NetworkAddress) -> Result<Self, Self::Error> {
match value { match value {
NetworkAddress::Clear(soc) => match soc { NetworkAddress::Clear(soc) => match soc {
SocketAddr::V4(v4) => Ok(TestNetZoneAddr(u32::from_be_bytes(v4.ip().octets()))), SocketAddr::V4(v4) => Ok(Self(u32::from_be_bytes(v4.ip().octets()))),
_ => panic!("None v4 address in test code"), SocketAddr::V6(_) => panic!("None v4 address in test code"),
}, },
} }
} }
} }
/// TODO
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct TestNetZone<const ALLOW_SYNC: bool, const DANDELION_PP: bool, const CHECK_NODE_ID: bool>; pub struct TestNetZone<const ALLOW_SYNC: bool, const DANDELION_PP: bool, const CHECK_NODE_ID: bool>;