Skip to main content

moloch_core/
arena.rs

1//! Arena allocator for efficient batch operations.
2//!
3//! Arena allocation (bump allocation) is extremely efficient for batch operations:
4//! - Allocation is O(1) - just a pointer bump
5//! - No per-object deallocation overhead
6//! - All memory freed at once when arena is dropped
7//! - Cache-friendly linear memory layout
8//!
9//! # Use Cases
10//!
11//! - Batch event serialization
12//! - Batch signature verification (collecting canonical bytes)
13//! - Merkle tree construction
14//! - Temporary buffers during block building
15//!
16//! # Example
17//!
18//! ```rust
19//! use moloch_core::arena::BatchArena;
20//!
21//! // Create arena with initial capacity
22//! let arena = BatchArena::new();
23//!
24//! // Allocate data in the arena
25//! let bytes1 = arena.alloc_bytes(b"hello");
26//! let bytes2 = arena.alloc_bytes(b"world");
27//!
28//! // Create a vector in the arena
29//! let mut vec = arena.alloc_vec::<u32>();
30//! vec.push(1);
31//! vec.push(2);
32//! vec.push(3);
33//!
34//! // Arena is dropped and all memory freed at once
35//! ```
36
37use bumpalo::collections::Vec as BumpVec;
38use bumpalo::Bump;
39
40use crate::crypto::Hash;
41
42/// Default arena capacity (1MB).
43pub const DEFAULT_ARENA_CAPACITY: usize = 1024 * 1024;
44
45/// Arena allocator optimized for batch cryptographic operations.
46///
47/// Provides fast bump allocation for temporary data structures
48/// used during batch verification, serialization, and merkle tree building.
49pub struct BatchArena {
50    bump: Bump,
51}
52
53impl BatchArena {
54    /// Create a new arena with default capacity (1MB).
55    #[inline]
56    pub fn new() -> Self {
57        Self {
58            bump: Bump::with_capacity(DEFAULT_ARENA_CAPACITY),
59        }
60    }
61
62    /// Create a new arena with specified capacity.
63    #[inline]
64    pub fn with_capacity(capacity: usize) -> Self {
65        Self {
66            bump: Bump::with_capacity(capacity),
67        }
68    }
69
70    /// Create arena sized for a batch of events.
71    ///
72    /// Estimates ~1KB per event for canonical bytes + metadata.
73    #[inline]
74    pub fn for_events(count: usize) -> Self {
75        let capacity = count * 1024;
76        Self::with_capacity(capacity.max(DEFAULT_ARENA_CAPACITY))
77    }
78
79    /// Create arena sized for batch hash operations.
80    ///
81    /// Estimates 32 bytes per hash + overhead.
82    #[inline]
83    pub fn for_hashes(count: usize) -> Self {
84        let capacity = count * 64;
85        Self::with_capacity(capacity.max(65536))
86    }
87
88    /// Allocate a slice of bytes in the arena.
89    #[inline]
90    pub fn alloc_bytes(&self, bytes: &[u8]) -> &[u8] {
91        self.bump.alloc_slice_copy(bytes)
92    }
93
94    /// Allocate a copy of a string in the arena.
95    #[inline]
96    pub fn alloc_str(&self, s: &str) -> &str {
97        self.bump.alloc_str(s)
98    }
99
100    /// Allocate a value in the arena.
101    #[inline]
102    pub fn alloc<T>(&self, value: T) -> &mut T {
103        self.bump.alloc(value)
104    }
105
106    /// Allocate a slice in the arena.
107    #[inline]
108    pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &[T] {
109        self.bump.alloc_slice_copy(slice)
110    }
111
112    /// Create a new vector in the arena.
113    #[inline]
114    pub fn alloc_vec<T>(&self) -> BumpVec<'_, T> {
115        BumpVec::new_in(&self.bump)
116    }
117
118    /// Create a vector with capacity in the arena.
119    #[inline]
120    pub fn alloc_vec_with_capacity<T>(&self, capacity: usize) -> BumpVec<'_, T> {
121        BumpVec::with_capacity_in(capacity, &self.bump)
122    }
123
124    /// Allocate space for N items and return a slice.
125    #[inline]
126    pub fn alloc_slice_fill_default<T: Default + Clone>(&self, count: usize) -> &mut [T] {
127        self.bump.alloc_slice_fill_default(count)
128    }
129
130    /// Allocate a hash array in the arena.
131    #[inline]
132    pub fn alloc_hash_array(&self, count: usize) -> &mut [Hash] {
133        self.bump.alloc_slice_fill_default(count)
134    }
135
136    /// Get the number of bytes allocated.
137    #[inline]
138    pub fn allocated_bytes(&self) -> usize {
139        self.bump.allocated_bytes()
140    }
141
142    /// Reset the arena, freeing all allocations.
143    ///
144    /// This allows reusing the arena for another batch without
145    /// deallocating the underlying memory.
146    #[inline]
147    pub fn reset(&mut self) {
148        self.bump.reset();
149    }
150
151    /// Get a reference to the underlying bump allocator.
152    ///
153    /// Useful for advanced use cases or when integrating with
154    /// other bumpalo-aware APIs.
155    #[inline]
156    pub fn as_bump(&self) -> &Bump {
157        &self.bump
158    }
159}
160
161impl Default for BatchArena {
162    fn default() -> Self {
163        Self::new()
164    }
165}
166
167impl std::fmt::Debug for BatchArena {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        f.debug_struct("BatchArena")
170            .field("allocated_bytes", &self.allocated_bytes())
171            .finish()
172    }
173}
174
175/// Arena-backed batch of bytes for signature verification.
176///
177/// Efficiently collects canonical bytes from multiple events
178/// for batch signature verification.
179pub struct CanonicalBytesArena<'a> {
180    arena: &'a BatchArena,
181    items: BumpVec<'a, &'a [u8]>,
182}
183
184impl<'a> CanonicalBytesArena<'a> {
185    /// Create a new canonical bytes collector.
186    pub fn new(arena: &'a BatchArena, capacity: usize) -> Self {
187        Self {
188            arena,
189            items: arena.alloc_vec_with_capacity(capacity),
190        }
191    }
192
193    /// Add canonical bytes to the collection.
194    pub fn push(&mut self, bytes: &[u8]) {
195        let allocated = self.arena.alloc_bytes(bytes);
196        self.items.push(allocated);
197    }
198
199    /// Get all collected bytes slices.
200    pub fn as_slices(&self) -> &[&'a [u8]] {
201        &self.items
202    }
203
204    /// Get the number of items collected.
205    pub fn len(&self) -> usize {
206        self.items.len()
207    }
208
209    /// Check if empty.
210    pub fn is_empty(&self) -> bool {
211        self.items.is_empty()
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::hash;
219
220    #[test]
221    fn test_arena_basic_allocation() {
222        let arena = BatchArena::new();
223
224        let bytes1 = arena.alloc_bytes(b"hello");
225        let bytes2 = arena.alloc_bytes(b"world");
226
227        assert_eq!(bytes1, b"hello");
228        assert_eq!(bytes2, b"world");
229    }
230
231    #[test]
232    fn test_arena_vector() {
233        let arena = BatchArena::new();
234
235        let mut vec = arena.alloc_vec::<u32>();
236        vec.push(1);
237        vec.push(2);
238        vec.push(3);
239
240        assert_eq!(vec.as_slice(), &[1, 2, 3]);
241    }
242
243    #[test]
244    fn test_arena_hash_array() {
245        let arena = BatchArena::new();
246
247        let hashes = arena.alloc_hash_array(4);
248        assert_eq!(hashes.len(), 4);
249
250        // All should be zero initially
251        for h in hashes.iter() {
252            assert!(h.is_zero());
253        }
254
255        // Modify them
256        hashes[0] = hash(b"test");
257        assert!(!hashes[0].is_zero());
258    }
259
260    #[test]
261    fn test_arena_reset() {
262        let mut arena = BatchArena::new();
263
264        // Allocate some data
265        let first = arena.alloc_bytes(&[0u8; 10000]);
266        let first_ptr = first.as_ptr();
267        let before = arena.allocated_bytes();
268        assert!(before >= 10000);
269
270        // Reset the arena (memory is retained for reuse, pointer reset)
271        arena.reset();
272
273        // After reset, we can reuse the same memory
274        // Allocate again - should start from the beginning of the chunk
275        let second = arena.alloc_bytes(&[1u8; 10000]);
276        let second_ptr = second.as_ptr();
277
278        // The pointers may be the same (memory reused) or different
279        // depending on implementation, but allocation should work
280        assert_eq!(second.len(), 10000);
281
282        // Key test: after reset, we can allocate more data without
283        // growing the arena (memory was already allocated)
284        let after = arena.allocated_bytes();
285        // The arena should not have grown significantly
286        // (it might grow slightly due to alignment, but not double)
287        assert!(after <= before * 2, "Arena should reuse memory after reset");
288
289        // Verify pointers are distinct (first is now invalid, second is valid)
290        // This is just a sanity check - the exact behavior depends on bumpalo
291        let _ = (first_ptr, second_ptr);
292    }
293
294    #[test]
295    fn test_canonical_bytes_arena() {
296        let arena = BatchArena::new();
297        let mut collector = CanonicalBytesArena::new(&arena, 10);
298
299        collector.push(b"event 1 canonical bytes");
300        collector.push(b"event 2 canonical bytes");
301        collector.push(b"event 3 canonical bytes");
302
303        assert_eq!(collector.len(), 3);
304
305        let slices = collector.as_slices();
306        assert_eq!(slices[0], b"event 1 canonical bytes");
307        assert_eq!(slices[1], b"event 2 canonical bytes");
308        assert_eq!(slices[2], b"event 3 canonical bytes");
309    }
310
311    #[test]
312    fn test_arena_sizing() {
313        let arena = BatchArena::for_events(100);
314        // Should be at least 100KB
315        assert!(arena.as_bump().chunk_capacity() >= 100 * 1024);
316
317        let arena = BatchArena::for_hashes(1000);
318        // Should be at least 64KB (1000 * 64 bytes)
319        assert!(arena.as_bump().chunk_capacity() >= 64 * 1024);
320    }
321
322    #[test]
323    fn test_arena_many_small_allocations() {
324        let arena = BatchArena::new();
325
326        // Allocate 1000 small items
327        for i in 0..1000u32 {
328            let _ = arena.alloc(i);
329        }
330
331        // Should have allocated about 4KB (1000 * 4 bytes) + overhead
332        assert!(arena.allocated_bytes() >= 4000);
333    }
334
335    #[test]
336    fn test_arena_slice_allocation() {
337        let arena = BatchArena::new();
338
339        let hashes: Vec<Hash> = (0..10u32).map(|i| hash(&i.to_le_bytes())).collect();
340
341        let allocated = arena.alloc_slice(&hashes);
342
343        assert_eq!(allocated.len(), 10);
344        for (i, h) in allocated.iter().enumerate() {
345            assert_eq!(*h, hash(&(i as u32).to_le_bytes()));
346        }
347    }
348}