cuprate_test_utils/data/
statics.rs

1//! `static LazyLock`s to access data.
2
3#![allow(
4    const_item_mutation, // `R: Read` needs `&mut self`
5    clippy::missing_panics_doc, // These functions shouldn't panic
6)]
7
8//---------------------------------------------------------------------------------------------------- Import
9use std::sync::LazyLock;
10
11use hex_literal::hex;
12use monero_serai::{block::Block, transaction::Transaction};
13
14use cuprate_helper::{map::combine_low_high_bits_to_u128, tx::tx_fee};
15use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
16
17use crate::data::constants::{
18    BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73,
19    TX_B6B439, TX_D7FEBD, TX_E2D393, TX_E57440,
20};
21
22//---------------------------------------------------------------------------------------------------- Conversion
23/// Converts [`monero_serai::Block`] into a
24/// [`VerifiedBlockInformation`] (superset).
25///
26/// To prevent pulling other code in order to actually calculate things
27/// (e.g. `pow_hash`), some information must be provided statically,
28/// this struct represents that data that must be provided.
29///
30/// Consider using [`cuprate_test_utils::rpc`] to get this data easily.
31struct VerifiedBlockMap {
32    block_blob: &'static [u8],
33    pow_hash: [u8; 32],
34    height: usize,
35    generated_coins: u64,
36    weight: usize,
37    long_term_weight: usize,
38    cumulative_difficulty_low: u64,
39    cumulative_difficulty_high: u64,
40    // Vec of `tx_blob`'s, i.e. the data in `/test-utils/src/data/tx/`.
41    // This should the actual `tx_blob`'s of the transactions within this block.
42    txs: &'static [&'static [u8]],
43}
44
45impl VerifiedBlockMap {
46    /// Turn the various static data bits in `self` into a [`VerifiedBlockInformation`].
47    ///
48    /// Transactions are verified that they at least match the block's,
49    /// although the correctness of data (whether this block actually existed or not)
50    /// is not checked.
51    fn into_verified(self) -> VerifiedBlockInformation {
52        let Self {
53            block_blob,
54            pow_hash,
55            height,
56            generated_coins,
57            weight,
58            long_term_weight,
59            cumulative_difficulty_low,
60            cumulative_difficulty_high,
61            txs,
62        } = self;
63
64        let block_blob = block_blob.to_vec();
65        let block = Block::read(&mut block_blob.as_slice()).unwrap();
66
67        let txs = txs.iter().map(to_tx_verification_data).collect::<Vec<_>>();
68
69        assert_eq!(
70            txs.len(),
71            block.transactions.len(),
72            "(deserialized txs).len() != (txs hashes in block).len()"
73        );
74
75        for (tx, tx_hash_in_block) in txs.iter().zip(&block.transactions) {
76            assert_eq!(
77                &tx.tx_hash, tx_hash_in_block,
78                "deserialized tx hash is not the same as the one in the parent block"
79            );
80        }
81
82        VerifiedBlockInformation {
83            block_hash: block.hash(),
84            block_blob,
85            block,
86            txs,
87            pow_hash,
88            height,
89            generated_coins,
90            weight,
91            long_term_weight,
92            cumulative_difficulty: combine_low_high_bits_to_u128(
93                cumulative_difficulty_low,
94                cumulative_difficulty_high,
95            ),
96        }
97    }
98}
99
100// Same as [`VerifiedBlockMap`] but for [`VerifiedTransactionInformation`].
101fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInformation {
102    let tx_blob = tx_blob.as_ref().to_vec();
103    let tx = Transaction::read(&mut tx_blob.as_slice()).unwrap();
104    VerifiedTransactionInformation {
105        tx_weight: tx.weight(),
106        fee: tx_fee(&tx),
107        tx_hash: tx.hash(),
108        tx_blob,
109        tx,
110    }
111}
112
113//---------------------------------------------------------------------------------------------------- Blocks
114/// Generate a `static LazyLock<VerifiedBlockInformation>`.
115///
116/// This will use `VerifiedBlockMap` type above to do various
117/// checks on the input data and makes sure it seems correct.
118///
119/// This requires some static block/tx input (from data) and some fields.
120/// This data can be accessed more easily via:
121/// - A block explorer (<https://xmrchain.net>)
122/// - Monero RPC (see `cuprate_test_utils::rpc` for this)
123///
124/// See below for actual usage.
125macro_rules! verified_block_information {
126    (
127        name: $name:ident, // Name of the `LazyLock` created
128        block_blob: $block_blob:ident, // Block blob ([u8], found in `constants.rs`)
129        tx_blobs: [$($tx_blob:ident),*], // Array of contained transaction blobs
130        pow_hash: $pow_hash:literal, // PoW hash as a string literal
131        height: $height:literal, // Block height
132        generated_coins: $generated_coins:literal, // Generated coins in block (minus fees)
133        weight: $weight:literal, // Block weight
134        long_term_weight: $long_term_weight:literal, // Block long term weight
135        cumulative_difficulty_low: $cumulative_difficulty_low:literal, // Least significant 64-bits of block cumulative difficulty
136        cumulative_difficulty_high: $cumulative_difficulty_high:literal, // Most significant 64-bits of block cumulative difficulty
137        tx_len: $tx_len:literal, // Amount of transactions in this block
138    ) => {
139        #[doc = concat!(
140            "Return [`",
141            stringify!($block_blob),
142            "`] as a [`VerifiedBlockInformation`].",
143        )]
144        ///
145        /// Contained transactions:
146        $(
147            #[doc = concat!("- [`", stringify!($tx_blob), "`]")]
148        )*
149        ///
150        /// ```rust
151        #[doc = "# use cuprate_test_utils::data::*;"]
152        #[doc = "# use hex_literal::hex;"]
153        #[doc = "use cuprate_helper::map::combine_low_high_bits_to_u128;"]
154        #[doc = ""]
155        #[doc = concat!("let block = &*", stringify!($name), ";")]
156        #[doc = concat!("assert_eq!(&block.block.serialize(), ", stringify!($block_blob), ");")]
157        #[doc = concat!("assert_eq!(block.pow_hash, hex!(\"", $pow_hash, "\"));")]
158        #[doc = concat!("assert_eq!(block.height, ", $height, ");")]
159        #[doc = concat!("assert_eq!(block.generated_coins, ", $generated_coins, ");")]
160        #[doc = concat!("assert_eq!(block.weight, ", $weight, ");")]
161        #[doc = concat!("assert_eq!(block.long_term_weight, ", $long_term_weight, ");")]
162        #[doc = concat!("assert_eq!(block.txs.len(), ", $tx_len, ");")]
163        #[doc = ""]
164        #[doc = concat!(
165            "assert_eq!(block.cumulative_difficulty, ",
166            "combine_low_high_bits_to_u128(",
167            stringify!($cumulative_difficulty_low),
168            ", ",
169            stringify!($cumulative_difficulty_high),
170            "));"
171        )]
172        /// ```
173        pub static $name: LazyLock<VerifiedBlockInformation> = LazyLock::new(|| {
174            VerifiedBlockMap {
175                block_blob: $block_blob,
176                pow_hash: hex!($pow_hash),
177                height: $height,
178                generated_coins: $generated_coins,
179                weight: $weight,
180                long_term_weight: $long_term_weight,
181                cumulative_difficulty_low: $cumulative_difficulty_low,
182                cumulative_difficulty_high: $cumulative_difficulty_high,
183                txs: &[$($tx_blob),*],
184            }
185            .into_verified()
186        });
187    };
188}
189
190verified_block_information! {
191    name: BLOCK_V1_TX2,
192    block_blob: BLOCK_5ECB7E,
193    tx_blobs: [TX_2180A8, TX_D7FEBD],
194    pow_hash: "c960d540000459480560b7816de968c7470083e5874e10040bdd4cc501000000",
195    height: 202_609,
196    generated_coins: 14_535_350_982_449,
197    weight: 21_905,
198    long_term_weight: 21_905,
199    cumulative_difficulty_low: 126_650_740_038_710,
200    cumulative_difficulty_high: 0,
201    tx_len: 2,
202}
203
204verified_block_information! {
205    name: BLOCK_V9_TX3,
206    block_blob: BLOCK_F91043,
207    tx_blobs: [TX_E2D393, TX_E57440, TX_B6B439],
208    pow_hash: "7c78b5b67a112a66ea69ea51477492057dba9cfeaa2942ee7372c61800000000",
209    height: 1_731_606,
210    generated_coins: 3_403_774_022_163,
211    weight: 6_597,
212    long_term_weight: 6_597,
213    cumulative_difficulty_low: 23_558_910_234_058_343,
214    cumulative_difficulty_high: 0,
215    tx_len: 3,
216}
217
218verified_block_information! {
219    name: BLOCK_V16_TX0,
220    block_blob: BLOCK_43BD1F,
221    tx_blobs: [],
222    pow_hash: "10b473b5d097d6bfa0656616951840724dfe38c6fb9c4adf8158800300000000",
223    height: 2_751_506,
224    generated_coins: 600_000_000_000,
225    weight: 106,
226    long_term_weight: 176_470,
227    cumulative_difficulty_low: 236_046_001_376_524_168,
228    cumulative_difficulty_high: 0,
229    tx_len: 0,
230}
231
232//---------------------------------------------------------------------------------------------------- Transactions
233/// Generate a `const LazyLock<VerifiedTransactionInformation>`.
234///
235/// Same as [`verified_block_information`] but for transactions.
236macro_rules! transaction_verification_data {
237    (
238        name: $name:ident, // Name of the `LazyLock` created
239        tx_blobs: $tx_blob:ident, // Transaction blob ([u8], found in `constants.rs`)
240        weight: $weight:literal, // Transaction weight
241        hash: $hash:literal, // Transaction hash as a string literal
242    ) => {
243        #[doc = concat!("Return [`", stringify!($tx_blob), "`] as a [`VerifiedTransactionInformation`].")]
244        ///
245        /// ```rust
246        #[doc = "# use cuprate_test_utils::data::*;"]
247        #[doc = "# use hex_literal::hex;"]
248        #[doc = concat!("let tx = &*", stringify!($name), ";")]
249        #[doc = concat!("assert_eq!(&tx.tx.serialize(), ", stringify!($tx_blob), ");")]
250        #[doc = concat!("assert_eq!(tx.tx_blob, ", stringify!($tx_blob), ");")]
251        #[doc = concat!("assert_eq!(tx.tx_weight, ", $weight, ");")]
252        #[doc = concat!("assert_eq!(tx.tx_hash, hex!(\"", $hash, "\"));")]
253        /// ```
254        pub static $name: LazyLock<VerifiedTransactionInformation> = LazyLock::new(|| {
255            to_tx_verification_data($tx_blob)
256        });
257    };
258}
259
260transaction_verification_data! {
261    name: TX_V1_SIG0,
262    tx_blobs: TX_3BC7FF,
263    weight: 248,
264    hash: "3bc7ff015b227e7313cc2e8668bfbb3f3acbee274a9c201d6211cf681b5f6bb1",
265}
266
267transaction_verification_data! {
268    name: TX_V1_SIG2,
269    tx_blobs: TX_9E3F73,
270    weight: 448,
271    hash: "9e3f73e66d7c7293af59c59c1ff5d6aae047289f49e5884c66caaf4aea49fb34",
272}
273
274transaction_verification_data! {
275    name: TX_V2_RCT3,
276    tx_blobs: TX_84D48D,
277    weight: 2743,
278    hash: "84d48dc11ec91950f8b70a85af9db91fe0c8abef71ef5db08304f7344b99ea66",
279}
280
281//---------------------------------------------------------------------------------------------------- TESTS
282#[cfg(test)]
283mod tests {
284    use pretty_assertions::assert_eq;
285
286    use crate::rpc::client::HttpRpcClient;
287
288    use super::*;
289
290    /// Assert the defined blocks are the same compared to ones received from a local RPC call.
291    #[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node
292    #[tokio::test]
293    async fn block_same_as_rpc() {
294        let rpc = HttpRpcClient::new(None).await;
295        for block in [&*BLOCK_V1_TX2, &*BLOCK_V9_TX3, &*BLOCK_V16_TX0] {
296            println!("block_height: {}", block.height);
297            let block_rpc = rpc.get_verified_block_information(block.height).await;
298            assert_eq!(block, &block_rpc);
299        }
300    }
301
302    /// Same as `block_same_as_rpc` but for transactions.
303    /// This also tests all the transactions within the defined blocks.
304    #[ignore] // FIXME: doesn't work in CI, we need a real unrestricted node
305    #[tokio::test]
306    async fn tx_same_as_rpc() {
307        let rpc = HttpRpcClient::new(None).await;
308
309        let mut txs = [&*BLOCK_V1_TX2, &*BLOCK_V9_TX3, &*BLOCK_V16_TX0]
310            .into_iter()
311            .flat_map(|block| block.txs.iter().cloned())
312            .collect::<Vec<VerifiedTransactionInformation>>();
313
314        txs.extend([TX_V1_SIG0.clone(), TX_V1_SIG2.clone(), TX_V2_RCT3.clone()]);
315
316        for tx in txs {
317            println!("tx_hash: {:?}", tx.tx_hash);
318            let tx_rpc = rpc
319                .get_transaction_verification_data(&[tx.tx_hash])
320                .await
321                .collect::<Vec<VerifiedTransactionInformation>>()
322                .pop()
323                .unwrap();
324            assert_eq!(tx, tx_rpc);
325        }
326    }
327}