A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
501 lines
15 KiB
Rust
501 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,
|
|
);
|