#![cfg(feature = "std")]
use std::{
alloc::{
GlobalAlloc,
Layout,
System,
},
sync::{
Mutex,
atomic::{
AtomicU64,
Ordering::Relaxed,
},
},
};
use chainfold::{
Batch,
BlockRef,
Engine,
EngineConfig,
test_util::NoopFold,
};
static ALLOCATIONS: AtomicU64 = AtomicU64::new(0);
static ALLOCATED_BYTES: AtomicU64 = AtomicU64::new(0);
static MEASURING: Mutex<()> = Mutex::new(());
struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATIONS.fetch_add(1, Relaxed);
ALLOCATED_BYTES.fetch_add(layout.size() as u64, Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[global_allocator]
static GLOBAL: Counting = Counting;
fn block_ref(number: u64) -> BlockRef {
let mut hash = [0u8; 32];
hash[..8].copy_from_slice(&number.to_le_bytes());
BlockRef { number, hash }
}
fn block_batch(boundary: Option<BlockRef>, number: u64, count: u32) -> Batch<u64> {
let mut batch = Batch::new();
batch.boundary = boundary;
batch.push_block(
block_ref(number),
(0..count).map(|log_index| (log_index, u64::from(log_index))),
);
batch
}
#[test]
fn steady_state_apply_allocates_zero() {
let _measuring = MEASURING
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let mut engine = Engine::new(
NoopFold,
EngineConfig {
ring_capacity: 64,
checkpoint_slots: 4,
},
)
.unwrap();
engine.apply_batch(&block_batch(None, 1, 1)).unwrap();
let batch = block_batch(Some(block_ref(1)), 2, 1024);
let before = ALLOCATIONS.load(Relaxed);
engine.apply_batch(&batch).unwrap();
let after = ALLOCATIONS.load(Relaxed);
assert_eq!(after, before);
}
#[cfg(feature = "wincode")]
fn failed_decode_bytes(bytes: &[u8]) -> u64 {
use chainfold::test_util::RecordingFold;
let config = EngineConfig {
ring_capacity: 8,
checkpoint_slots: 0,
};
let before = ALLOCATED_BYTES.load(Relaxed);
let result = Engine::<RecordingFold>::decode_snapshot(bytes, config);
let after = ALLOCATED_BYTES.load(Relaxed);
assert!(result.is_err(), "corrupt input decoded successfully");
after - before
}
#[cfg(feature = "wincode")]
#[test]
fn corrupt_snapshot_decode_allocates_within_the_input_length() {
use chainfold::test_util::RecordingFold;
let _measuring = MEASURING
.lock()
.unwrap_or_else(|poison| poison.into_inner());
let mut engine = Engine::new(
RecordingFold::default(),
EngineConfig {
ring_capacity: 8,
checkpoint_slots: 0,
},
)
.unwrap();
engine.apply_batch(&block_batch(None, 1, 4)).unwrap();
let mut encoded = Vec::new();
engine.encode_snapshot(&mut encoded).unwrap();
let mut flipped = encoded.clone();
flipped[10] ^= 0x01;
let mut worst = 0u64;
for len in 0..encoded.len() {
worst = worst.max(failed_decode_bytes(&encoded[..len]));
}
let flipped_bytes = failed_decode_bytes(&flipped);
assert!(
worst <= encoded.len() as u64,
"truncated decode allocated {worst} bytes"
);
assert!(
flipped_bytes <= flipped.len() as u64,
"bit-flipped decode allocated {flipped_bytes} bytes"
);
}