use snarkvm_ledger::{
Ledger,
test_helpers::{CurrentConsensusStore, CurrentLedger, CurrentNetwork, LedgerType},
};
use aleo_std::StorageMode;
use snarkvm_console::{account::PrivateKey, prelude::*};
use snarkvm_synthesizer::vm::VM;
#[test]
fn test_block_cache_initialization() {
let rng = &mut TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
const NUM_BLOCKS: u32 = 15;
let temp_ledger = CurrentLedger::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
for _ in 1..=NUM_BLOCKS {
let transactions = vec![];
let block =
temp_ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions, rng).unwrap();
temp_ledger.advance_to_next_block(&block).unwrap();
}
let ledger = Ledger::<CurrentNetwork, LedgerType>::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
let latest_height = ledger.latest_height();
for height in (latest_height.saturating_sub(9))..=latest_height {
let block = ledger.get_block(height).unwrap();
assert_eq!(block.height(), height);
}
for height in 0..=(latest_height.saturating_sub(10)) {
let block = ledger.get_block(height).unwrap();
assert_eq!(block.height(), height);
}
}
#[test]
fn test_block_cache_hit_miss_behavior() {
let rng = &mut TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
let ledger = Ledger::<CurrentNetwork, LedgerType>::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
let cache_size = ledger.block_cache_size().expect("No cache size found");
assert!(cache_size > 0, "Cache size is 0");
let mut block_hashes = vec![genesis.hash()];
for _ in 1..=(cache_size + 5) {
let transactions = vec![];
let block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions, rng).unwrap();
let block_hash = block.hash();
ledger.advance_to_next_block(&block).unwrap();
block_hashes.push(block_hash);
}
let latest_height = ledger.latest_height();
for height in (latest_height.saturating_sub(cache_size - 1))..=latest_height {
let start = std::time::Instant::now();
let block = ledger.get_block(height).unwrap();
let duration = start.elapsed();
assert_eq!(block.height(), height);
assert_eq!(block.hash(), block_hashes[height as usize]);
assert!(duration.as_micros() < 1000, "Block {height} retrieval took too long: {duration:?}");
}
for height in 0..=(latest_height.saturating_sub(cache_size)) {
let block = ledger.get_block(height).unwrap();
assert_eq!(block.height(), height);
assert_eq!(block.hash(), block_hashes[height as usize]);
}
}
#[test]
fn test_block_cache_eviction() {
let rng = &mut TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
let ledger = Ledger::<CurrentNetwork, LedgerType>::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
for i in 1..=10 {
let transactions = vec![];
let block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions, rng).unwrap();
ledger.advance_to_next_block(&block).unwrap();
assert_eq!(ledger.latest_height(), i);
}
let initial_height = ledger.latest_height();
for i in 1..=5 {
let transactions = vec![];
let block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions, rng).unwrap();
ledger.advance_to_next_block(&block).unwrap();
assert_eq!(ledger.latest_height(), initial_height + i);
}
let final_height = ledger.latest_height();
for height in 0..=final_height {
let block = ledger.get_block(height).unwrap();
assert_eq!(block.height(), height);
}
let cache_start = final_height.saturating_sub(9);
for height in cache_start..=final_height {
let start = std::time::Instant::now();
let _block = ledger.get_block(height).unwrap();
let duration = start.elapsed();
assert!(duration.as_micros() < 1000, "Cached block {height} retrieval took too long: {duration:?}");
}
}
#[test]
fn test_block_cache_performance_comparison() {
let rng = &mut TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
let ledger_no_cache = CurrentLedger::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
let ledger_with_cache =
Ledger::<CurrentNetwork, LedgerType>::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
const NUM_BLOCKS: u32 = 20;
for _ in 1..=NUM_BLOCKS {
let transactions = vec![];
let block = ledger_no_cache
.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions.clone(), rng)
.unwrap();
ledger_no_cache.advance_to_next_block(&block).unwrap();
ledger_with_cache.advance_to_next_block(&block).unwrap();
}
let latest_height = ledger_with_cache.latest_height();
let test_heights: Vec<u32> = (latest_height.saturating_sub(9)..=latest_height).collect();
let start_no_cache = std::time::Instant::now();
for &height in &test_heights {
let _block = ledger_no_cache.get_block(height).unwrap();
}
let duration_no_cache = start_no_cache.elapsed();
let start_with_cache = std::time::Instant::now();
for &height in &test_heights {
let _block = ledger_with_cache.get_block(height).unwrap();
}
let duration_with_cache = start_with_cache.elapsed();
println!("No cache: {duration_no_cache:?}, With cache: {duration_with_cache:?}");
for height in 0..=latest_height {
let block_no_cache = ledger_no_cache.get_block(height).unwrap();
let block_with_cache = ledger_with_cache.get_block(height).unwrap();
assert_eq!(block_no_cache.hash(), block_with_cache.hash(), "different block hashes at height {height}");
assert_eq!(block_no_cache.height(), block_with_cache.height());
}
}
#[test]
fn test_block_cache_during_advancement() {
let rng = &mut TestRng::default();
let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let store = CurrentConsensusStore::open(StorageMode::new_test(None)).unwrap();
let genesis = VM::from(store).unwrap().genesis_beacon(&private_key, rng).unwrap();
let ledger = Ledger::<CurrentNetwork, LedgerType>::load(genesis.clone(), StorageMode::new_test(None)).unwrap();
const NUM_BLOCKS: u32 = 25; let mut added_blocks = vec![genesis];
for i in 1..=NUM_BLOCKS {
let transactions = vec![];
let block =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], transactions, rng).unwrap();
let current_latest = ledger.get_block(ledger.latest_height()).unwrap();
assert_eq!(current_latest.height(), i - 1);
ledger.advance_to_next_block(&block).unwrap();
added_blocks.push(block.clone());
let new_latest = ledger.get_block(ledger.latest_height()).unwrap();
assert_eq!(new_latest.height(), i);
assert_eq!(new_latest.hash(), block.hash());
if i >= 10 {
let recent_block = ledger.get_block(i - 5).unwrap();
assert_eq!(recent_block.height(), i - 5);
if i >= 15 {
let older_block = ledger.get_block(i - 15).unwrap();
assert_eq!(older_block.height(), i - 15);
}
}
}
for (expected_height, expected_block) in added_blocks.iter().enumerate() {
let retrieved_block = ledger.get_block(expected_height as u32).unwrap();
assert_eq!(retrieved_block.hash(), expected_block.hash());
assert_eq!(retrieved_block.height(), expected_height as u32);
}
let latest_height = ledger.latest_height();
let recent_heights: Vec<u32> = (latest_height.saturating_sub(9)..=latest_height).collect();
let start = std::time::Instant::now();
let recent_blocks = ledger.get_blocks(recent_heights[0]..recent_heights[recent_heights.len() - 1] + 1).unwrap();
let duration = start.elapsed();
assert_eq!(recent_blocks.len(), recent_heights.len());
for (block, &expected_height) in recent_blocks.iter().zip(&recent_heights) {
assert_eq!(block.height(), expected_height);
}
assert!(duration.as_millis() < 100, "Batch retrieval of recent blocks took too long: {duration:?}");
}