diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index 84d5753eb..f0c9ffda5 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -5,8 +5,9 @@ use std::{ }; use crate::{ - audit_transaction::AuditTransaction, utils::U32HasherState, GbtResult, ThreadTransactionsMap, - STARTING_CAPACITY, + audit_transaction::AuditTransaction, + u32_hashmap::{u32hashmap_with_capacity, U32HasherState}, + GbtResult, ThreadTransactionsMap, STARTING_CAPACITY, }; const BLOCK_WEIGHT_UNITS: u32 = 4_000_000; @@ -47,8 +48,7 @@ impl Ord for TxPriority { * Ported from https://github.com/mempool/mempool/blob/master/backend/src/api/tx-selection-worker.ts */ pub fn gbt(mempool: &mut ThreadTransactionsMap) -> Option { - let mut audit_pool: AuditPool = - HashMap::with_capacity_and_hasher(STARTING_CAPACITY, U32HasherState); + let mut audit_pool: AuditPool = u32hashmap_with_capacity(STARTING_CAPACITY); let mut mempool_array: VecDeque = VecDeque::with_capacity(STARTING_CAPACITY); let mut clusters: Vec> = Vec::new(); diff --git a/backend/rust-gbt/src/lib.rs b/backend/rust-gbt/src/lib.rs index 81236e443..b6ed322e0 100644 --- a/backend/rust-gbt/src/lib.rs +++ b/backend/rust-gbt/src/lib.rs @@ -3,11 +3,12 @@ use napi_derive::napi; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use utils::U32HasherState; +use u32_hashmap::{u32hashmap_with_capacity, U32HasherState}; mod audit_transaction; mod gbt; mod thread_transaction; +mod u32_hashmap; mod utils; use thread_transaction::ThreadTransaction; @@ -32,10 +33,7 @@ impl GbtGenerator { #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - thread_transactions: Arc::new(Mutex::new(HashMap::with_capacity_and_hasher( - STARTING_CAPACITY, - U32HasherState, - ))), + thread_transactions: Arc::new(Mutex::new(u32hashmap_with_capacity(STARTING_CAPACITY))), } } diff --git a/backend/rust-gbt/src/u32_hashmap.rs b/backend/rust-gbt/src/u32_hashmap.rs new file mode 100644 index 000000000..26ab38718 --- /dev/null +++ b/backend/rust-gbt/src/u32_hashmap.rs @@ -0,0 +1,72 @@ +use std::{ + collections::HashMap, + hash::{BuildHasher, Hasher}, +}; + +// Note: If needed, this will create a new HashMap without initial capacity. +// /// This is the only way to create a HashMap with the U32HasherState +// pub fn u32hashmap_new() -> HashMap { +// HashMap::with_hasher(U32HasherState(())) +// } + +/// This is the only way to create a HashMap with the U32HasherState and capacity +pub fn u32hashmap_with_capacity(capacity: usize) -> HashMap { + HashMap::with_capacity_and_hasher(capacity, U32HasherState(())) +} + +/// A private unit type is contained so no one can make an instance of it. +pub struct U32HasherState(()); + +impl BuildHasher for U32HasherState { + type Hasher = U32Hasher; + + fn build_hasher(&self) -> Self::Hasher { + U32Hasher(0) + } +} + +/// This also can't be created outside this module due to private field. +pub struct U32Hasher(u32); + +impl Hasher for U32Hasher { + fn finish(&self) -> u64 { + // Safety: Two u32s next to each other will make a u64 + unsafe { core::mem::transmute::<(u32, u32), u64>((self.0, 0_u32)) } + } + + fn write(&mut self, bytes: &[u8]) { + // Assert in debug builds (testing too) that only 4 byte keys (u32, i32, f32, etc.) run + debug_assert!(bytes.len() == 4); + // Safety: We know that the size of the key is at least 4 bytes + // We also know that the only way to get an instance of HashMap using this "hasher" + // is through the public functions in this module which set the key to u32. + self.0 = unsafe { *bytes.as_ptr().cast::() }; + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::U32HasherState; + + #[test] + fn test_me() { + let mut hm: HashMap = HashMap::with_hasher(U32HasherState(())); + + hm.insert(0, String::from("0")); + hm.insert(42, String::from("42")); + hm.insert(256, String::from("256")); + hm.insert(u32::MAX, String::from("MAX")); + hm.insert(u32::MAX >> 2, String::from("MAX >> 2")); + + assert_eq!(hm.get(&0), Some(&String::from("0"))); + assert_eq!(hm.get(&42), Some(&String::from("42"))); + assert_eq!(hm.get(&256), Some(&String::from("256"))); + assert_eq!(hm.get(&u32::MAX), Some(&String::from("MAX"))); + assert_eq!(hm.get(&(u32::MAX >> 2)), Some(&String::from("MAX >> 2"))); + assert_eq!(hm.get(&(u32::MAX >> 4)), None); + assert_eq!(hm.get(&3), None); + assert_eq!(hm.get(&43), None); + } +} diff --git a/backend/rust-gbt/src/utils.rs b/backend/rust-gbt/src/utils.rs index b969c8361..c1b6063a1 100644 --- a/backend/rust-gbt/src/utils.rs +++ b/backend/rust-gbt/src/utils.rs @@ -1,8 +1,5 @@ use bytes::buf::Buf; -use std::{ - hash::{BuildHasher, Hasher}, - io::Cursor, -}; +use std::io::Cursor; pub fn txids_from_buffer(buffer: &[u8]) -> Vec { let mut txids: Vec = Vec::new(); @@ -14,56 +11,3 @@ pub fn txids_from_buffer(buffer: &[u8]) -> Vec { txids } - -pub struct U32HasherState; - -impl BuildHasher for U32HasherState { - type Hasher = U32Hasher; - - fn build_hasher(&self) -> Self::Hasher { - U32Hasher(0) - } -} - -pub struct U32Hasher(u32); - -impl Hasher for U32Hasher { - fn finish(&self) -> u64 { - // Safety: Two u32s next to each other will make a u64 - unsafe { core::mem::transmute::<(u32, u32), u64>((self.0, 0_u32)) } - } - - fn write(&mut self, bytes: &[u8]) { - // Assert in debug builds (testing too) that only 4 byte keys (u32, i32, f32, etc.) run - debug_assert!(bytes.len() == 4); - // Safety: We know that the size of the key is at least 4 bytes - self.0 = unsafe { *bytes.as_ptr().cast::() }; - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use super::U32HasherState; - - #[test] - fn test_me() { - let mut hm: HashMap = HashMap::with_hasher(U32HasherState); - - hm.insert(0, String::from("0")); - hm.insert(42, String::from("42")); - hm.insert(256, String::from("256")); - hm.insert(u32::MAX, String::from("MAX")); - hm.insert(u32::MAX >> 2, String::from("MAX >> 2")); - - assert_eq!(hm.get(&0), Some(&String::from("0"))); - assert_eq!(hm.get(&42), Some(&String::from("42"))); - assert_eq!(hm.get(&256), Some(&String::from("256"))); - assert_eq!(hm.get(&u32::MAX), Some(&String::from("MAX"))); - assert_eq!(hm.get(&(u32::MAX >> 2)), Some(&String::from("MAX >> 2"))); - assert_eq!(hm.get(&(u32::MAX >> 4)), None); - assert_eq!(hm.get(&3), None); - assert_eq!(hm.get(&43), None); - } -}