synor/crates/synor-consensus/benches/consensus_bench.rs
2026-01-08 05:22:24 +05:30

495 lines
15 KiB
Rust

//! Consensus benchmarks for Synor.
//!
//! Benchmarks transaction validation, block validation, and UTXO operations.
//! Run with: cargo bench -p synor-consensus
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use synor_consensus::{
utxo::{UtxoDiff, UtxoEntry, UtxoSet},
validation::{BlockValidator, TransactionValidator},
};
use synor_types::{
block::{Block, BlockBody, BlockHeader},
transaction::{Outpoint, ScriptPubKey, SubnetworkId, Transaction, TxInput, TxOutput},
Amount, Hash256, Timestamp,
};
// ==================== Test Data Helpers ====================
/// Creates a deterministic hash.
fn make_hash(n: u64) -> Hash256 {
let mut bytes = [0u8; 32];
bytes[..8].copy_from_slice(&n.to_le_bytes());
Hash256::from_bytes(bytes)
}
/// Creates a test outpoint.
fn make_outpoint(tx_n: u64, index: u32) -> Outpoint {
Outpoint::new(make_hash(tx_n), index)
}
/// Creates a P2PKH output.
fn make_p2pkh_output(amount: u64) -> TxOutput {
TxOutput::new(Amount::from_sompi(amount), ScriptPubKey::p2pkh(&[0u8; 32]))
}
/// Creates a UTXO entry.
fn make_utxo_entry(amount: u64, daa_score: u64, is_coinbase: bool) -> UtxoEntry {
UtxoEntry::new(make_p2pkh_output(amount), daa_score, is_coinbase)
}
/// Creates a valid regular transaction with given input/output counts.
fn make_transaction(input_count: usize, output_count: usize) -> Transaction {
let inputs: Vec<TxInput> = (0..input_count)
.map(|i| TxInput::new(make_outpoint(i as u64, 0), vec![0u8; 100]))
.collect();
let amount_per_output = 1_000_000_000u64 / output_count as u64;
let outputs: Vec<TxOutput> = (0..output_count)
.map(|_| make_p2pkh_output(amount_per_output))
.collect();
Transaction {
version: 1,
inputs,
outputs,
lock_time: 0,
subnetwork_id: SubnetworkId::default(),
gas: 0,
payload: vec![],
}
}
/// Creates a coinbase transaction.
fn make_coinbase(reward: u64) -> Transaction {
Transaction::coinbase(vec![make_p2pkh_output(reward)], b"benchmark".to_vec())
}
/// Creates a populated UTXO set with n entries.
fn make_utxo_set(n: usize) -> UtxoSet {
let set = UtxoSet::new();
for i in 0..n {
let outpoint = make_outpoint(i as u64, 0);
let entry = make_utxo_entry(1_000_000_000, 100, false);
set.add(outpoint, entry).unwrap();
}
set
}
/// Creates a test block header.
fn make_block_header(parent: Hash256) -> BlockHeader {
BlockHeader {
version: 1,
parents: vec![parent],
merkle_root: Hash256::ZERO,
accepted_id_merkle_root: Hash256::ZERO,
utxo_commitment: Hash256::ZERO,
timestamp: Timestamp::now(),
bits: 0x1d00ffff,
nonce: 0,
daa_score: 1000,
blue_score: 1000.into(),
blue_work: Hash256::ZERO,
pruning_point: Hash256::ZERO,
}
}
/// Creates a test block with transactions.
fn make_block(tx_count: usize) -> Block {
let mut transactions = vec![make_coinbase(50_000_000_000)];
for _ in 1..tx_count {
transactions.push(make_transaction(1, 2));
}
let body = BlockBody { transactions };
let merkle_root = body.merkle_root();
let mut header = make_block_header(Hash256::ZERO);
header.merkle_root = merkle_root;
Block { header, body }
}
// ==================== Transaction Validation Benchmarks ====================
fn tx_structure_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("tx_structure_validation");
let validator = TransactionValidator::new();
for (inputs, outputs) in [(1, 1), (2, 2), (5, 5), (10, 10)] {
let tx = make_transaction(inputs, outputs);
let id = format!("{}in_{}out", inputs, outputs);
group.throughput(Throughput::Elements(1));
group.bench_with_input(BenchmarkId::new("validate", &id), &tx, |b, tx| {
b.iter(|| black_box(validator.validate_structure(tx)))
});
}
group.finish();
}
fn tx_coinbase_validation(c: &mut Criterion) {
let validator = TransactionValidator::new();
let coinbase = make_coinbase(50_000_000_000);
c.bench_function("tx_coinbase_validation", |b| {
b.iter(|| black_box(validator.validate_structure(&coinbase)))
});
}
fn tx_utxo_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("tx_utxo_validation");
let validator = TransactionValidator::new();
for input_count in [1, 2, 5, 10] {
// Create UTXO set with the UTXOs we'll spend
let utxo_set = UtxoSet::new();
for i in 0..input_count {
let outpoint = make_outpoint(i as u64, 0);
let entry = make_utxo_entry(1_000_000_000, 100, false);
utxo_set.add(outpoint, entry).unwrap();
}
let tx = make_transaction(input_count, 2);
group.throughput(Throughput::Elements(1));
group.bench_with_input(
BenchmarkId::from_parameter(input_count),
&(tx, utxo_set),
|b, (tx, utxo_set)| {
b.iter(|| black_box(validator.validate_against_utxos(tx, utxo_set, 1000)))
},
);
}
group.finish();
}
// ==================== Block Validation Benchmarks ====================
fn block_header_validation(c: &mut Criterion) {
let validator = BlockValidator::new();
let header = make_block_header(Hash256::ZERO);
c.bench_function("block_header_validation", |b| {
b.iter(|| black_box(validator.validate_header(&header)))
});
}
fn block_pow_validation(c: &mut Criterion) {
let validator = BlockValidator::new();
let header = make_block_header(Hash256::ZERO);
// Target that the block will pass
let target = Hash256::from_bytes([0xff; 32]);
c.bench_function("block_pow_validation", |b| {
b.iter(|| black_box(validator.validate_pow(&header, target)))
});
}
fn block_full_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("block_full_validation");
let validator = BlockValidator::new();
for tx_count in [1, 5, 10, 50] {
let block = make_block(tx_count);
// Create UTXO set for the transactions
let utxo_set = UtxoSet::new();
for tx in block.body.transactions.iter().skip(1) {
for input in &tx.inputs {
let entry = make_utxo_entry(1_000_000_000, 100, false);
let _ = utxo_set.add(input.previous_output, entry);
}
}
let expected_reward = Amount::from_sompi(50_000_000_000);
group.throughput(Throughput::Elements(tx_count as u64));
group.bench_with_input(
BenchmarkId::from_parameter(tx_count),
&(block, utxo_set, expected_reward),
|b, (block, utxo_set, reward)| {
b.iter(|| black_box(validator.validate_block(block, utxo_set, *reward)))
},
);
}
group.finish();
}
// ==================== UTXO Set Benchmarks ====================
fn utxo_lookup(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_lookup");
for set_size in [100, 1000, 10000, 100000] {
let utxo_set = make_utxo_set(set_size);
let target = make_outpoint((set_size / 2) as u64, 0);
group.bench_with_input(
BenchmarkId::from_parameter(set_size),
&(utxo_set, target),
|b, (set, target)| b.iter(|| black_box(set.get(target))),
);
}
group.finish();
}
fn utxo_contains(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_contains");
for set_size in [100, 1000, 10000, 100000] {
let utxo_set = make_utxo_set(set_size);
let target = make_outpoint((set_size / 2) as u64, 0);
group.bench_with_input(
BenchmarkId::from_parameter(set_size),
&(utxo_set, target),
|b, (set, target)| b.iter(|| black_box(set.contains(target))),
);
}
group.finish();
}
fn utxo_add(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_add");
for set_size in [100, 1000, 10000] {
group.bench_with_input(BenchmarkId::from_parameter(set_size), &set_size, |b, &n| {
b.iter_batched(
|| make_utxo_set(n),
|set| {
let outpoint = make_outpoint(999999, 0);
let entry = make_utxo_entry(1_000_000_000, 100, false);
black_box(set.add(outpoint, entry))
},
criterion::BatchSize::SmallInput,
)
});
}
group.finish();
}
fn utxo_remove(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_remove");
for set_size in [100, 1000, 10000] {
group.bench_with_input(BenchmarkId::from_parameter(set_size), &set_size, |b, &n| {
b.iter_batched(
|| {
let set = make_utxo_set(n);
let target = make_outpoint((n / 2) as u64, 0);
(set, target)
},
|(set, target)| black_box(set.remove(&target)),
criterion::BatchSize::SmallInput,
)
});
}
group.finish();
}
// ==================== UTXO Diff Benchmarks ====================
fn utxo_diff_apply(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_diff_apply");
for diff_size in [10, 50, 100, 500] {
group.bench_with_input(
BenchmarkId::from_parameter(diff_size),
&diff_size,
|b, &n| {
b.iter_batched(
|| {
// Create UTXO set with entries to remove
let set = make_utxo_set(n);
// Create diff that removes half and adds half
let mut diff = UtxoDiff::new();
// Remove first half
for i in 0..(n / 2) {
diff.remove(make_outpoint(i as u64, 0));
}
// Add new entries
for i in n..(n + n / 2) {
diff.add(
make_outpoint(i as u64, 0),
make_utxo_entry(500_000_000, 100, false),
);
}
(set, diff)
},
|(set, diff)| black_box(set.apply_diff(&diff)),
criterion::BatchSize::SmallInput,
)
},
);
}
group.finish();
}
fn utxo_diff_merge(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_diff_merge");
for diff_size in [10, 50, 100] {
group.bench_with_input(
BenchmarkId::from_parameter(diff_size),
&diff_size,
|b, &n| {
b.iter_batched(
|| {
let mut diff1 = UtxoDiff::new();
let mut diff2 = UtxoDiff::new();
for i in 0..n {
diff1.add(
make_outpoint(i as u64, 0),
make_utxo_entry(100_000_000, 100, false),
);
}
for i in 0..(n / 2) {
diff2.remove(make_outpoint(i as u64, 0));
}
for i in n..(n * 2) {
diff2.add(
make_outpoint(i as u64, 0),
make_utxo_entry(200_000_000, 100, false),
);
}
(diff1, diff2)
},
|(mut diff1, diff2)| {
diff1.merge(diff2);
black_box(diff1)
},
criterion::BatchSize::SmallInput,
)
},
);
}
group.finish();
}
fn utxo_create_tx_diff(c: &mut Criterion) {
let mut group = c.benchmark_group("utxo_create_tx_diff");
for input_count in [1, 2, 5, 10] {
// Create UTXO set with UTXOs to spend
let utxo_set = UtxoSet::new();
for i in 0..input_count {
let outpoint = make_outpoint(i as u64, 0);
let entry = make_utxo_entry(1_000_000_000, 100, false);
utxo_set.add(outpoint, entry).unwrap();
}
let tx = make_transaction(input_count, 2);
group.throughput(Throughput::Elements(1));
group.bench_with_input(
BenchmarkId::from_parameter(input_count),
&(utxo_set, tx),
|b, (set, tx)| b.iter(|| black_box(set.create_transaction_diff(tx, 1000))),
);
}
group.finish();
}
// ==================== UTXO Selection Benchmarks ====================
fn utxo_get_balance(c: &mut Criterion) {
use synor_types::{Address, Network};
// Create a test address from a dummy Ed25519 public key
let dummy_pubkey = [0u8; 32];
let address = Address::from_ed25519_pubkey(Network::Mainnet, &dummy_pubkey);
c.bench_function("utxo_get_balance_1000", |b| {
// For this benchmark, we use an empty set since address matching
// requires proper script parsing
let utxo_set = make_utxo_set(1000);
b.iter(|| black_box(utxo_set.get_balance(&address, Network::Mainnet)))
});
}
// ==================== Batch Validation Benchmarks ====================
fn batch_tx_validation(c: &mut Criterion) {
let mut group = c.benchmark_group("batch_tx_validation");
let validator = TransactionValidator::new();
for batch_size in [10, 50, 100] {
let transactions: Vec<_> = (0..batch_size).map(|_| make_transaction(2, 2)).collect();
group.throughput(Throughput::Elements(batch_size as u64));
group.bench_with_input(
BenchmarkId::from_parameter(batch_size),
&transactions,
|b, txs| {
b.iter(|| {
for tx in txs {
black_box(validator.validate_structure(tx)).ok();
}
})
},
);
}
group.finish();
}
// ==================== Criterion Groups ====================
criterion_group!(
tx_validation_benches,
tx_structure_validation,
tx_coinbase_validation,
tx_utxo_validation,
);
criterion_group!(
block_validation_benches,
block_header_validation,
block_pow_validation,
block_full_validation,
);
criterion_group!(
utxo_set_benches,
utxo_lookup,
utxo_contains,
utxo_add,
utxo_remove,
);
criterion_group!(
utxo_diff_benches,
utxo_diff_apply,
utxo_diff_merge,
utxo_create_tx_diff,
);
criterion_group!(misc_benches, utxo_get_balance, batch_tx_validation,);
criterion_main!(
tx_validation_benches,
block_validation_benches,
utxo_set_benches,
utxo_diff_benches,
misc_benches,
);