From fe2366c5df00683e603e72d5a98d671fbb487d4d Mon Sep 17 00:00:00 2001 From: hinto-janai Date: Sat, 20 Apr 2024 20:34:04 -0400 Subject: [PATCH] test-utils: return `&'static` verified superset types (#108) * data: return `&'static` verified superset types * docs * fix VerifiedBlockMap, use manual input, add block 5da0a3d * free: use macros to generate accessor functions * constants: use macro * docs * `tx_data` -> `tx_blob` --- Cargo.lock | 2 + test-utils/Cargo.toml | 6 +- test-utils/{README.MD => README.md} | 1 + ...b676695d76a4d1de16036c41ba4dd188c4d76f.bin | Bin 0 -> 351 bytes ...d7d52dc7d6644bb82d81a6ad4057d127ee8eda.bin | Bin 0 -> 319 bytes test-utils/src/data/constants.rs | 336 +++++++++++------- test-utils/src/data/free.rs | 317 ++++++++++++----- test-utils/src/data/mod.rs | 34 +- ...76297e818a73579ef7b7da947da963245202a3.bin | Bin 0 -> 5714 bytes ...62550064caa8d225dd9ad6d739ebf60291c169.bin | Bin 0 -> 2709 bytes ...99b8a0a3e0da5a8a165098937b60f0bbd582df.bin | Bin 0 -> 15980 bytes ...89e7eab9d24c2bd2978ec38ef910961a8cdcee.bin | Bin 0 -> 1911 bytes ...40128868973e7c021bb3877290db3066317474.bin | Bin 0 -> 1887 bytes test-utils/src/lib.rs | 4 +- 14 files changed, 480 insertions(+), 220 deletions(-) rename test-utils/{README.MD => README.md} (85%) create mode 100644 test-utils/src/data/block/5da0a3d004c352a90cc86b00fab676695d76a4d1de16036c41ba4dd188c4d76f.bin create mode 100644 test-utils/src/data/block/5ecb7e663bbe947c734c8059e7d7d52dc7d6644bb82d81a6ad4057d127ee8eda.bin create mode 100644 test-utils/src/data/tx/2180a87f724702d37af087e22476297e818a73579ef7b7da947da963245202a3.bin create mode 100644 test-utils/src/data/tx/b6b4394d4ec5f08ad63267c07962550064caa8d225dd9ad6d739ebf60291c169.bin create mode 100644 test-utils/src/data/tx/d7febd16293799d9c6a8e0fe9199b8a0a3e0da5a8a165098937b60f0bbd582df.bin create mode 100644 test-utils/src/data/tx/e2d39395dd1625b2d707b98af789e7eab9d24c2bd2978ec38ef910961a8cdcee.bin create mode 100644 test-utils/src/data/tx/e57440ec66d2f3b2a5fa2081af40128868973e7c021bb3877290db3066317474.bin diff --git a/Cargo.lock b/Cargo.lock index 92a275f..1c99de9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,8 +633,10 @@ dependencies = [ "borsh", "bytes", "bzip2", + "cuprate-types", "futures", "hex", + "hex-literal", "monero-p2p", "monero-serai", "monero-wire", diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 4143a94..349176f 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -6,8 +6,9 @@ license = "MIT" authors = ["Boog900"] [dependencies] -monero-wire = {path = "../net/monero-wire"} -monero-p2p = {path = "../p2p/monero-p2p", features = ["borsh"] } +monero-wire = { path = "../net/monero-wire"} +monero-p2p = { path = "../p2p/monero-p2p", features = ["borsh"] } +cuprate-types = { path = "../types" } monero-serai = { workspace = true } futures = { workspace = true, features = ["std"] } @@ -17,6 +18,7 @@ tokio-util = { workspace = true } reqwest = { workspace = true } bytes = { workspace = true, features = ["std"] } tempfile = { workspace = true } +hex-literal = { workspace = true } borsh = { workspace = true, features = ["derive"]} diff --git a/test-utils/README.MD b/test-utils/README.md similarity index 85% rename from test-utils/README.MD rename to test-utils/README.md index e7fd3bc..441f6f3 100644 --- a/test-utils/README.MD +++ b/test-utils/README.md @@ -6,3 +6,4 @@ Cuprate crate, only in tests. It currently contains: - Code to spawn monerod instances and a testing network zone - Real raw and typed Monero data, e.g. `Block, Transaction` +- An RPC client to generate types from `cuprate_types` \ No newline at end of file diff --git a/test-utils/src/data/block/5da0a3d004c352a90cc86b00fab676695d76a4d1de16036c41ba4dd188c4d76f.bin b/test-utils/src/data/block/5da0a3d004c352a90cc86b00fab676695d76a4d1de16036c41ba4dd188c4d76f.bin new file mode 100644 index 0000000000000000000000000000000000000000..bfe51fb50791aafd3df383b152eb83e01b0387ba GIT binary patch literal 351 zcmV-l0igZ?0Qu3Mpal}39h}qYTd5R5AGSFtcTk6N_JLglDumS=?A%jiq1N@`m-} z7Xoj>q8@=>_R3}fSM>b@z;ZdV9F9^+n zH4UL`YGFs!P%8nP_<^jW!Ui1L(+X%c{O3+~43N>m?E$dp3T?RMl9_yshl7%Pqk!Z)Jp>y+jd7I$=j@euz}jI z6ap)%px1Ww#7UAYRXyIT=y5M#8dc;G&ey+ZZG|&{M-700(Ui4x0_^^;JH@~#q>K8e zAI7h9Do-11Y6^SA*W;R81q%*^W`MxT^Ms=U0^(Y9mm;XJ^NMcF(Pp*&?fJbyEtxJk zG?v-sSj6DGKPv(8``=FNB;kVmH!TtHl6w^Q?U0$X3P=_;aneAAV}Q5<2mk;80Hoff zt^y%|sDE-t0@Hf%hvFo5DSm;9b61}Cx7w6_sbeHk0;AXdy%s4qnc2pu;Qo=BxS*rp R+FFVhP?(c@VDP)ug5M(lp7;O& literal 0 HcmV?d00001 diff --git a/test-utils/src/data/constants.rs b/test-utils/src/data/constants.rs index 77795a6..4f1eca7 100644 --- a/test-utils/src/data/constants.rs +++ b/test-utils/src/data/constants.rs @@ -3,147 +3,223 @@ //---------------------------------------------------------------------------------------------------- Import //---------------------------------------------------------------------------------------------------- Block -/// Block with height `202612` and hash `bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698`. +/// Generate a `const _: &[u8]` pointing to a block blob. /// -/// ```rust -/// use monero_serai::{block::Block, transaction::Input}; +/// This will deserialize with `Block` to assume the blob is at least deserializable. /// -/// let block = Block::read(&mut -/// cuprate_test_utils::data::BLOCK_BBD604 -/// ).unwrap(); +/// This requires some static block input for testing. /// -/// 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); +/// The actual block blob data on disk is found in `data/block`. /// -/// assert_eq!( -/// hex::encode(block.hash()), -/// "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698", -/// ); -/// ``` -pub const BLOCK_BBD604: &[u8] = - include_bytes!("block/bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin"); +/// See below for actual usage. +macro_rules! const_block_blob { + ( + name: $name:ident, // Name of the `const` created + height: $height:literal, // Block height + hash: $hash:literal, // Block hash + data_path: $data_path:literal, // Path to the block blob + major_version: $major_version:literal, // Block's major version + minor_version: $minor_version:literal, // Block's minor version + timestamp: $timestamp:literal, // Block's timestamp + nonce: $nonce:literal, // Block's nonce + miner_tx_generated: $miner_tx_generated:literal, // Generated Monero in block's miner transaction + tx_len: $tx_len:literal, // How many transactions there are in the block + ) => { + #[doc = concat!("Block with hash `", $hash, "`.")] + /// + #[doc = concat!("Height: `", $height, "`.")] + /// + /// ```rust + #[doc = "# use cuprate_test_utils::data::*;"] + #[doc = "use monero_serai::{block::Block, transaction::Input};"] + #[doc = ""] + #[doc = concat!("let block = Block::read(&mut ", stringify!($name), ").unwrap();")] + #[doc = ""] + #[doc = concat!("assert_eq!(block.header.major_version, ", $major_version, ");")] + #[doc = concat!("assert_eq!(block.header.minor_version, ", $minor_version, ");")] + #[doc = concat!("assert_eq!(block.header.timestamp, ", $timestamp, ");")] + #[doc = concat!("assert_eq!(block.header.nonce, ", $nonce, ");")] + #[doc = concat!("assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(", $miner_tx_generated, ")));")] + #[doc = concat!("assert_eq!(block.txs.len(), ", $tx_len, ");")] + #[doc = concat!("assert_eq!(hex::encode(block.hash()), \"", $hash, "\")")] + /// ``` + pub const $name: &[u8] = include_bytes!($data_path); + }; +} -/// 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"); +const_block_blob! { + name: BLOCK_BBD604, + height: 202_612, + hash: "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698", + data_path: "block/bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698.bin", + major_version: 1, + minor_version: 0, + timestamp: 1409804570, + nonce: 1073744198, + miner_tx_generated: 202612, + tx_len: 513, +} -/// 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"); +const_block_blob! { + name: BLOCK_5ECB7E, + height: 202_609, + hash: "5ecb7e663bbe947c734c8059e7d7d52dc7d6644bb82d81a6ad4057d127ee8eda", + data_path: "block/5ecb7e663bbe947c734c8059e7d7d52dc7d6644bb82d81a6ad4057d127ee8eda.bin", + major_version: 1, + minor_version: 0, + timestamp: 1409804315, + nonce: 48426, + miner_tx_generated: 202609, + tx_len: 2, +} + +const_block_blob! { + name: BLOCK_F91043, + height: 2_751_506, + hash: "f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4", + data_path: "block/f910435a5477ca27be1986c080d5476aeab52d0c07cf3d9c72513213350d25d4.bin", + major_version: 9, + minor_version: 9, + timestamp: 1545423190, + nonce: 4123173351, + miner_tx_generated: 1731606, + tx_len: 3, +} + +const_block_blob! { + name: BLOCK_43BD1F, + height: 2_751_506, + hash: "43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428", + data_path: "block/43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428.bin", + major_version: 16, + minor_version: 16, + timestamp: 1667941829, + nonce: 4110909056, + miner_tx_generated: 2751506, + tx_len: 0, +} //---------------------------------------------------------------------------------------------------- Transaction -/// Transaction with hash `3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1`. +/// Generate a `const _: &[u8]` pointing to a transaction blob. /// -/// ```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"); +/// Same as [`const_block_blob`] but for transactions. +macro_rules! const_tx_blob { + ( + name: $name:ident, // Name of the `const` created + hash: $hash:literal, // Transaction hash + data_path: $data_path:literal, // Path to the transaction blob + version: $version:literal, // Transaction version + timelock: $timelock:expr, // Transaction's timelock (use the real type `Timelock`) + input_len: $input_len:literal, // Amount of inputs + output_len: $output_len:literal, // Amount of outputs + signatures_len: $signatures_len:literal, // Amount of signatures + ) => { + #[doc = concat!("Transaction with hash `", $hash, "`.")] + /// + /// ```rust + #[doc = "# use cuprate_test_utils::data::*;"] + #[doc = "use monero_serai::transaction::{Transaction, Timelock};"] + #[doc = ""] + #[doc = concat!("let tx = Transaction::read(&mut ", stringify!($name), ").unwrap();")] + #[doc = ""] + #[doc = concat!("assert_eq!(tx.prefix.version, ", $version, ");")] + #[doc = concat!("assert_eq!(tx.prefix.timelock, ", stringify!($timelock), ");")] + #[doc = concat!("assert_eq!(tx.prefix.inputs.len(), ", $input_len, ");")] + #[doc = concat!("assert_eq!(tx.prefix.outputs.len(), ", $output_len, ");")] + #[doc = concat!("assert_eq!(tx.signatures.len(), ", $signatures_len, ");")] + #[doc = concat!("assert_eq!(hex::encode(tx.hash()), \"", $hash, "\")")] + /// ``` + pub const $name: &[u8] = include_bytes!($data_path); + }; +} -/// 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"); +const_tx_blob! { + name: TX_3BC7FF, + hash: "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1", + data_path: "tx/3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1.bin", + version: 1, + timelock: Timelock::Block(100_081), + input_len: 1, + output_len: 5, + signatures_len: 0, +} -/// 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"); +const_tx_blob! { + name: TX_2180A8, + hash: "2180a87f724702d37af087e22476297e818a73579ef7b7da947da963245202a3", + data_path: "tx/2180a87f724702d37af087e22476297e818a73579ef7b7da947da963245202a3.bin", + version: 1, + timelock: Timelock::None, + input_len: 19, + output_len: 61, + signatures_len: 19, +} + +const_tx_blob! { + name: TX_D7FEBD, + hash: "d7febd16293799d9c6a8e0fe9199b8a0a3e0da5a8a165098937b60f0bbd582df", + data_path: "tx/d7febd16293799d9c6a8e0fe9199b8a0a3e0da5a8a165098937b60f0bbd582df.bin", + version: 1, + timelock: Timelock::None, + input_len: 46, + output_len: 46, + signatures_len: 46, +} + +const_tx_blob! { + name: TX_E2D393, + hash: "e2d39395dd1625b2d707b98af789e7eab9d24c2bd2978ec38ef910961a8cdcee", + data_path: "tx/e2d39395dd1625b2d707b98af789e7eab9d24c2bd2978ec38ef910961a8cdcee.bin", + version: 2, + timelock: Timelock::None, + input_len: 1, + output_len: 2, + signatures_len: 0, +} + +const_tx_blob! { + name: TX_E57440, + hash: "e57440ec66d2f3b2a5fa2081af40128868973e7c021bb3877290db3066317474", + data_path: "tx/e57440ec66d2f3b2a5fa2081af40128868973e7c021bb3877290db3066317474.bin", + version: 2, + timelock: Timelock::None, + input_len: 1, + output_len: 2, + signatures_len: 0, +} + +const_tx_blob! { + name: TX_B6B439, + hash: "b6b4394d4ec5f08ad63267c07962550064caa8d225dd9ad6d739ebf60291c169", + data_path: "tx/b6b4394d4ec5f08ad63267c07962550064caa8d225dd9ad6d739ebf60291c169.bin", + version: 2, + timelock: Timelock::None, + input_len: 2, + output_len: 2, + signatures_len: 0, +} + +const_tx_blob! { + name: TX_9E3F73, + hash: "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34", + data_path: "tx/9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34.bin", + version: 1, + timelock: Timelock::None, + input_len: 2, + output_len: 5, + signatures_len: 2, +} + +const_tx_blob! { + name: TX_84D48D, + hash: "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66", + data_path: "tx/84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66.bin", + version: 2, + timelock: Timelock::None, + input_len: 2, + output_len: 2, + signatures_len: 0, +} //---------------------------------------------------------------------------------------------------- Tests #[cfg(test)] diff --git a/test-utils/src/data/free.rs b/test-utils/src/data/free.rs index e65f267..9a7665d 100644 --- a/test-utils/src/data/free.rs +++ b/test-utils/src/data/free.rs @@ -6,105 +6,260 @@ )] //---------------------------------------------------------------------------------------------------- Import -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; +use hex_literal::hex; use monero_serai::{block::Block, transaction::Transaction}; +use cuprate_types::{TransactionVerificationData, VerifiedBlockInformation}; + use crate::data::constants::{ - BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73, + BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73, + TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440, }; +//---------------------------------------------------------------------------------------------------- Conversion +/// Converts `monero_serai`'s `Block` into a +/// `cuprate_types::VerifiedBlockInformation` (superset). +/// +/// To prevent pulling other code in order to actually calculate things +/// (e.g. `pow_hash`), some information must be provided statically, +/// this struct represents that data that must be provided. +/// +/// Consider using `cuprate_test_utils::rpc` to get this data easily. +struct VerifiedBlockMap<'a> { + block: Block, + pow_hash: [u8; 32], + height: u64, + generated_coins: u64, + weight: usize, + long_term_weight: usize, + cumulative_difficulty: u128, + // Vec of `tx_blob`'s, i.e. the data in `/test-utils/src/data/tx/`. + // This should the actual `tx_blob`'s of the transactions within this block. + txs: Vec<&'a [u8]>, +} + +impl VerifiedBlockMap<'_> { + /// Turn the various static data bits in `self` into a `VerifiedBlockInformation`. + /// + /// Transactions are verified that they at least match the block's, + /// although the correctness of data (whether this block actually existed or not) + /// is not checked. + fn into_verified(self) -> VerifiedBlockInformation { + let Self { + block, + pow_hash, + height, + generated_coins, + weight, + long_term_weight, + cumulative_difficulty, + txs, + } = self; + + let txs: Vec> = txs + .into_iter() + .map(to_tx_verification_data) + .map(Arc::new) + .collect(); + + assert_eq!( + txs.len(), + block.txs.len(), + "(deserialized txs).len() != (txs hashes in block).len()" + ); + + for (tx, tx_hash_in_block) in txs.iter().zip(&block.txs) { + assert_eq!( + &tx.tx_hash, tx_hash_in_block, + "deserialized tx hash is not the same as the one in the parent block" + ); + } + + VerifiedBlockInformation { + block_hash: block.hash(), + block, + txs, + pow_hash, + height, + generated_coins, + weight, + long_term_weight, + cumulative_difficulty, + } + } +} + +// Same as [`VerifiedBlockMap`] but for [`TransactionVerificationData`]. +fn to_tx_verification_data(tx_blob: &[u8]) -> TransactionVerificationData { + let tx_blob = tx_blob.to_vec(); + let tx = Transaction::read(&mut tx_blob.as_slice()).unwrap(); + TransactionVerificationData { + tx_weight: tx.weight(), + fee: tx.rct_signatures.base.fee, + tx_hash: tx.hash(), + tx_blob, + tx, + } +} + //---------------------------------------------------------------------------------------------------- Blocks -/// Return [`BLOCK_BBD604`] as a [`Block`]. +/// Generate a block accessor function with this signature: +/// `fn() -> &'static VerifiedBlockInformation` /// -/// ```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 = OnceLock::new(); - BLOCK - .get_or_init(|| Block::read(&mut BLOCK_BBD604).unwrap()) - .clone() +/// This will use `VerifiedBlockMap` type above to do various +/// checks on the input data and makes sure it seems correct. +/// +/// This requires some static block/tx input (from data) and some fields. +/// This data can be accessed more easily via: +/// - A block explorer (https://xmrchain.net) +/// - Monero RPC (see cuprate_test_utils::rpc for this) +/// +/// See below for actual usage. +macro_rules! verified_block_information_fn { + ( + fn_name: $fn_name:ident, // Name of the function created + block_blob: $block_blob:ident, // Block blob ([u8], found in `constants.rs`) + tx_blobs: [$($tx_blob:ident),*], // Array of contained transaction blobs + pow_hash: $pow_hash:literal, // PoW hash as a string literal + height: $height:literal, // Block height + generated_coins: $generated_coins:literal, // Generated coins in block (`reward`) + weight: $weight:literal, // Block weight + long_term_weight: $long_term_weight:literal, // Block long term weight + cumulative_difficulty: $cumulative_difficulty:literal, // Block cumulative difficulty + tx_len: $tx_len:literal, // Amount of transactions in this block + ) => { + #[doc = concat!( + "Return [`", + stringify!($block_blob), + "`] as a [`VerifiedBlockInformation`].", + )] + /// + /// Contained transactions: + $( + #[doc = concat!("- [`", stringify!($tx_blob), "`]")] + )* + /// + /// ```rust + #[doc = "# use cuprate_test_utils::data::*;"] + #[doc = "# use hex_literal::hex;"] + #[doc = concat!("let block = ", stringify!($fn_name), "();")] + #[doc = concat!("assert_eq!(&block.block.serialize(), ", stringify!($block_blob), ");")] + #[doc = concat!("assert_eq!(block.pow_hash, hex!(\"", $pow_hash, "\"));")] + #[doc = concat!("assert_eq!(block.height, ", $height, ");")] + #[doc = concat!("assert_eq!(block.generated_coins, ", $generated_coins, ");")] + #[doc = concat!("assert_eq!(block.weight, ", $weight, ");")] + #[doc = concat!("assert_eq!(block.long_term_weight, ", $long_term_weight, ");")] + #[doc = concat!("assert_eq!(block.cumulative_difficulty, ", $cumulative_difficulty, ");")] + #[doc = concat!("assert_eq!(block.txs.len(), ", $tx_len, ");")] + /// ``` + pub fn $fn_name() -> &'static VerifiedBlockInformation { + static BLOCK: OnceLock = OnceLock::new(); + BLOCK.get_or_init(|| { + VerifiedBlockMap { + block: Block::read(&mut $block_blob).unwrap(), + pow_hash: hex!($pow_hash), + height: $height, + generated_coins: $generated_coins, + weight: $weight, + long_term_weight: $long_term_weight, + cumulative_difficulty: $cumulative_difficulty, + txs: vec![$($tx_blob),*], + } + .into_verified() + }) + } + }; } -/// 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 = OnceLock::new(); - BLOCK - .get_or_init(|| Block::read(&mut BLOCK_F91043).unwrap()) - .clone() +verified_block_information_fn! { + fn_name: block_v1_tx2, + block_blob: BLOCK_5ECB7E, + tx_blobs: [TX_2180A8, TX_D7FEBD], + pow_hash: "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000", + height: 202_612, + generated_coins: 13_138_270_468_431, + weight: 55_503, + long_term_weight: 55_503, + cumulative_difficulty: 126_654_460_829_362, + tx_len: 2, } -/// 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 = OnceLock::new(); - BLOCK - .get_or_init(|| Block::read(&mut BLOCK_43BD1F).unwrap()) - .clone() +verified_block_information_fn! { + fn_name: block_v9_tx3, + block_blob: BLOCK_F91043, + tx_blobs: [TX_E2D393, TX_E57440, TX_B6B439], + pow_hash: "7c78b5b67a112a66ea69ea51477492057dba9cfeaa2942ee7372c61800000000", + height: 1_731_606, + generated_coins: 3_403_921_682_163, + weight: 6_597, + long_term_weight: 6_597, + cumulative_difficulty: 23_558_910_234_058_343, + tx_len: 3, +} + +verified_block_information_fn! { + fn_name: block_v16_tx0, + block_blob: BLOCK_43BD1F, + tx_blobs: [], + pow_hash: "10b473b5d097d6bfa0656616951840724dfe38c6fb9c4adf8158800300000000", + height: 2_751_506, + generated_coins: 600_000_000_000, + weight: 106, + long_term_weight: 176_470, + cumulative_difficulty: 236_046_001_376_524_168, + tx_len: 0, } //---------------------------------------------------------------------------------------------------- Transactions -/// Return [`TX_3BC7FF`] as a [`Transaction`]. +/// Generate a transaction accessor function with this signature: +/// `fn() -> &'static TransactionVerificationData` /// -/// ```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 = OnceLock::new(); - TX.get_or_init(|| Transaction::read(&mut TX_3BC7FF).unwrap()) - .clone() +/// Same as [`verified_block_information_fn`] but for transactions. +macro_rules! transaction_verification_data_fn { + ( + fn_name: $fn_name:ident, // Name of the function created + tx_blobs: $tx_blob:ident, // Transaction blob ([u8], found in `constants.rs`) + weight: $weight:literal, // Transaction weight + hash: $hash:literal, // Transaction hash as a string literal + ) => { + #[doc = concat!("Return [`", stringify!($tx_blob), "`] as a [`TransactionVerificationData`].")] + /// + /// ```rust + #[doc = "# use cuprate_test_utils::data::*;"] + #[doc = "# use hex_literal::hex;"] + #[doc = concat!("let tx = ", stringify!($fn_name), "();")] + #[doc = concat!("assert_eq!(&tx.tx.serialize(), ", stringify!($tx_blob), ");")] + #[doc = concat!("assert_eq!(tx.tx_blob, ", stringify!($tx_blob), ");")] + #[doc = concat!("assert_eq!(tx.tx_weight, ", $weight, ");")] + #[doc = concat!("assert_eq!(tx.tx_hash, hex!(\"", $hash, "\"));")] + #[doc = "assert_eq!(tx.fee, tx.tx.rct_signatures.base.fee);"] + /// ``` + pub fn $fn_name() -> &'static TransactionVerificationData { + static TX: OnceLock = OnceLock::new(); + TX.get_or_init(|| to_tx_verification_data($tx_blob)) + } + }; } -/// 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 = OnceLock::new(); - TX.get_or_init(|| Transaction::read(&mut TX_9E3F73).unwrap()) - .clone() +transaction_verification_data_fn! { + fn_name: tx_v1_sig0, + tx_blobs: TX_3BC7FF, + weight: 248, + hash: "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1", } -/// 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 = OnceLock::new(); - TX.get_or_init(|| Transaction::read(&mut TX_84D48D).unwrap()) - .clone() +transaction_verification_data_fn! { + fn_name: tx_v1_sig2, + tx_blobs: TX_9E3F73, + weight: 448, + hash: "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34", +} + +transaction_verification_data_fn! { + fn_name: tx_v2_rct3, + tx_blobs: TX_84D48D, + weight: 2743, + hash: "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66", } diff --git a/test-utils/src/data/mod.rs b/test-utils/src/data/mod.rs index a721cfb..03c4524 100644 --- a/test-utils/src/data/mod.rs +++ b/test-utils/src/data/mod.rs @@ -1,9 +1,35 @@ -//! Testing data and utilities. +//! Real Monero data. //! -//! Raw data is found in `data/`. +//! This module provides access to _real_ Monero data, +//! either in raw bytes or typed. +//! +//! ## Constants +//! The `const`ants provide byte slices representing block +//! and transaction blobs that can be directly deserialized: +//! +//! ```rust +//! # use cuprate_test_utils::data::*; +//! use monero_serai::{block::Block, transaction::Transaction}; +//! +//! let block: Block = Block::read(&mut BLOCK_43BD1F).unwrap(); +//! let tx: Transaction = Transaction::read(&mut TX_E57440).unwrap(); +//! ``` +//! +//! ## Functions +//! The free functions provide access to typed data found in `cuprate_types`: +//! ```rust +//! # use cuprate_test_utils::data::*; +//! use cuprate_types::{VerifiedBlockInformation, TransactionVerificationData}; +//! +//! let block: VerifiedBlockInformation = block_v16_tx0().clone(); +//! let tx: TransactionVerificationData = tx_v1_sig0().clone(); +//! ``` mod constants; -pub use constants::{BLOCK_43BD1F, BLOCK_BBD604, BLOCK_F91043, TX_3BC7FF, TX_84D48D, TX_9E3F73}; +pub use constants::{ + BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_BBD604, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, + TX_9E3F73, TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440, +}; mod free; -pub use free::{block_v16_tx0, block_v1_tx513, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; +pub use free::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3}; diff --git a/test-utils/src/data/tx/2180a87f724702d37af087e22476297e818a73579ef7b7da947da963245202a3.bin b/test-utils/src/data/tx/2180a87f724702d37af087e22476297e818a73579ef7b7da947da963245202a3.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7c506deb072b3a0cdaec78ff40dd1bde7686b89 GIT binary patch literal 5714 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 literal 0 HcmV?d00001 diff --git a/test-utils/src/data/tx/b6b4394d4ec5f08ad63267c07962550064caa8d225dd9ad6d739ebf60291c169.bin b/test-utils/src/data/tx/b6b4394d4ec5f08ad63267c07962550064caa8d225dd9ad6d739ebf60291c169.bin new file mode 100644 index 0000000000000000000000000000000000000000..b4767363ee1740d5e3297aa014cbaa787a054069 GIT binary patch literal 2709 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 literal 0 HcmV?d00001 diff --git a/test-utils/src/data/tx/e2d39395dd1625b2d707b98af789e7eab9d24c2bd2978ec38ef910961a8cdcee.bin b/test-utils/src/data/tx/e2d39395dd1625b2d707b98af789e7eab9d24c2bd2978ec38ef910961a8cdcee.bin new file mode 100644 index 0000000000000000000000000000000000000000..f8667cf021a17c31c1661708880b877c70700a25 GIT binary patch literal 1911 zcmV--2Z;Cr009C33*Pa~1HtMSf}s%03bjV$V63VFskZ_B8~P4~2fiGK{EwB7X#3+v zp^f`7LIm8WS3hB@;YdKZ1AV2!h5`Tr45&P+Jmh7!;Ye>fZ*jc@4$iy;`)6YX3YXAN znFI(`00P{`Gd|U}t{c*XR3h?%To;1LVF_D75J+r0avjA_kf1~YApi-;?CLe$)&~U~ z8-?pH!~t9=Xwj@@;#AXZ;04un+EoG9v7v~MM&xq6h1b16Jn$u~^Mi`t6^4ASntbz^ zk^MCTkc`(9uN}iSKnzFb{Vh2pNv_^K(Y=i+yaJM2rOSJ2nY)w>u@5BqqoXHbHpvZ4 zE_Iw(3qFo$scUUshjVZg(VD#v$ys(A!@5k3Zi?z(d7d+ZR7C!Jt~n1Q%N6EFt(u<$ zDw;uLxqOu>mjX!d6d}>$slw4p=&8}t)U@sn0~AM9H~{A0RR91`N?^e z;;tbho*`AsTD#x9@v6278{8umz0hH*apsz$ATZ#4_>i04gjE#nd!ms=uV}K6pI70* zw-Bg-EA;8%8;#?<>?heSYWbEnn@)>Mg6%CEH&dJ-d4$iJ?+i>uDp9VoFW5E` z_DSwAJVXROZ(tyCCx4J_)c2ldq+wmcs!z6NXG%GCf;epRcb8Gre0yJTrZ~6K^QXHC z`ZLCT$Gm7NOE{Vz`{wzDf5%@K(;hHnE8qnaULbJ?2esdqS8E9s<0~9#gR2@ur@VWN zzTVQP_>PX=55p}z%_W2*;peyW?{m- zRh+IL9zo)v0gDHzZOo^>id`0l8a?mLpsGOMg&K0;1_6Va4o;+;C3E3P;w{&51u?I9g zI}v&MP|{cTc#5M~jYO!rhFYQd4_3WJuWl|$d+p-=W%n2_`vYTT#KlSgZ*vHJSVR(< zl32o_X4CqLG;3?SMEttbfw0}N=6}*$3iShomf4G_(^Nx<;oMc@~|X2jd;K% z#uo%CJvdkl`h+-^&~#H;nASl>xs zDN2zI13HeV4`pnF*@8vdmIpkTwmlOaav>3!83@f_1W;-;U8)O4pVAlWb-&t{@c_Nl zLoNE*(1JCB%{C5DyuSq2WXWj4^wA zcW>Tc{wj$D!BsIU)d z5d-qx|GzuB2$XLREYL`dswXI46oLBU`{rHO9s;aXg6SVVhf!ilH4Rx&N)Rcm>ROUX6jWOCroch)?rq^RKjrCE zDFiFFCk^sp=sZ5aXrb;zg~SsVdwg^gizS-<+((FQrzz|;V+Cz`XQT_SkY<#VU3sog z>PuNCM+7V(z?+C;PK@%^f(Qxjm8;_wEOxFvFnpklBi=65>ynN=Bg08)>M&;Z;sdgz zEEe`Di39$(+dSyeLG<}wf(46O)*Yv6R6clagz8u)ingOp5g!i literal 0 HcmV?d00001 diff --git a/test-utils/src/data/tx/e57440ec66d2f3b2a5fa2081af40128868973e7c021bb3877290db3066317474.bin b/test-utils/src/data/tx/e57440ec66d2f3b2a5fa2081af40128868973e7c021bb3877290db3066317474.bin new file mode 100644 index 0000000000000000000000000000000000000000..578163801ec2d881113b963fcd1a6fa77255d35a GIT binary patch literal 1887 zcmV-l2cY-@009C33$f?F1L)fto6rP|8NS~cw21VVT)yStMeZ00L1LX%OJ>r%L;E zrvfdi00IbNl;a|8I%IXTCb=bskR65PbUM#%64Nw9mW58H7E>$&2?5mIC|uPcl)3>^ zl9S}?S1FkTX_PF&YkxS?Gg)pcD*CDEcUtnfcG@@tpu@EkhY8GowIer#4*96MHpX{c zDEWZ%l+oGxeyI-d+GXAifk}cq;+J58zU;@T-mdBi)+!tB?a{xg?koW+^n@MNFoRPKYubD%Anatv+<9-$B;*O|WJ@xmlKkIfTC@>hp2Oxv>tvnK#kCb!}kMOy~jW}?C_hb}!^;Bv~4$9rWLfw+rQ&g!|+sTI{ z^4!e-y_I8A=Nwm^?}Eycg$P}@Crfc!?AbX%E3hMW`ran6MT}!_&UQ~RY!*qZVehFJ z2CPlb1D`V&?4!Mu(*xTMp{2;>8UtLwJ#b9h|NLBs_Squ;2wP>|1-2ub&6E68sssC{ zRz#`PNbh>J+Icb!(XRn$2T7$fDR|-hd5lg$!4KD8;|C&o%{et80sPY2Di#!jAiK^C z{RbO+b3z{kv(?Ga;|~Ru_z}thhUCds%Lms&Xd;m>l3~&#Z|#P-VddBe!xm}2CxXR? z`FQQH4^YdTR(90Yc{Y7Fl;nbeY@qDllsdmNcJ@3Ep9u2`xVbS7OXnzn-B6qs75jF0 z(X0eL0bnZ!02fU8qyTr#A~fHKZ%@{{lUd06PPD*oGTKmgp*j~sic%%>Z}}x~;au27 z7`-+cW7@Cb^Bofb4BCe~(ers&q0QyKpqPES4u3;ntE#oDB8)nwM@+Qcn@hCENsOrs zZ`Hk4GtHNeJuH0eJ<55z2_Ejp?53eA^XuGS(X_bhy0P4xz*3Yll$wBZtZL zcF$tX&ZNelTmvL881k&Y&$i2U1`Jx=aN-3IX>Z4Spen3O2M8)@gJ?sF5w$-tb8Gdi zj|O5BAoZ^n^Khfo_i!t@cb0lbUMWeW>B{Y9oCi>AcLw6#h1-u)uFaYcuVlbC6sRaTDD?*e{X<-oU4nhQwXT(<;U_Gh{F z{s6^Vbj!}@jx85$R1vzkm~1!>td^c{$CPO-0_%(~8wZ-4NiQHbe4ae(I55acQ0J@Q zB6bR>^bQBoZMyLLA`C^7CKCv$ct&s@R?f@RB}`wxGf{iqE1gFtgr0CWZ?xfz7-MSTf+WCD5}fJsIC#f_E~CLG=#pw&y|BQYTX)b<7-wQy|TFs;A=pWOS)3TzqB`<|0v{sqzqGBoWuFt=WTZ2of5 zG1^WgaYT&Pp7u`0|CH6vfeEgN{$-V6;lz9L1zi_B8D5OyuayEbImPSME^RJna0bgy z#HZS81At48FbP12TXCq;G%)1JN0PbY1)nVt6b(!T0)U+3YnL4;G>K#=8no>Tj7Jd2 zXYFL>W=u7Ok_%Uz{r)EH_^iR()Jo@BGW-)9Bud_4Rvq(iPDzCE&kcjJ(YKVUndcTQ Ze%JHm(x(7xOVZZ