Skip to content

Commit 73ad301

Browse files
author
SyntheticBird45
committed
Implements P2P Bucket data structure
This commit implements a "Bucket" data structure that is a collection of data that discriminates its items into "buckets" (vector of size N) following a defined function. - Implements Bucket data structure and Bucketable trait - Implements Bucketable for Ipv4Addr - Added the crate to the workspace dependencies - Added arrayvec as a dependency
1 parent b57ee2f commit 73ad301

File tree

4 files changed

+163
-26
lines changed

4 files changed

+163
-26
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ members = [
1616
"net/wire",
1717
"p2p/p2p",
1818
"p2p/p2p-core",
19+
"p2p/bucket",
1920
"p2p/dandelion-tower",
2021
"p2p/async-buffer",
2122
"p2p/address-book",
@@ -51,35 +52,37 @@ opt-level = 3
5152

5253
[workspace.dependencies]
5354
# Cuprate members
54-
cuprate-fast-sync = { path = "consensus/fast-sync" ,default-features = false}
55-
cuprate-consensus-rules = { path = "consensus/rules" ,default-features = false}
56-
cuprate-constants = { path = "constants" ,default-features = false}
57-
cuprate-consensus = { path = "consensus" ,default-features = false}
58-
cuprate-consensus-context = { path = "consensus/context" ,default-features = false}
59-
cuprate-cryptonight = { path = "cryptonight" ,default-features = false}
60-
cuprate-helper = { path = "helper" ,default-features = false}
61-
cuprate-epee-encoding = { path = "net/epee-encoding" ,default-features = false}
62-
cuprate-fixed-bytes = { path = "net/fixed-bytes" ,default-features = false}
63-
cuprate-levin = { path = "net/levin" ,default-features = false}
64-
cuprate-wire = { path = "net/wire" ,default-features = false}
65-
cuprate-p2p = { path = "p2p/p2p" ,default-features = false}
66-
cuprate-p2p-core = { path = "p2p/p2p-core" ,default-features = false}
67-
cuprate-dandelion-tower = { path = "p2p/dandelion-tower" ,default-features = false}
68-
cuprate-async-buffer = { path = "p2p/async-buffer" ,default-features = false}
69-
cuprate-address-book = { path = "p2p/address-book" ,default-features = false}
70-
cuprate-blockchain = { path = "storage/blockchain" ,default-features = false}
71-
cuprate-database = { path = "storage/database" ,default-features = false}
72-
cuprate-database-service = { path = "storage/service" ,default-features = false}
73-
cuprate-txpool = { path = "storage/txpool" ,default-features = false}
74-
cuprate-pruning = { path = "pruning" ,default-features = false}
75-
cuprate-test-utils = { path = "test-utils" ,default-features = false}
76-
cuprate-types = { path = "types" ,default-features = false}
77-
cuprate-json-rpc = { path = "rpc/json-rpc" ,default-features = false}
78-
cuprate-rpc-types = { path = "rpc/types" ,default-features = false}
79-
cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false}
55+
cuprate-fast-sync = { path = "consensus/fast-sync" ,default-features = false}
56+
cuprate-consensus-rules = { path = "consensus/rules" ,default-features = false}
57+
cuprate-constants = { path = "constants" ,default-features = false}
58+
cuprate-consensus = { path = "consensus" ,default-features = false}
59+
cuprate-consensus-context = { path = "consensus/context" ,default-features = false}
60+
cuprate-cryptonight = { path = "cryptonight" ,default-features = false}
61+
cuprate-helper = { path = "helper" ,default-features = false}
62+
cuprate-epee-encoding = { path = "net/epee-encoding" ,default-features = false}
63+
cuprate-fixed-bytes = { path = "net/fixed-bytes" ,default-features = false}
64+
cuprate-levin = { path = "net/levin" ,default-features = false}
65+
cuprate-wire = { path = "net/wire" ,default-features = false}
66+
cuprate-p2p = { path = "p2p/p2p" ,default-features = false}
67+
cuprate-p2p-core = { path = "p2p/p2p-core" ,default-features = false}
68+
cuprate-p2p-bucket = { path = "p2p/p2p-bucket" ,default-features = false}
69+
cuprate-dandelion-tower = { path = "p2p/dandelion-tower" ,default-features = false}
70+
cuprate-async-buffer = { path = "p2p/async-buffer" ,default-features = false}
71+
cuprate-address-book = { path = "p2p/address-book" ,default-features = false}
72+
cuprate-blockchain = { path = "storage/blockchain" ,default-features = false}
73+
cuprate-database = { path = "storage/database" ,default-features = false}
74+
cuprate-database-service = { path = "storage/service" ,default-features = false}
75+
cuprate-txpool = { path = "storage/txpool" ,default-features = false}
76+
cuprate-pruning = { path = "pruning" ,default-features = false}
77+
cuprate-test-utils = { path = "test-utils" ,default-features = false}
78+
cuprate-types = { path = "types" ,default-features = false}
79+
cuprate-json-rpc = { path = "rpc/json-rpc" ,default-features = false}
80+
cuprate-rpc-types = { path = "rpc/types" ,default-features = false}
81+
cuprate-rpc-interface = { path = "rpc/interface" ,default-features = false}
8082

8183
# External dependencies
8284
anyhow = { version = "1.0.89", default-features = false }
85+
arrayvec = { version = "0.7.6", default-features = false}
8386
async-trait = { version = "0.1.82", default-features = false }
8487
bitflags = { version = "2.6.0", default-features = false }
8588
blake3 = { version = "1", default-features = false }

p2p/bucket/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "cuprate-p2p-bucket"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
arrayvec = { workspace = true }
8+
9+
[lints]
10+
workspace = true

p2p/bucket/src/lib.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! Bucket data structure
2+
//!
3+
//! A collection data structure that discriminates its unique items and place them into "buckets"
4+
//!
5+
//! The item must implement the [`Bucketable`] trait that defines how to create the discriminant
6+
//! from the item type. The data structure will internally contain any item into "buckets" or vectors
7+
//! of sized capacity N that regroup all the stored items with this specific discriminant.
8+
//!
9+
//! A practical example of this data structure is for storing N amount of IP discriminated by their subnets.
10+
//! You can store in each "buckets" corresponding to a /16 subnet up to N IPs of that subnet.
11+
//!
12+
13+
use arrayvec::ArrayVec;
14+
use std::{collections::BTreeMap, net::Ipv4Addr};
15+
16+
/// A discriminant can be compute from the type
17+
pub trait Bucketable: Sized + Eq + Clone {
18+
/// The type of the discriminant being used in the Binary tree.
19+
type Discriminant: Ord + AsRef<[u8]>;
20+
21+
/// Method that can compute the discriminant from the item.
22+
fn discriminant(&self) -> Self::Discriminant;
23+
}
24+
25+
/// A collection data structure discriminating its unique items
26+
/// with a specified method. Enable a fair distribution of the
27+
/// items based on their discriminants when popped.
28+
pub struct Bucket<const N: usize, I: Bucketable> {
29+
/// The storage of the bucket
30+
storage: BTreeMap<I::Discriminant, ArrayVec<I, N>>,
31+
/// Internal round-robin state
32+
round_robin: usize,
33+
}
34+
35+
impl<const N: usize, I: Bucketable> Bucket<N, I> {
36+
/// Create a new Bucket
37+
pub const fn new() -> Self {
38+
Self {
39+
storage: BTreeMap::new(),
40+
round_robin: 0,
41+
}
42+
}
43+
44+
/// Push a new element into the Bucket
45+
///
46+
/// Will internally create a new vector for each new discriminant being
47+
/// generated from an item.
48+
///
49+
/// This function WILL NOT push the element if it already exists.
50+
///
51+
pub fn push(&mut self, item: I) {
52+
let discriminant = item.discriminant();
53+
54+
if let Some(vec) = self.storage.get_mut(&discriminant) {
55+
// Check if the element already exists
56+
let already_exist = vec.iter().any(|v| &item == v);
57+
if !already_exist {
58+
// Push the new element
59+
vec.push(item);
60+
}
61+
} else {
62+
// Initialize the vector if not found
63+
let mut vec = ArrayVec::<I, N>::new();
64+
vec.push(item);
65+
self.storage.insert(discriminant, vec);
66+
}
67+
}
68+
69+
/// Will attempt to remove an item from the bucket
70+
pub fn remove(&mut self, item: &I) -> Option<I> {
71+
self.storage.get_mut(&item.discriminant()).and_then(|vec| {
72+
let find = vec
73+
.iter()
74+
.enumerate()
75+
.filter(|(_, v)| &item == v)
76+
.map(|(i, _)| i)
77+
.last();
78+
find.map(|index| vec.swap_remove(index))
79+
})
80+
}
81+
82+
/// Will remove a new element from any bucket following a round-robin
83+
/// pattern.
84+
///
85+
/// Repeated use of this function will provide a fair distribution of
86+
/// item based on their discriminants
87+
pub fn pop(&mut self) -> Option<I> {
88+
// Get the total amount of discriminants to explore
89+
let len = self.storage.len();
90+
91+
// Loop over every bucket for an element.
92+
for _ in 0..len {
93+
let (_, vec) = self.storage.iter_mut().nth(self.round_robin / len).unwrap();
94+
self.round_robin = self.round_robin.wrapping_add(1);
95+
if let Some(item) = vec.pop() {
96+
return Some(item);
97+
}
98+
}
99+
100+
None
101+
}
102+
}
103+
104+
impl<const N: usize, I: Bucketable> Default for Bucket<N, I> {
105+
fn default() -> Self {
106+
Self::new()
107+
}
108+
}
109+
110+
impl Bucketable for Ipv4Addr {
111+
// We are discriminating by /16 subnets
112+
type Discriminant = [u8; 2];
113+
114+
fn discriminant(&self) -> Self::Discriminant {
115+
[self.octets()[0], self.octets()[1]]
116+
}
117+
}

0 commit comments

Comments
 (0)