zebra_chain/block/tests/
generate.rs

1//! Generate large transparent blocks and transactions for testing.
2
3use chrono::DateTime;
4use std::sync::Arc;
5
6use crate::{
7    block::{serialize::MAX_BLOCK_BYTES, Block, Header},
8    serialization::{ZcashDeserialize, ZcashSerialize},
9    transaction::{LockTime, Transaction},
10    transparent,
11};
12
13/// The minimum size of the blocks produced by this module.
14pub const MIN_LARGE_BLOCK_BYTES: u64 = MAX_BLOCK_BYTES - 100;
15
16/// The maximum number of bytes used to serialize a CompactSize,
17/// for the transaction, input, and output counts generated by this module.
18pub const MAX_COMPACT_SIZE_BYTES: usize = 4;
19
20/// The number of bytes used to serialize a version 1 transaction header.
21pub const TX_V1_HEADER_BYTES: usize = 4;
22
23/// Returns a generated block header, and its canonical serialized bytes.
24pub fn block_header() -> (Header, Vec<u8>) {
25    // Some of the test vectors are in a non-canonical format,
26    // so we have to round-trip serialize them.
27
28    let block_header = Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
29    let block_header_bytes = block_header.zcash_serialize_to_vec().unwrap();
30
31    (block_header, block_header_bytes)
32}
33
34/// Returns a generated transparent transaction, and its canonical serialized bytes.
35pub fn transaction() -> (Transaction, Vec<u8>) {
36    // Some of the test vectors are in a non-canonical format,
37    // so we have to round-trip serialize them.
38
39    let transaction = Transaction::zcash_deserialize(&zebra_test::vectors::DUMMY_TX1[..]).unwrap();
40    let transaction_bytes = transaction.zcash_serialize_to_vec().unwrap();
41
42    (transaction, transaction_bytes)
43}
44
45/// Returns a generated transparent input, and its canonical serialized bytes.
46pub fn input() -> (transparent::Input, Vec<u8>) {
47    // Some of the test vectors are in a non-canonical format,
48    // so we have to round-trip serialize them.
49
50    let input =
51        transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
52    let input_bytes = input.zcash_serialize_to_vec().unwrap();
53
54    (input, input_bytes)
55}
56
57/// Returns a generated transparent output, and its canonical serialized bytes.
58pub fn output() -> (transparent::Output, Vec<u8>) {
59    // Some of the test vectors are in a non-canonical format,
60    // so we have to round-trip serialize them.
61
62    let output =
63        transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
64    let output_bytes = output.zcash_serialize_to_vec().unwrap();
65
66    (output, output_bytes)
67}
68
69/// Generate a block with multiple transparent transactions just below limit
70///
71/// TODO: add a coinbase height to the returned block
72pub fn large_multi_transaction_block() -> Block {
73    multi_transaction_block(false)
74}
75
76/// Generate a block with one transaction and multiple transparent inputs just below limit
77///
78/// TODO: add a coinbase height to the returned block
79///       make the returned block stable under round-trip serialization
80pub fn large_single_transaction_block_many_inputs() -> Block {
81    single_transaction_block_many_inputs(false)
82}
83
84/// Generate a block with one transaction and multiple transparent outputs just below limit
85///
86/// TODO: add a coinbase height to the returned block
87///       make the returned block stable under round-trip serialization
88pub fn large_single_transaction_block_many_outputs() -> Block {
89    single_transaction_block_many_outputs(false)
90}
91
92/// Generate a block with multiple transparent transactions just above limit
93///
94/// TODO: add a coinbase height to the returned block
95pub fn oversized_multi_transaction_block() -> Block {
96    multi_transaction_block(true)
97}
98
99/// Generate a block with one transaction and multiple transparent inputs just above limit
100///
101/// TODO: add a coinbase height to the returned block
102///       make the returned block stable under round-trip serialization
103pub fn oversized_single_transaction_block_many_inputs() -> Block {
104    single_transaction_block_many_inputs(true)
105}
106
107/// Generate a block with one transaction and multiple transparent outputs just above limit
108///
109/// TODO: add a coinbase height to the returned block
110///       make the returned block stable under round-trip serialization
111pub fn oversized_single_transaction_block_many_outputs() -> Block {
112    single_transaction_block_many_outputs(true)
113}
114
115/// Implementation of block generation with multiple transparent transactions
116fn multi_transaction_block(oversized: bool) -> Block {
117    // A dummy transaction
118    let (transaction, transaction_bytes) = transaction();
119
120    // A block header
121    let (block_header, block_header_bytes) = block_header();
122
123    // Calculate the number of transactions we need,
124    // subtracting the bytes used to serialize the expected transaction count.
125    let mut max_transactions_in_block = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
126        - block_header_bytes.len()
127        - MAX_COMPACT_SIZE_BYTES)
128        / transaction_bytes.len();
129    if oversized {
130        max_transactions_in_block += 1;
131    }
132
133    // Create transactions to be just below or just above the limit
134    let transactions =
135        std::iter::repeat_n(Arc::new(transaction), max_transactions_in_block).collect::<Vec<_>>();
136
137    // Add the transactions into a block
138    let block = Block {
139        header: block_header.into(),
140        transactions,
141    };
142
143    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
144    assert_eq!(
145        oversized,
146        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
147        "block is over-sized if requested:\n\
148         oversized: {oversized},\n\
149         serialized_len: {serialized_len},\n\
150         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
151    );
152    assert!(
153        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
154        "block is large\n\
155         oversized: {oversized},\n\
156         serialized_len: {serialized_len},\n\
157         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
158    );
159
160    block
161}
162
163/// Implementation of block generation with one transaction and multiple transparent inputs
164fn single_transaction_block_many_inputs(oversized: bool) -> Block {
165    // Dummy input and output
166    let (input, input_bytes) = input();
167    let (output, output_bytes) = output();
168
169    // A block header
170    let (block_header, block_header_bytes) = block_header();
171
172    let lock_time = LockTime::Time(DateTime::from_timestamp(61, 0).unwrap());
173    let lock_time_bytes = lock_time.zcash_serialize_to_vec().unwrap();
174
175    // Calculate the number of inputs we need,
176    // subtracting the bytes used to serialize the expected input count,
177    // transaction count, and output count.
178    let mut max_inputs_in_tx = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
179        - block_header_bytes.len()
180        - 1
181        - TX_V1_HEADER_BYTES
182        - lock_time_bytes.len()
183        - MAX_COMPACT_SIZE_BYTES
184        - 1
185        - output_bytes.len())
186        / input_bytes.len();
187
188    if oversized {
189        max_inputs_in_tx += 1;
190    }
191
192    let mut outputs = Vec::new();
193
194    // Create inputs to be just below the limit
195    let inputs = std::iter::repeat_n(input, max_inputs_in_tx).collect::<Vec<_>>();
196
197    // 1 single output
198    outputs.push(output);
199
200    // Create a big transaction
201    let big_transaction = Transaction::V1 {
202        inputs,
203        outputs,
204        lock_time,
205    };
206
207    // Put the big transaction into a block
208    let transactions = vec![Arc::new(big_transaction)];
209
210    let block = Block {
211        header: block_header.into(),
212        transactions,
213    };
214
215    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
216    assert_eq!(
217        oversized,
218        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
219        "block is over-sized if requested:\n\
220         oversized: {oversized},\n\
221         serialized_len: {serialized_len},\n\
222         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
223    );
224    assert!(
225        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
226        "block is large\n\
227         oversized: {oversized},\n\
228         serialized_len: {serialized_len},\n\
229         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
230    );
231
232    block
233}
234
235/// Implementation of block generation with one transaction and multiple transparent outputs
236fn single_transaction_block_many_outputs(oversized: bool) -> Block {
237    // Dummy input and output
238    let (input, input_bytes) = input();
239    let (output, output_bytes) = output();
240
241    // A block header
242    let (block_header, block_header_bytes) = block_header();
243
244    let lock_time = LockTime::Time(DateTime::from_timestamp(61, 0).unwrap());
245    let lock_time_bytes = lock_time.zcash_serialize_to_vec().unwrap();
246
247    // Calculate the number of outputs we need,
248    // subtracting the bytes used to serialize the expected output count,
249    // transaction count, and input count.
250    let mut max_outputs_in_tx = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
251        - block_header_bytes.len()
252        - 1
253        - TX_V1_HEADER_BYTES
254        - lock_time_bytes.len()
255        - 1
256        - input_bytes.len()
257        - MAX_COMPACT_SIZE_BYTES)
258        / output_bytes.len();
259
260    if oversized {
261        max_outputs_in_tx += 1;
262    }
263
264    // 1 single input
265    let inputs = vec![input];
266
267    // Create outputs to be just below the limit
268    let outputs = std::iter::repeat_n(output, max_outputs_in_tx).collect::<Vec<_>>();
269
270    // Create a big transaction
271    let big_transaction = Transaction::V1 {
272        inputs,
273        outputs,
274        lock_time,
275    };
276
277    // Put the big transaction into a block
278    let transactions = vec![Arc::new(big_transaction)];
279
280    let block = Block {
281        header: block_header.into(),
282        transactions,
283    };
284
285    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
286    assert_eq!(
287        oversized,
288        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
289        "block is over-sized if requested:\n\
290         oversized: {oversized},\n\
291         serialized_len: {serialized_len},\n\
292         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
293    );
294    assert!(
295        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
296        "block is large\n\
297         oversized: {oversized},\n\
298         serialized_len: {serialized_len},\n\
299         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
300    );
301
302    block
303}