#![cfg(feature = "dhat-profiling")]
#[cfg(feature = "transaction")]
#[path = "support/beef_chain.rs"]
mod beef_chain;
use bsv_rs::primitives::bsv::shamir::split_private_key;
use bsv_rs::primitives::ec::PrivateKey;
use bsv_rs::primitives::hash;
use bsv_rs::primitives::symmetric::SymmetricKey;
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
static PROFILER: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serial() -> std::sync::MutexGuard<'static, ()> {
PROFILER.lock().unwrap_or_else(|e| e.into_inner())
}
#[allow(dead_code)]
fn profile_operation<F>(name: &str, iterations: usize, mut op: F)
where
F: FnMut(),
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..iterations {
op();
}
let stats = dhat::HeapStats::get();
println!("\n=== {} ({} iterations) ===", name, iterations);
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(
" Avg bytes per iteration: {}",
stats.total_bytes / iterations as u64
);
}
#[test]
fn test_encryption_allocations() {
let _serial = serial();
let key = SymmetricKey::random();
let sizes: &[usize] = &[64, 256, 1024, 4096, 16384];
for &size in sizes {
let plaintext = vec![0u8; size];
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let ciphertext = key.encrypt(&plaintext).unwrap();
let _ = key.decrypt(&ciphertext).unwrap();
}
let stats = dhat::HeapStats::get();
println!(
"\n=== AES-GCM Encrypt/Decrypt {} bytes (100 cycles) ===",
size
);
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per cycle: {}", stats.total_bytes / 100);
}
}
#[test]
fn test_key_derivation_allocations() {
let _serial = serial();
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let bob_pub = bob.public_key();
{
let _profiler = dhat::Profiler::new_heap();
for i in 0..100 {
let invoice_id = format!("invoice-{}", i);
let _ = alice.derive_child(&bob_pub, &invoice_id);
}
let stats = dhat::HeapStats::get();
println!("\n=== BRC-42 Key Derivation (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per derivation: {}", stats.total_bytes / 100);
}
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let _ = alice.derive_shared_secret(&bob_pub);
}
let stats = dhat::HeapStats::get();
println!("\n=== ECDH Shared Secret (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per derivation: {}", stats.total_bytes / 100);
}
}
#[test]
fn test_shamir_allocations() {
let _serial = serial();
let key = PrivateKey::random();
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..50 {
let _ = split_private_key(&key, 3, 5);
}
let stats = dhat::HeapStats::get();
println!("\n=== Shamir Split 3-of-5 (50 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per split: {}", stats.total_bytes / 50);
}
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..50 {
let _ = split_private_key(&key, 5, 10);
}
let stats = dhat::HeapStats::get();
println!("\n=== Shamir Split 5-of-10 (50 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per split: {}", stats.total_bytes / 50);
}
{
let shares = split_private_key(&key, 3, 5).unwrap();
let _profiler = dhat::Profiler::new_heap();
for _ in 0..50 {
let subset = bsv_rs::primitives::bsv::shamir::KeyShares::new(
shares.points[0..3].to_vec(),
3,
shares.integrity.clone(),
);
let _ = subset.recover_private_key();
}
let stats = dhat::HeapStats::get();
println!("\n=== Shamir Recover 3-of-5 (50 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per recovery: {}", stats.total_bytes / 50);
}
}
#[test]
fn test_signing_allocations() {
let _serial = serial();
let key = PrivateKey::random();
let pubkey = key.public_key();
let msg_hash = hash::sha256(b"benchmark message for signing");
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let _ = key.sign(&msg_hash);
}
let stats = dhat::HeapStats::get();
println!("\n=== ECDSA Sign (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per sign: {}", stats.total_bytes / 100);
}
{
let sig = key.sign(&msg_hash).unwrap();
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let _ = pubkey.verify(&msg_hash, &sig);
}
let stats = dhat::HeapStats::get();
println!("\n=== ECDSA Verify (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per verify: {}", stats.total_bytes / 100);
}
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let sig = key.sign(&msg_hash).unwrap();
let _ = pubkey.verify(&msg_hash, &sig);
}
let stats = dhat::HeapStats::get();
println!("\n=== ECDSA Sign+Verify Cycle (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
println!(" Avg bytes per cycle: {}", stats.total_bytes / 100);
}
}
#[test]
fn test_hashing_allocations() {
let _serial = serial();
let data_1kb = vec![0u8; 1024];
let data_16kb = vec![0u8; 16384];
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..1000 {
let _ = hash::sha256(&data_1kb);
}
let stats = dhat::HeapStats::get();
println!("\n=== SHA-256 1KB (1000 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
}
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..100 {
let _ = hash::sha256(&data_16kb);
}
let stats = dhat::HeapStats::get();
println!("\n=== SHA-256 16KB (100 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
}
{
let _profiler = dhat::Profiler::new_heap();
for _ in 0..1000 {
let _ = hash::hash160(&data_1kb);
}
let stats = dhat::HeapStats::get();
println!("\n=== Hash160 1KB (1000 iterations) ===");
println!(" Total allocations: {}", stats.total_blocks);
println!(" Total bytes allocated: {}", stats.total_bytes);
println!(" Peak heap bytes: {}", stats.max_bytes);
}
}
#[cfg(feature = "transaction")]
#[test]
fn test_beef_stream_memory_is_flat_per_element() {
use beef_chain::{funding_root, subject, ChainSource, HEIGHT};
use bsv_rs::transaction::{verify_stream, verify_stream_structure, BeefStream};
use std::collections::HashMap;
let _serial = serial();
fn peak(f: impl FnOnce()) -> u64 {
let _profiler = dhat::Profiler::builder().testing().build();
f();
dhat::HeapStats::get().max_bytes as u64
}
println!("\n=== BEEF stream, the P0-5 deep chain ===");
let mut rows = Vec::new();
for n in [1_000usize, 10_000, 100_000] {
let tip = subject(n);
let stream_only = peak(|| {
let mut elements = 0usize;
for element in BeefStream::new(ChainSource::new(n)) {
element.expect("the chain reads");
elements += 1;
}
assert_eq!(elements, n + 1);
});
let structure = peak(|| {
let headers = HashMap::from([(HEIGHT, funding_root())]);
let verdict = verify_stream_structure(ChainSource::new(n), &headers, Some(tip));
assert!(verdict.expect("the source does not fail").is_valid());
});
let full = peak(|| {
let headers = HashMap::from([(HEIGHT, funding_root())]);
let verdict = verify_stream(ChainSource::new(n), &headers, Some(tip));
assert!(verdict.expect("the source does not fail").is_valid());
});
let per = |total: u64| (total - stream_only.min(total)) as f64 / n as f64;
println!(
" N={n}: stream alone {stream_only} B; structure {structure} B ({:.1} B per element); \
scripts {full} B ({:.1} B per element)",
per(structure),
per(full)
);
rows.push((n, stream_only, per(structure), per(full)));
}
let (_, first, ..) = rows[0];
for (n, stream_only, ..) in &rows {
assert_eq!(
*stream_only, first,
"the stream's peak moved with the depth at N={n}"
);
assert!(
*stream_only < 32 * 1024,
"the stream holds more than a chunk"
);
}
for (n, _, structure, full) in &rows {
assert!(*structure < 320.0, "N={n}: {structure} B per element");
assert!(*full < 320.0, "N={n}: {full} B per element");
assert!(
(*full - *structure).abs() * (*n as f64) < 16.0 * 1024.0,
"N={n}: the scripts kept {:.0} B more than the structure",
(*full - *structure) * *n as f64
);
}
let (_, _, at_1_000, _) = rows[0];
let (_, _, at_100_000, _) = rows[2];
assert!(
at_100_000 <= at_1_000 * 1.25,
"the bytes per element grew with the depth: {at_1_000} then {at_100_000}"
);
}