zebra-chain 6.0.2

Core Zcash data structures
Documentation
//! Generate large transparent blocks and transactions for testing.

use chrono::DateTime;
use std::sync::Arc;

use crate::{
    block::{serialize::MAX_BLOCK_BYTES, Block, Header},
    serialization::{ZcashDeserialize, ZcashSerialize},
    transaction::{LockTime, Transaction},
    transparent,
};

/// The minimum size of the blocks produced by this module.
pub const MIN_LARGE_BLOCK_BYTES: u64 = MAX_BLOCK_BYTES - 100;

/// The maximum number of bytes used to serialize a CompactSize,
/// for the transaction, input, and output counts generated by this module.
pub const MAX_COMPACT_SIZE_BYTES: usize = 4;

/// The number of bytes used to serialize a version 1 transaction header.
pub const TX_V1_HEADER_BYTES: usize = 4;

/// Returns a generated block header, and its canonical serialized bytes.
pub fn block_header() -> (Header, Vec<u8>) {
    // Some of the test vectors are in a non-canonical format,
    // so we have to round-trip serialize them.

    let block_header = Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();
    let block_header_bytes = block_header.zcash_serialize_to_vec().unwrap();

    (block_header, block_header_bytes)
}

/// Returns a generated transparent transaction, and its canonical serialized bytes.
pub fn transaction() -> (Transaction, Vec<u8>) {
    // Some of the test vectors are in a non-canonical format,
    // so we have to round-trip serialize them.

    let transaction = Transaction::zcash_deserialize(&zebra_test::vectors::DUMMY_TX1[..]).unwrap();
    let transaction_bytes = transaction.zcash_serialize_to_vec().unwrap();

    (transaction, transaction_bytes)
}

/// Returns a generated transparent input, and its canonical serialized bytes.
pub fn input() -> (transparent::Input, Vec<u8>) {
    // Some of the test vectors are in a non-canonical format,
    // so we have to round-trip serialize them.

    let input =
        transparent::Input::zcash_deserialize(&zebra_test::vectors::DUMMY_INPUT1[..]).unwrap();
    let input_bytes = input.zcash_serialize_to_vec().unwrap();

    (input, input_bytes)
}

/// Returns a generated transparent output, and its canonical serialized bytes.
pub fn output() -> (transparent::Output, Vec<u8>) {
    // Some of the test vectors are in a non-canonical format,
    // so we have to round-trip serialize them.

    let output =
        transparent::Output::zcash_deserialize(&zebra_test::vectors::DUMMY_OUTPUT1[..]).unwrap();
    let output_bytes = output.zcash_serialize_to_vec().unwrap();

    (output, output_bytes)
}

/// Generate a block with multiple transparent transactions just below limit
///
/// TODO: add a coinbase height to the returned block
pub fn large_multi_transaction_block() -> Block {
    multi_transaction_block(false)
}

/// Generate a block with one transaction and multiple transparent inputs just below limit
///
/// TODO: add a coinbase height to the returned block
///       make the returned block stable under round-trip serialization
pub fn large_single_transaction_block_many_inputs() -> Block {
    single_transaction_block_many_inputs(false)
}

/// Generate a block with one transaction and multiple transparent outputs just below limit
///
/// TODO: add a coinbase height to the returned block
///       make the returned block stable under round-trip serialization
pub fn large_single_transaction_block_many_outputs() -> Block {
    single_transaction_block_many_outputs(false)
}

/// Generate a block with multiple transparent transactions just above limit
///
/// TODO: add a coinbase height to the returned block
pub fn oversized_multi_transaction_block() -> Block {
    multi_transaction_block(true)
}

/// Generate a block with one transaction and multiple transparent inputs just above limit
///
/// TODO: add a coinbase height to the returned block
///       make the returned block stable under round-trip serialization
pub fn oversized_single_transaction_block_many_inputs() -> Block {
    single_transaction_block_many_inputs(true)
}

/// Generate a block with one transaction and multiple transparent outputs just above limit
///
/// TODO: add a coinbase height to the returned block
///       make the returned block stable under round-trip serialization
pub fn oversized_single_transaction_block_many_outputs() -> Block {
    single_transaction_block_many_outputs(true)
}

/// Implementation of block generation with multiple transparent transactions
fn multi_transaction_block(oversized: bool) -> Block {
    // A dummy transaction
    let (transaction, transaction_bytes) = transaction();

    // A block header
    let (block_header, block_header_bytes) = block_header();

    // Calculate the number of transactions we need,
    // subtracting the bytes used to serialize the expected transaction count.
    let mut max_transactions_in_block = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
        - block_header_bytes.len()
        - MAX_COMPACT_SIZE_BYTES)
        / transaction_bytes.len();
    if oversized {
        max_transactions_in_block += 1;
    }

    // Create transactions to be just below or just above the limit
    let transactions =
        std::iter::repeat_n(Arc::new(transaction), max_transactions_in_block).collect::<Vec<_>>();

    // Add the transactions into a block
    let block = Block {
        header: block_header.into(),
        transactions,
    };

    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
    assert_eq!(
        oversized,
        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
        "block is over-sized if requested:\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
    );
    assert!(
        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
        "block is large\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
    );

    block
}

/// Implementation of block generation with one transaction and multiple transparent inputs
fn single_transaction_block_many_inputs(oversized: bool) -> Block {
    // Dummy input and output
    let (input, input_bytes) = input();
    let (output, output_bytes) = output();

    // A block header
    let (block_header, block_header_bytes) = block_header();

    let lock_time = LockTime::Time(DateTime::from_timestamp(61, 0).unwrap());
    let lock_time_bytes = lock_time.zcash_serialize_to_vec().unwrap();

    // Calculate the number of inputs we need,
    // subtracting the bytes used to serialize the expected input count,
    // transaction count, and output count.
    let mut max_inputs_in_tx = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
        - block_header_bytes.len()
        - 1
        - TX_V1_HEADER_BYTES
        - lock_time_bytes.len()
        - MAX_COMPACT_SIZE_BYTES
        - 1
        - output_bytes.len())
        / input_bytes.len();

    if oversized {
        max_inputs_in_tx += 1;
    }

    let mut outputs = Vec::new();

    // Create inputs to be just below the limit
    let inputs = std::iter::repeat_n(input, max_inputs_in_tx).collect::<Vec<_>>();

    // 1 single output
    outputs.push(output);

    // Create a big transaction
    let big_transaction = Transaction::V1 {
        inputs,
        outputs,
        lock_time,
    };

    // Put the big transaction into a block
    let transactions = vec![Arc::new(big_transaction)];

    let block = Block {
        header: block_header.into(),
        transactions,
    };

    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
    assert_eq!(
        oversized,
        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
        "block is over-sized if requested:\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
    );
    assert!(
        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
        "block is large\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
    );

    block
}

/// Implementation of block generation with one transaction and multiple transparent outputs
fn single_transaction_block_many_outputs(oversized: bool) -> Block {
    // Dummy input and output
    let (input, input_bytes) = input();
    let (output, output_bytes) = output();

    // A block header
    let (block_header, block_header_bytes) = block_header();

    let lock_time = LockTime::Time(DateTime::from_timestamp(61, 0).unwrap());
    let lock_time_bytes = lock_time.zcash_serialize_to_vec().unwrap();

    // Calculate the number of outputs we need,
    // subtracting the bytes used to serialize the expected output count,
    // transaction count, and input count.
    let mut max_outputs_in_tx = (usize::try_from(MAX_BLOCK_BYTES).unwrap()
        - block_header_bytes.len()
        - 1
        - TX_V1_HEADER_BYTES
        - lock_time_bytes.len()
        - 1
        - input_bytes.len()
        - MAX_COMPACT_SIZE_BYTES)
        / output_bytes.len();

    if oversized {
        max_outputs_in_tx += 1;
    }

    // 1 single input
    let inputs = vec![input];

    // Create outputs to be just below the limit
    let outputs = std::iter::repeat_n(output, max_outputs_in_tx).collect::<Vec<_>>();

    // Create a big transaction
    let big_transaction = Transaction::V1 {
        inputs,
        outputs,
        lock_time,
    };

    // Put the big transaction into a block
    let transactions = vec![Arc::new(big_transaction)];

    let block = Block {
        header: block_header.into(),
        transactions,
    };

    let serialized_len = block.zcash_serialize_to_vec().unwrap().len();
    assert_eq!(
        oversized,
        serialized_len > MAX_BLOCK_BYTES.try_into().unwrap(),
        "block is over-sized if requested:\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MAX_BLOCK_BYTES: {MAX_BLOCK_BYTES},",
    );
    assert!(
        serialized_len > MIN_LARGE_BLOCK_BYTES.try_into().unwrap(),
        "block is large\n\
         oversized: {oversized},\n\
         serialized_len: {serialized_len},\n\
         MIN_LARGE_BLOCK_BYTES: {MIN_LARGE_BLOCK_BYTES},",
    );

    block
}