diff --git a/Cargo.lock b/Cargo.lock index 16fdc9c54..d7e7ef21a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,6 @@ dependencies = [ "napi", "napi-build", "napi-derive", - "once_cell", "priority-queue", ] diff --git a/backend/rust-gbt/Cargo.toml b/backend/rust-gbt/Cargo.toml index 2585a09e6..4a8de082f 100644 --- a/backend/rust-gbt/Cargo.toml +++ b/backend/rust-gbt/Cargo.toml @@ -14,7 +14,6 @@ crate-type = ["cdylib"] [dependencies] priority-queue = "1.3.2" bytes = "1.4.0" -once_cell = "1.18.0" napi = { version = "2.13.2", features = ["napi8", "tokio_rt"] } napi-derive = "2.13.0" bytemuck = "1.13.1" diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index f0c9ffda5..5109b044b 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ audit_transaction::AuditTransaction, - u32_hashmap::{u32hashmap_with_capacity, U32HasherState}, + u32_hasher_types::{u32hashmap_with_capacity, u32priority_queue_with_capacity, U32HasherState}, GbtResult, ThreadTransactionsMap, STARTING_CAPACITY, }; @@ -16,6 +16,7 @@ const BLOCK_RESERVED_WEIGHT: u32 = 4_000; const MAX_BLOCKS: usize = 8; type AuditPool = HashMap; +type ModifiedQueue = PriorityQueue; struct TxPriority { uid: u32, @@ -82,7 +83,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap) -> Option { let mut block_weight: u32 = BLOCK_RESERVED_WEIGHT; let mut block_sigops: u32 = 0; let mut transactions: Vec = Vec::with_capacity(STARTING_CAPACITY); - let mut modified: PriorityQueue = PriorityQueue::new(); + let mut modified: ModifiedQueue = u32priority_queue_with_capacity(STARTING_CAPACITY); let mut overflow: Vec = Vec::new(); let mut failures = 0; while !mempool_array.is_empty() || !modified.is_empty() { @@ -274,7 +275,7 @@ fn set_relatives(txid: u32, audit_pool: &mut AuditPool) { fn update_descendants( root_txid: u32, audit_pool: &mut AuditPool, - modified: &mut PriorityQueue, + modified: &mut ModifiedQueue, cluster_rate: f64, ) { let mut visited: HashSet = HashSet::new(); diff --git a/backend/rust-gbt/src/lib.rs b/backend/rust-gbt/src/lib.rs index b6ed322e0..b22862a55 100644 --- a/backend/rust-gbt/src/lib.rs +++ b/backend/rust-gbt/src/lib.rs @@ -3,12 +3,12 @@ use napi_derive::napi; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use u32_hashmap::{u32hashmap_with_capacity, U32HasherState}; +use u32_hasher_types::{u32hashmap_with_capacity, U32HasherState}; mod audit_transaction; mod gbt; mod thread_transaction; -mod u32_hashmap; +mod u32_hasher_types; mod utils; use thread_transaction::ThreadTransaction; diff --git a/backend/rust-gbt/src/u32_hasher_types.rs b/backend/rust-gbt/src/u32_hasher_types.rs new file mode 100644 index 000000000..4f5985ec2 --- /dev/null +++ b/backend/rust-gbt/src/u32_hasher_types.rs @@ -0,0 +1,119 @@ +use priority_queue::PriorityQueue; +use std::{ + collections::HashMap, + hash::{BuildHasher, Hasher}, +}; + +/// 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(())) +} + +/// This is the only way to create a PriorityQueue with the U32HasherState and capacity +pub fn u32priority_queue_with_capacity( + capacity: usize, +) -> PriorityQueue { + PriorityQueue::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 + bytemuck::cast([self.0, 0]) + } + + 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 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 type to u32. + self.0 = *bytemuck::from_bytes(bytes); + } +} + +#[cfg(test)] +mod tests { + use super::U32HasherState; + use priority_queue::PriorityQueue; + use std::collections::HashMap; + + #[test] + fn test_hashmap() { + let mut hm: HashMap = HashMap::with_hasher(U32HasherState(())); + + // Testing basic operations with the custom hasher + 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); + } + + #[test] + fn test_priority_queue() { + let mut pq: PriorityQueue = + PriorityQueue::with_hasher(U32HasherState(())); + + // Testing basic operations with the custom hasher + assert_eq!(pq.push(1, 5), None); + assert_eq!(pq.push(2, -10), None); + assert_eq!(pq.push(3, 7), None); + assert_eq!(pq.push(4, 20), None); + assert_eq!(pq.push(u32::MAX, -42), None); + + assert_eq!(pq.push_increase(1, 4), Some(4)); + assert_eq!(pq.push_increase(2, -8), Some(-10)); + assert_eq!(pq.push_increase(3, 5), Some(5)); + assert_eq!(pq.push_increase(4, 21), Some(20)); + assert_eq!(pq.push_increase(u32::MAX, -99), Some(-99)); + assert_eq!(pq.push_increase(42, 1337), None); + + assert_eq!(pq.push_decrease(1, 4), Some(5)); + assert_eq!(pq.push_decrease(2, -10), Some(-8)); + assert_eq!(pq.push_decrease(3, 5), Some(7)); + assert_eq!(pq.push_decrease(4, 20), Some(21)); + assert_eq!(pq.push_decrease(u32::MAX, 100), Some(100)); + assert_eq!(pq.push_decrease(69, 420), None); + + assert_eq!(pq.peek(), Some((&42, &1337))); + assert_eq!(pq.pop(), Some((42, 1337))); + assert_eq!(pq.peek(), Some((&69, &420))); + assert_eq!(pq.pop(), Some((69, 420))); + assert_eq!(pq.peek(), Some((&4, &20))); + assert_eq!(pq.pop(), Some((4, 20))); + assert_eq!(pq.peek(), Some((&3, &5))); + assert_eq!(pq.pop(), Some((3, 5))); + assert_eq!(pq.peek(), Some((&1, &4))); + assert_eq!(pq.pop(), Some((1, 4))); + assert_eq!(pq.peek(), Some((&2, &-10))); + assert_eq!(pq.pop(), Some((2, -10))); + assert_eq!(pq.peek(), Some((&u32::MAX, &-42))); + assert_eq!(pq.pop(), Some((u32::MAX, -42))); + assert_eq!(pq.peek(), None); + assert_eq!(pq.pop(), None); + } +} diff --git a/backend/rust-gbt/src/u32_hashmap.rs b/backend/rust-gbt/src/u32_hashmap.rs deleted file mode 100644 index cfa513301..000000000 --- a/backend/rust-gbt/src/u32_hashmap.rs +++ /dev/null @@ -1,72 +0,0 @@ -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 - bytemuck::cast([self.0, 0]) - } - - 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 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 type to u32. - self.0 = *bytemuck::from_bytes(bytes); - } -} - -#[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); - } -}