-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add gossipable trait + relevant logic #45
base: main
Are you sure you want to change the base?
Changes from 31 commits
5cbdd66
a0e662a
aa7f63c
ed23c1e
0c35d21
86580a6
bb397b9
10657e0
baf2923
1fea689
9f1ab3b
f5a464b
7ba33ea
cc7edeb
231d93b
c1b42c2
1f57923
db4cf30
e52b0a1
94085a6
3c3e138
c63269c
476016a
139dacb
b9fc9ec
a606351
573db11
f1b752a
bbf1488
5654fb1
20db932
27df668
0c446e9
9e67429
61c8256
3a7a5cd
8cc6084
b676024
acb1de0
3bec776
e046013
13205e7
7b1e8eb
f56ab25
884f1fd
c5446e6
e4b3400
fb0359e
ccce15b
774ca6e
ec6767a
7db76bc
36c40dd
f6e8baf
672c728
b89abd9
3faedf9
ff65229
65e8a14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,25 +12,42 @@ readme = "README.md" | |
|
||
[dependencies] | ||
avalanche-types = { path = "../../crates/avalanche-types", features = ["message"] } | ||
byteorder = "1.4.3" | ||
cert-manager = "0.0.10" # https://github.com/gyuho/cert-manager | ||
log = "0.4.20" | ||
rustls = { version = "0.21.5", features = ["logging", "dangerous_configuration"]} # https://github.com/rustls/rustls/tags | ||
rustls = { version = "0.21.5", features = ["logging", "dangerous_configuration"] } # https://github.com/rustls/rustls/tags | ||
rcgen = "0.10.0" | ||
hyper-rustls = "0.24.1" | ||
rustls-native-certs = "0.6.3" | ||
probabilistic-collections = { version = "0.7.0", features = ["serde"] } | ||
hyper = { version = "0.14.27", features = ["full"], optional = true } | ||
tokio-rustls = { version = "0.24.1", optional = true } | ||
tokio = { version = "1.32.0", features = ["full"] } | ||
prost = "0.12.0" | ||
prost-types = "0.12.0" | ||
prost-build = "0.12.0" | ||
bincode = "1.3.3" | ||
serde = { version = "1.0.188", features = ["derive"] } | ||
|
||
# for feature "pem" | ||
pem = { version = "3.0.0", optional = true } # https://github.com/jcreekmore/pem-rs | ||
|
||
|
||
[dev-dependencies] | ||
env_logger = "0.10.0" | ||
mockall = "0.11.4" | ||
proptest = "1.2.0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
random-manager = "0.0.5" | ||
testing_logger = "0.1.1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔, not sure we need this as it looks like it's unmaintained. I guess I'll see where it's used. |
||
tokio = { version = "1.32.0", features = ["full"] } | ||
tracing = "0.1.37" | ||
tracing-subscriber = "0.3.17" | ||
|
||
[build-dependencies] | ||
# ref. https://github.com/hyperium/tonic/tags | ||
# ref. https://github.com/hyperium/tonic/tree/master/tonic-build | ||
tonic-build = "0.9.2" | ||
|
||
[features] | ||
default = ["rustls", "pem_encoding"] | ||
rustls = ["hyper", "tokio-rustls"] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/// ref. <https://github.com/hyperium/tonic/tree/master/tonic-build> | ||
fn main() { | ||
tonic_build::configure() | ||
.out_dir("./src/p2p") | ||
.build_server(true) | ||
.build_client(true) | ||
.compile(&["./src/p2p/gossip/sdk.proto"], &["./src/p2p/gossip/"]) | ||
.unwrap(); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
//! A library for building p2p inbound and outbound connections. | ||
pub mod p2p; | ||
pub mod peer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
pub struct Client {} | ||
|
||
impl Client { | ||
pub async fn app_request_any(&self) {} | ||
pub async fn app_request(&self) {} | ||
pub async fn app_gossip(&self) {} | ||
pub async fn app_gossip_specific(&self) {} | ||
pub async fn cross_chain_app_request(&self) {} | ||
pub async fn prefix_message(&self) {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
use crate::p2p::gossip::Gossipable; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. avalanchego uses the |
||
use avalanche_types::ids::{Id, LEN}; | ||
use byteorder::{BigEndian, ByteOrder}; | ||
use probabilistic_collections::bloom::BloomFilter; | ||
use proptest::prelude::*; | ||
use std::error::Error; | ||
use serde::Deserialize; | ||
|
||
#[derive(Debug)] | ||
pub struct Bloom { | ||
pub bloom: BloomFilter<Hasher>, | ||
pub salt: Id, | ||
} | ||
|
||
#[derive(Debug, Hash, Deserialize)] | ||
pub struct Hasher { | ||
hash: Vec<u8>, | ||
salt: Id, | ||
} | ||
|
||
impl Bloom { | ||
pub fn new_bloom_filter(max_expected_elements: usize, false_positive_probability: f64) -> Self { | ||
let salt = random_salt(); | ||
|
||
Bloom { | ||
bloom: BloomFilter::new(max_expected_elements, false_positive_probability), | ||
salt, | ||
} | ||
} | ||
|
||
pub fn new_bloom_filter_with_salt(max_expected_elements: usize, false_positive_probability: f64, salt: Id) -> Self { | ||
Bloom { | ||
bloom: BloomFilter::new(max_expected_elements, false_positive_probability), | ||
salt, | ||
} | ||
} | ||
|
||
pub fn add<T: Gossipable>(&mut self, gossipable: T) { | ||
let id = gossipable.get_id(); | ||
|
||
let salted = Hasher { | ||
hash: id.to_vec(), | ||
salt: self.salt, | ||
}; | ||
|
||
self.bloom.insert(&salted) | ||
} | ||
|
||
pub fn has<T: Gossipable>(&self, gossipable: &T) -> bool { | ||
let id = gossipable.get_id(); | ||
|
||
let salted = Hasher { | ||
hash: id.to_vec(), | ||
salt: self.salt, | ||
}; | ||
|
||
self.bloom.contains(&salted) | ||
} | ||
} | ||
|
||
pub fn reset_bloom_filter_if_needed( | ||
bloom_filter: &mut Bloom, | ||
false_positive_probability: f64, | ||
) -> bool { | ||
if bloom_filter.bloom.estimated_fpp() < false_positive_probability { | ||
return false; | ||
} | ||
|
||
let new_bloom_filter = BloomFilter::new(bloom_filter.bloom.len(), false_positive_probability); | ||
let salt = random_salt(); | ||
|
||
bloom_filter.bloom = new_bloom_filter; | ||
bloom_filter.salt = salt; | ||
true | ||
} | ||
|
||
fn random_salt() -> Id { | ||
let random_32_bytes = random_manager::secure_bytes(32).unwrap(); | ||
let salt: Id = Id::from_slice(random_32_bytes.as_slice()); | ||
salt | ||
} | ||
|
||
impl Hasher { | ||
pub fn write(&mut self, p: &[u8]) -> Result<usize, std::io::Error> { | ||
self.hash.extend_from_slice(p); | ||
Ok(p.len()) | ||
} | ||
|
||
pub fn sum(&mut self, b: &[u8]) -> Vec<u8> { | ||
self.hash.extend_from_slice(b); | ||
self.hash.clone() | ||
} | ||
|
||
pub fn reset(&mut self) { | ||
self.hash = vec![0; LEN]; | ||
} | ||
|
||
pub fn block_size() -> usize { | ||
LEN | ||
} | ||
|
||
pub fn sum64(&self) -> u64 { | ||
let mut salted = [0u8; LEN]; | ||
|
||
for i in 0..std::cmp::min(self.hash.len(), LEN) { | ||
salted[i] = self.hash[i] ^ self.salt.to_vec().get(i).unwrap(); | ||
} | ||
|
||
BigEndian::read_u64(&salted[0..8]) | ||
} | ||
|
||
pub fn size(&self) -> usize { | ||
self.hash.len() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use std::error::Error; | ||
use proptest::proptest; | ||
use avalanche_types::ids::Id; | ||
use crate::p2p::gossip::Gossipable; | ||
use proptest::prelude::*; | ||
use crate::p2p::gossip::bloom::*; | ||
|
||
#[derive(Debug, Clone)] | ||
struct TestTx { | ||
pub id: Id, | ||
} | ||
|
||
impl Gossipable for TestTx { | ||
fn get_id(&self) -> Id { | ||
self.id | ||
} | ||
|
||
fn marshal(&self) -> Result<Vec<u8>, Box<dyn Error>> { | ||
todo!() | ||
} | ||
|
||
fn unmarshal(&mut self, bytes: &[u8]) -> Result<(), Box<dyn Error>> { | ||
todo!() | ||
} | ||
} | ||
|
||
proptest! { | ||
#![proptest_config(ProptestConfig { | ||
cases: 100, // Need 100 successful test cases | ||
.. ProptestConfig::default() | ||
})] | ||
|
||
#[test] | ||
fn test_bloom_filter_refresh( | ||
false_positive_probability in 0.0..1.0f64, | ||
txs in proptest::collection::vec(any::<[u8; 32]>(), 0..100) // Will populate txs with 0 to 100 [u8; 32] | ||
) { | ||
let mut bloom_filter = Bloom::new_bloom_filter(10, 0.01); | ||
let mut expected = vec![]; | ||
|
||
for tx in txs { | ||
let should_reset = reset_bloom_filter_if_needed(&mut bloom_filter, false_positive_probability); | ||
let test_tx = TestTx { id: Id::from_slice(&tx) }; | ||
if should_reset { | ||
expected.clear(); | ||
} | ||
|
||
bloom_filter.add(test_tx.clone()); | ||
expected.push(test_tx.clone()); | ||
|
||
for expected_tx in &expected { | ||
assert!(bloom_filter.has(expected_tx)) | ||
} | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the
full
tokio set of features is really big. Can we only enable the features we definitely need?