mod tests {
use byteorder::{BigEndian, ByteOrder};
use std::thread::sleep;
use std::time::Duration;
use async_std::net::Ipv4Addr;
use rand::Rng;
use crate::{
GossipsubConfig, GossipsubConfigBuilder, GossipsubMessage, IdentTopic as Topic,
TopicScoreParams,
};
use super::super::*;
use crate::error::ValidationError;
use crate::subscription_filter::WhitelistSubscriptionFilter;
use crate::transform::{DataTransform, IdentityTransform};
use crate::types::FastMessageId;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(Default, Builder, Debug)]
#[builder(default)]
struct InjectNodes<D, F>
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
peer_no: usize,
topics: Vec<String>,
to_subscribe: bool,
gs_config: GossipsubConfig,
explicit: usize,
outbound: usize,
scoring: Option<(PeerScoreParams, PeerScoreThresholds)>,
data_transform: D,
subscription_filter: F,
}
impl<D, F> InjectNodes<D, F>
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
pub fn create_network(self) -> (Gossipsub<D, F>, Vec<PeerId>, Vec<TopicHash>) {
let keypair = tet_libp2p_core::identity::Keypair::generate_secp256k1();
let mut gs: Gossipsub<D, F> = Gossipsub::new_with_subscription_filter_and_transform(
MessageAuthenticity::Signed(keypair),
self.gs_config,
self.subscription_filter,
self.data_transform,
)
.unwrap();
if let Some((scoring_params, scoring_thresholds)) = self.scoring {
gs.with_peer_score(scoring_params, scoring_thresholds)
.unwrap();
}
let mut topic_hashes = vec![];
for t in self.topics {
let topic = Topic::new(t);
gs.subscribe(&topic).unwrap();
topic_hashes.push(topic.hash().clone());
}
let mut peers = vec![];
let empty = vec![];
for i in 0..self.peer_no {
peers.push(add_peer(
&mut gs,
if self.to_subscribe {
&topic_hashes
} else {
&empty
},
i < self.outbound,
i < self.explicit,
));
}
(gs, peers, topic_hashes)
}
}
impl<D, F> InjectNodesBuilder<D, F>
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
pub fn create_network(&self) -> (Gossipsub<D, F>, Vec<PeerId>, Vec<TopicHash>) {
self.build().unwrap().create_network()
}
}
fn inject_nodes<D, F>() -> InjectNodesBuilder<D, F>
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
InjectNodesBuilder::default()
}
fn inject_nodes1() -> InjectNodesBuilder<IdentityTransform, AllowAllSubscriptionFilter> {
inject_nodes()
}
fn add_peer<D, F>(
gs: &mut Gossipsub<D, F>,
topic_hashes: &Vec<TopicHash>,
outbound: bool,
explicit: bool,
) -> PeerId
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
add_peer_with_addr(gs, topic_hashes, outbound, explicit, Multiaddr::empty())
}
fn add_peer_with_addr<D, F>(
gs: &mut Gossipsub<D, F>,
topic_hashes: &Vec<TopicHash>,
outbound: bool,
explicit: bool,
address: Multiaddr,
) -> PeerId
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
add_peer_with_addr_and_kind(
gs,
topic_hashes,
outbound,
explicit,
address,
Some(PeerKind::Gossipsubv1_1),
)
}
fn add_peer_with_addr_and_kind<D, F>(
gs: &mut Gossipsub<D, F>,
topic_hashes: &Vec<TopicHash>,
outbound: bool,
explicit: bool,
address: Multiaddr,
kind: Option<PeerKind>,
) -> PeerId
where
D: DataTransform + Default + Clone + Send + 'static,
F: TopicSubscriptionFilter + Clone + Default + Send + 'static,
{
let peer = PeerId::random();
gs.inject_connection_established(
&peer,
&ConnectionId::new(0),
&if outbound {
ConnectedPoint::Dialer { address }
} else {
ConnectedPoint::Listener {
local_addr: Multiaddr::empty(),
send_back_addr: address,
}
},
);
<Gossipsub<D, F> as NetworkBehaviour>::inject_connected(gs, &peer);
if let Some(kind) = kind {
gs.inject_event(
peer.clone(),
ConnectionId::new(1),
HandlerEvent::PeerKind(kind),
);
}
if explicit {
gs.add_explicit_peer(&peer);
}
if !topic_hashes.is_empty() {
gs.handle_received_subscriptions(
&topic_hashes
.iter()
.cloned()
.map(|t| GossipsubSubscription {
action: GossipsubSubscriptionAction::Subscribe,
topic_hash: t,
})
.collect::<Vec<_>>(),
&peer,
);
}
peer
}
fn proto_to_message(rpc: &crate::rpc_proto::Rpc) -> GossipsubRpc {
let mut messages = Vec::with_capacity(rpc.publish.len());
let rpc = rpc.clone();
for message in rpc.publish.into_iter() {
messages.push(RawGossipsubMessage {
source: message.from.map(|x| PeerId::from_bytes(&x).unwrap()),
data: message.data.unwrap_or_default(),
sequence_number: message.seqno.map(|x| BigEndian::read_u64(&x)), topic: TopicHash::from_raw(message.topic),
signature: message.signature, key: None,
validated: false,
});
}
let mut control_msgs = Vec::new();
if let Some(rpc_control) = rpc.control {
let ihave_msgs: Vec<GossipsubControlAction> = rpc_control
.ihave
.into_iter()
.map(|ihave| GossipsubControlAction::IHave {
topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()),
message_ids: ihave
.message_ids
.into_iter()
.map(MessageId::from)
.collect::<Vec<_>>(),
})
.collect();
let iwant_msgs: Vec<GossipsubControlAction> = rpc_control
.iwant
.into_iter()
.map(|iwant| GossipsubControlAction::IWant {
message_ids: iwant
.message_ids
.into_iter()
.map(MessageId::from)
.collect::<Vec<_>>(),
})
.collect();
let graft_msgs: Vec<GossipsubControlAction> = rpc_control
.graft
.into_iter()
.map(|graft| GossipsubControlAction::Graft {
topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()),
})
.collect();
let mut prune_msgs = Vec::new();
for prune in rpc_control.prune {
let peers = prune
.peers
.into_iter()
.filter_map(|info| {
info.peer_id
.and_then(|id| PeerId::from_bytes(&id).ok())
.map(|peer_id|
PeerInfo {
peer_id: Some(peer_id),
})
})
.collect::<Vec<PeerInfo>>();
let topic_hash = TopicHash::from_raw(prune.topic_id.unwrap_or_default());
prune_msgs.push(GossipsubControlAction::Prune {
topic_hash,
peers,
backoff: prune.backoff,
});
}
control_msgs.extend(ihave_msgs);
control_msgs.extend(iwant_msgs);
control_msgs.extend(graft_msgs);
control_msgs.extend(prune_msgs);
}
GossipsubRpc {
messages,
subscriptions: rpc
.subscriptions
.into_iter()
.map(|sub| GossipsubSubscription {
action: if Some(true) == sub.subscribe {
GossipsubSubscriptionAction::Subscribe
} else {
GossipsubSubscriptionAction::Unsubscribe
},
topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()),
})
.collect(),
control_msgs,
}
}
#[test]
fn test_subscribe() {
let subscribe_topic = vec![String::from("test_subscribe")];
let (gs, _, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(subscribe_topic)
.to_subscribe(true)
.create_network();
assert!(
gs.mesh.get(&topic_hashes[0]).is_some(),
"Subscribe should add a new entry to the mesh[topic] hashmap"
);
let subscriptions =
gs.events
.iter()
.fold(vec![], |mut collected_subscriptions, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
for s in &event.subscriptions {
match s.subscribe {
Some(true) => collected_subscriptions.push(s.clone()),
_ => {}
};
}
collected_subscriptions
}
_ => collected_subscriptions,
});
assert!(
subscriptions.len() == 20,
"Should send a subscription to all known peers"
);
}
#[test]
fn test_unsubscribe() {
let topic_strings = vec![String::from("topic1"), String::from("topic2")];
let topics = topic_strings
.iter()
.map(|t| Topic::new(t.clone()))
.collect::<Vec<Topic>>();
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(topic_strings)
.to_subscribe(true)
.create_network();
for topic_hash in &topic_hashes {
assert!(
gs.topic_peers.get(&topic_hash).is_some(),
"Topic_peers contain a topic entry"
);
assert!(
gs.mesh.get(&topic_hash).is_some(),
"mesh should contain a topic entry"
);
}
assert!(
gs.unsubscribe(&topics[0]).unwrap(),
"should be able to unsubscribe successfully from each topic",
);
assert!(
gs.unsubscribe(&topics[1]).unwrap(),
"should be able to unsubscribe successfully from each topic",
);
let subscriptions =
gs.events
.iter()
.fold(vec![], |mut collected_subscriptions, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
for s in &event.subscriptions {
match s.subscribe {
Some(false) => collected_subscriptions.push(s.clone()),
_ => {}
};
}
collected_subscriptions
}
_ => collected_subscriptions,
});
assert!(
subscriptions.len() == 40,
"Should send an unsubscribe event to all known peers"
);
for topic_hash in &topic_hashes {
assert!(
gs.mesh.get(&topic_hash).is_none(),
"All topics should have been removed from the mesh"
);
}
}
#[test]
fn test_join() {
let topic_strings = vec![String::from("topic1"), String::from("topic2")];
let topics = topic_strings
.iter()
.map(|t| Topic::new(t.clone()))
.collect::<Vec<Topic>>();
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(topic_strings)
.to_subscribe(true)
.create_network();
assert!(
gs.unsubscribe(&topics[0]).unwrap(),
"should be able to unsubscribe successfully"
);
assert!(
gs.unsubscribe(&topics[1]).unwrap(),
"should be able to unsubscribe successfully"
);
assert!(
gs.subscribe(&topics[0]).unwrap(),
"should be able to subscribe successfully"
);
assert!(
gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6,
"Should have added 6 nodes to the mesh"
);
fn collect_grafts(
mut collected_grafts: Vec<GossipsubControlAction>,
(_, controls): (&PeerId, &Vec<GossipsubControlAction>),
) -> Vec<GossipsubControlAction> {
for c in controls.iter() {
match c {
GossipsubControlAction::Graft { topic_hash: _ } => {
collected_grafts.push(c.clone())
}
_ => {}
}
}
collected_grafts
}
let graft_messages = gs.control_pool.iter().fold(vec![], collect_grafts);
assert_eq!(
graft_messages.len(),
6,
"There should be 6 grafts messages sent to peers"
);
gs.fanout
.insert(topic_hashes[1].clone(), Default::default());
let new_peers: Vec<PeerId> = vec![];
for _ in 0..3 {
let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap();
fanout_peers.insert(PeerId::random());
}
gs.subscribe(&topics[1]).unwrap();
assert!(
gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6,
"Should have added 6 nodes to the mesh"
);
let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap();
for new_peer in new_peers {
assert!(
mesh_peers.contains(&new_peer),
"Fanout peer should be included in the mesh"
);
}
let graft_messages = gs.control_pool.iter().fold(vec![], collect_grafts);
assert!(
graft_messages.len() == 12,
"There should be 12 grafts messages sent to peers"
);
}
#[test]
fn test_publish_without_flood_publishing() {
let config = GossipsubConfigBuilder::default()
.flood_publish(false)
.build()
.unwrap();
let publish_topic = String::from("test_publish");
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![publish_topic.clone()])
.to_subscribe(true)
.gs_config(config)
.create_network();
assert!(
gs.mesh.get(&topic_hashes[0]).is_some(),
"Subscribe should add a new entry to the mesh[topic] hashmap"
);
assert_eq!(
gs.topic_peers.get(&topic_hashes[0]).map(|p| p.len()),
Some(20),
"Peers should be subscribed to the topic"
);
let publish_data = vec![0; 42];
gs.publish(Topic::new(publish_topic), publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push(s.clone());
}
collected_publish
}
_ => collected_publish,
});
let message = &gs
.data_transform
.inbound_transform(
publishes
.first()
.expect("Should contain > 0 entries")
.clone(),
)
.unwrap();
let msg_id = gs.config.message_id(&message);
let config: GossipsubConfig = GossipsubConfig::default();
assert_eq!(
publishes.len(),
config.mesh_n_low(),
"Should send a publish message to all known peers"
);
assert!(
gs.mcache.get(&msg_id).is_some(),
"Message cache should contain published message"
);
}
#[test]
fn test_fanout() {
let config = GossipsubConfigBuilder::default()
.flood_publish(false)
.build()
.unwrap();
let fanout_topic = String::from("test_fanout");
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![fanout_topic.clone()])
.to_subscribe(true)
.gs_config(config)
.create_network();
assert!(
gs.mesh.get(&topic_hashes[0]).is_some(),
"Subscribe should add a new entry to the mesh[topic] hashmap"
);
assert!(
gs.unsubscribe(&Topic::new(fanout_topic.clone())).unwrap(),
"should be able to unsubscribe successfully from topic"
);
let publish_data = vec![0; 42];
gs.publish(Topic::new(fanout_topic.clone()), publish_data)
.unwrap();
assert_eq!(
gs.fanout
.get(&TopicHash::from_raw(fanout_topic.clone()))
.unwrap()
.len(),
gs.config.mesh_n(),
"Fanout should contain `mesh_n` peers for fanout topic"
);
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push(s.clone());
}
collected_publish
}
_ => collected_publish,
});
let message = &gs
.data_transform
.inbound_transform(
publishes
.first()
.expect("Should contain > 0 entries")
.clone(),
)
.unwrap();
let msg_id = gs.config.message_id(&message);
assert_eq!(
publishes.len(),
gs.config.mesh_n(),
"Should send a publish message to `mesh_n` fanout peers"
);
assert!(
gs.mcache.get(&msg_id).is_some(),
"Message cache should contain published message"
);
}
#[test]
fn test_inject_connected() {
let (gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1"), String::from("topic2")])
.to_subscribe(true)
.create_network();
let send_events: Vec<&NetworkBehaviourAction<Arc<rpc_proto::Rpc>, GossipsubEvent>> = gs
.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
!event.subscriptions.is_empty()
}
_ => false,
})
.collect();
for sevent in send_events.clone() {
match sevent {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
assert!(
event.subscriptions.len() == 2,
"There should be two subscriptions sent to each peer (1 for each topic)."
);
}
_ => {}
};
}
assert!(
send_events.len() == 20,
"There should be a subscription event sent to each peer."
);
for peer in peers {
let known_topics = gs.peer_topics.get(&peer).unwrap();
assert!(
known_topics == &topic_hashes.iter().cloned().collect(),
"The topics for each node should all topics"
);
}
}
#[test]
fn test_handle_received_subscriptions() {
let topics = vec!["topic1", "topic2", "topic3", "topic4"]
.iter()
.map(|&t| String::from(t))
.collect();
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(topics)
.to_subscribe(false)
.create_network();
let mut subscriptions = topic_hashes[..3]
.iter()
.map(|topic_hash| GossipsubSubscription {
action: GossipsubSubscriptionAction::Subscribe,
topic_hash: topic_hash.clone(),
})
.collect::<Vec<GossipsubSubscription>>();
subscriptions.push(GossipsubSubscription {
action: GossipsubSubscriptionAction::Unsubscribe,
topic_hash: topic_hashes[topic_hashes.len() - 1].clone(),
});
let unknown_peer = PeerId::random();
gs.handle_received_subscriptions(&subscriptions, &peers[0]);
gs.handle_received_subscriptions(&subscriptions, &peers[1]);
gs.handle_received_subscriptions(&subscriptions, &unknown_peer);
let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone();
assert!(
peer_topics == topic_hashes.iter().take(3).cloned().collect(),
"First peer should be subscribed to three topics"
);
let peer_topics = gs.peer_topics.get(&peers[1]).unwrap().clone();
assert!(
peer_topics == topic_hashes.iter().take(3).cloned().collect(),
"Second peer should be subscribed to three topics"
);
assert!(
gs.peer_topics.get(&unknown_peer).is_none(),
"Unknown peer should not have been added"
);
for topic_hash in topic_hashes[..3].iter() {
let topic_peers = gs.topic_peers.get(topic_hash).unwrap().clone();
assert!(
topic_peers == peers[..2].into_iter().cloned().collect(),
"Two peers should be added to the first three topics"
);
}
gs.handle_received_subscriptions(
&vec![GossipsubSubscription {
action: GossipsubSubscriptionAction::Unsubscribe,
topic_hash: topic_hashes[0].clone(),
}],
&peers[0],
);
let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone();
assert!(
peer_topics == topic_hashes[1..3].into_iter().cloned().collect(),
"Peer should be subscribed to two topics"
);
let topic_peers = gs.topic_peers.get(&topic_hashes[0]).unwrap().clone(); assert!(
topic_peers == peers[1..2].into_iter().cloned().collect(),
"Only the second peers should be in the first topic"
);
}
#[test]
fn test_get_random_peers() {
let gs_config = GossipsubConfigBuilder::default()
.validation_mode(ValidationMode::Anonymous)
.build()
.unwrap();
let mut gs: Gossipsub = Gossipsub::new(MessageAuthenticity::Anonymous, gs_config).unwrap();
let topic_hash = Topic::new("Test").hash().clone();
let mut peers = vec![];
for _ in 0..20 {
peers.push(PeerId::random())
}
gs.topic_peers
.insert(topic_hash.clone(), peers.iter().cloned().collect());
gs.peer_protocols = peers
.iter()
.map(|p| (p.clone(), PeerKind::Gossipsubv1_1))
.collect();
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 5, |_| {
true
});
assert_eq!(random_peers.len(), 5, "Expected 5 peers to be returned");
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 30, |_| {
true
});
assert!(random_peers.len() == 20, "Expected 20 peers to be returned");
assert!(
random_peers == peers.iter().cloned().collect(),
"Expected no shuffling"
);
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 20, |_| {
true
});
assert!(random_peers.len() == 20, "Expected 20 peers to be returned");
assert!(
random_peers == peers.iter().cloned().collect(),
"Expected no shuffling"
);
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 0, |_| {
true
});
assert!(random_peers.len() == 0, "Expected 0 peers to be returned");
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 5, |_| {
false
});
assert!(random_peers.len() == 0, "Expected 0 peers to be returned");
let random_peers =
get_random_peers(&gs.topic_peers, &gs.peer_protocols, &topic_hash, 10, {
|peer| peers.contains(peer)
});
assert!(random_peers.len() == 10, "Expected 10 peers to be returned");
}
#[test]
fn test_handle_iwant_msg_cached() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(20)
.topics(Vec::new())
.to_subscribe(true)
.create_network();
let raw_message = RawGossipsubMessage {
source: Some(peers[11].clone()),
data: vec![1, 2, 3, 4],
sequence_number: Some(1u64),
topic: TopicHash::from_raw("topic"),
signature: None,
key: None,
validated: true,
};
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
gs.mcache.put(&msg_id, raw_message);
gs.handle_iwant(&peers[7], vec![msg_id.clone()]);
let sent_messages = gs
.events
.iter()
.fold(vec![], |mut collected_messages, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
for c in &event.messages {
collected_messages.push(c.clone())
}
collected_messages
}
_ => collected_messages,
});
assert!(
sent_messages
.iter()
.map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap())
.any(|msg| gs.config.message_id(&msg) == msg_id),
"Expected the cached message to be sent to an IWANT peer"
);
}
#[test]
fn test_handle_iwant_msg_cached_shifted() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(20)
.topics(Vec::new())
.to_subscribe(true)
.create_network();
for shift in 1..10 {
let raw_message = RawGossipsubMessage {
source: Some(peers[11].clone()),
data: vec![1, 2, 3, 4],
sequence_number: Some(shift),
topic: TopicHash::from_raw("topic"),
signature: None,
key: None,
validated: true,
};
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
gs.mcache.put(&msg_id, raw_message);
for _ in 0..shift {
gs.mcache.shift();
}
gs.handle_iwant(&peers[7], vec![msg_id.clone()]);
let message_exists = gs.events.iter().any(|e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
event
.messages
.iter()
.map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap())
.any(|msg| gs.config.message_id(&msg) == msg_id)
}
_ => false,
});
if shift < 5 {
assert!(
message_exists,
"Expected the cached message to be sent to an IWANT peer before 5 shifts"
);
} else {
assert!(
!message_exists,
"Expected the cached message to not be sent to an IWANT peer after 5 shifts"
);
}
}
}
#[test]
fn test_handle_iwant_msg_not_cached() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(20)
.topics(Vec::new())
.to_subscribe(true)
.create_network();
let events_before = gs.events.len();
gs.handle_iwant(&peers[7], vec![MessageId::new(b"unknown id")]);
let events_after = gs.events.len();
assert_eq!(
events_before, events_after,
"Expected event count to stay the same"
);
}
#[test]
fn test_handle_ihave_subscribed_and_msg_not_cached() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
gs.handle_ihave(
&peers[7],
vec![(topic_hashes[0].clone(), vec![MessageId::new(b"unknown id")])],
);
let iwant_exists = match gs.control_pool.get(&peers[7]) {
Some(controls) => controls.iter().any(|c| match c {
GossipsubControlAction::IWant { message_ids } => message_ids
.iter()
.any(|m| *m == MessageId::new(b"unknown id")),
_ => false,
}),
_ => false,
};
assert!(
iwant_exists,
"Expected to send an IWANT control message for unkown message id"
);
}
#[test]
fn test_handle_ihave_subscribed_and_msg_cached() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
let msg_id = MessageId::new(b"known id");
let events_before = gs.events.len();
gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]);
let events_after = gs.events.len();
assert_eq!(
events_before, events_after,
"Expected event count to stay the same"
)
}
#[test]
fn test_handle_ihave_not_subscribed() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(20)
.topics(vec![])
.to_subscribe(true)
.create_network();
let events_before = gs.events.len();
gs.handle_ihave(
&peers[7],
vec![(
TopicHash::from_raw(String::from("unsubscribed topic")),
vec![MessageId::new(b"irrelevant id")],
)],
);
let events_after = gs.events.len();
assert_eq!(
events_before, events_after,
"Expected event count to stay the same"
)
}
#[test]
fn test_handle_graft_is_subscribed() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
gs.handle_graft(&peers[7], topic_hashes.clone());
assert!(
gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]),
"Expected peer to have been added to mesh"
);
}
#[test]
fn test_handle_graft_is_not_subscribed() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
gs.handle_graft(
&peers[7],
vec![TopicHash::from_raw(String::from("unsubscribed topic"))],
);
assert!(
!gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]),
"Expected peer to have been added to mesh"
);
}
#[test]
fn test_handle_graft_multiple_topics() {
let topics: Vec<String> = vec!["topic1", "topic2", "topic3", "topic4"]
.iter()
.map(|&t| String::from(t))
.collect();
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(topics.clone())
.to_subscribe(true)
.create_network();
let mut their_topics = topic_hashes.clone();
their_topics.pop();
gs.leave(&their_topics[2]);
gs.handle_graft(&peers[7], their_topics.clone());
for i in 0..2 {
assert!(
gs.mesh.get(&topic_hashes[i]).unwrap().contains(&peers[7]),
"Expected peer to be in the mesh for the first 2 topics"
);
}
assert!(
gs.mesh.get(&topic_hashes[2]).is_none(),
"Expected the second topic to not be in the mesh"
);
}
#[test]
fn test_handle_prune_peer_in_mesh() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(20)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
gs.mesh
.insert(topic_hashes[0].clone(), peers.iter().cloned().collect());
assert!(
gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]),
"Expected peer to be in mesh"
);
gs.handle_prune(
&peers[7],
topic_hashes
.iter()
.map(|h| (h.clone(), vec![], None))
.collect(),
);
assert!(
!gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]),
"Expected peer to be removed from mesh"
);
}
fn count_control_msgs<D: DataTransform, F: TopicSubscriptionFilter>(
gs: &Gossipsub<D, F>,
mut filter: impl FnMut(&PeerId, &GossipsubControlAction) -> bool,
) -> usize {
gs.control_pool
.iter()
.map(|(peer_id, actions)| actions.iter().filter(|m| filter(peer_id, m)).count())
.sum::<usize>()
+ gs.events
.iter()
.map(|e| match e {
NetworkBehaviourAction::NotifyHandler { peer_id, event, .. } => {
let event = proto_to_message(event);
event
.control_msgs
.iter()
.filter(|m| filter(peer_id, m))
.count()
}
_ => 0,
})
.sum::<usize>()
}
fn flush_events<D: DataTransform, F: TopicSubscriptionFilter>(gs: &mut Gossipsub<D, F>) {
gs.control_pool.clear();
gs.events.clear();
}
#[test]
fn test_explicit_peer_gets_connected() {
let (mut gs, _, _) = inject_nodes1()
.peer_no(0)
.topics(Vec::new())
.to_subscribe(true)
.create_network();
let peer = PeerId::random();
gs.add_explicit_peer(&peer);
let dial_events: Vec<&NetworkBehaviourAction<Arc<rpc_proto::Rpc>, GossipsubEvent>> = gs
.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer {
peer_id,
condition: DialPeerCondition::Disconnected,
} => peer_id == &peer,
_ => false,
})
.collect();
assert_eq!(
dial_events.len(),
1,
"There was no dial peer event for the explicit peer"
);
}
#[test]
fn test_explicit_peer_reconnects() {
let config = GossipsubConfigBuilder::default()
.check_explicit_peers_ticks(2)
.build()
.unwrap();
let (mut gs, others, _) = inject_nodes1()
.peer_no(1)
.topics(Vec::new())
.to_subscribe(true)
.gs_config(config)
.create_network();
let peer = others.get(0).unwrap();
gs.add_explicit_peer(peer);
flush_events(&mut gs);
gs.inject_disconnected(peer);
gs.heartbeat();
assert_eq!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer {
peer_id,
condition: DialPeerCondition::Disconnected,
} => peer_id == peer,
_ => false,
})
.count(),
0,
"There was a dial peer event before explicit_peer_ticks heartbeats"
);
gs.heartbeat();
assert!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer {
peer_id,
condition: DialPeerCondition::Disconnected,
} => peer_id == peer,
_ => false,
})
.count()
>= 1,
"There was no dial peer event for the explicit peer"
);
}
#[test]
fn test_handle_graft_explicit_peer() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(1)
.topics(vec![String::from("topic1"), String::from("topic2")])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
let peer = peers.get(0).unwrap();
gs.handle_graft(peer, topic_hashes.clone());
assert!(gs.mesh[&topic_hashes[0]].is_empty());
assert!(gs.mesh[&topic_hashes[1]].is_empty());
assert!(
count_control_msgs(&gs, |peer_id, m| peer_id == peer
&& match m {
GossipsubControlAction::Prune { topic_hash, .. } =>
topic_hash == &topic_hashes[0] || topic_hash == &topic_hashes[1],
_ => false,
})
>= 2,
"Not enough prunes sent when grafting from explicit peer"
);
}
#[test]
fn explicit_peers_not_added_to_mesh_on_receiving_subscription() {
let (gs, peers, topic_hashes) = inject_nodes1()
.peer_no(2)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
assert_eq!(
gs.mesh[&topic_hashes[0]],
vec![peers[1].clone()].into_iter().collect()
);
assert!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
})
>= 1,
"No graft message got created to non-explicit peer"
);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"A graft message got created to an explicit peer"
);
}
#[test]
fn do_not_graft_explicit_peer() {
let (mut gs, others, topic_hashes) = inject_nodes1()
.peer_no(1)
.topics(vec![String::from("topic")])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
gs.heartbeat();
assert_eq!(gs.mesh[&topic_hashes[0]], BTreeSet::new());
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &others[0]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"A graft message got created to an explicit peer"
);
}
#[test]
fn do_forward_messages_to_explicit_peers() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(2)
.topics(vec![String::from("topic1"), String::from("topic2")])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
let local_id = PeerId::random();
let message = RawGossipsubMessage {
source: Some(peers[1].clone()),
data: vec![12],
sequence_number: Some(0),
topic: topic_hashes[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(message.clone(), &local_id);
assert_eq!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::NotifyHandler { peer_id, event, .. } => {
let event = proto_to_message(event);
peer_id == &peers[0]
&& event
.messages
.iter()
.filter(|m| m.data == message.data)
.count()
> 0
}
_ => false,
})
.count(),
1,
"The message did not get forwarded to the explicit peer"
);
}
#[test]
fn explicit_peers_not_added_to_mesh_on_subscribe() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(2)
.topics(Vec::new())
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
let topic = Topic::new(String::from("t"));
let topic_hash = topic.hash();
for i in 0..2 {
gs.handle_received_subscriptions(
&vec![GossipsubSubscription {
action: GossipsubSubscriptionAction::Subscribe,
topic_hash: topic_hash.clone(),
}],
&peers[i],
);
}
gs.subscribe(&topic).unwrap();
assert_eq!(
gs.mesh[&topic_hash],
vec![peers[1].clone()].into_iter().collect()
);
assert!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
})
> 0,
"No graft message got created to non-explicit peer"
);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"A graft message got created to an explicit peer"
);
}
#[test]
fn explicit_peers_not_added_to_mesh_from_fanout_on_subscribe() {
let (mut gs, peers, _) = inject_nodes1()
.peer_no(2)
.topics(Vec::new())
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
let topic = Topic::new(String::from("t"));
let topic_hash = topic.hash();
for i in 0..2 {
gs.handle_received_subscriptions(
&vec![GossipsubSubscription {
action: GossipsubSubscriptionAction::Subscribe,
topic_hash: topic_hash.clone(),
}],
&peers[i],
);
}
gs.publish(topic.clone(), vec![1, 2, 3]).unwrap();
gs.subscribe(&topic).unwrap();
assert_eq!(
gs.mesh[&topic_hash],
vec![peers[1].clone()].into_iter().collect()
);
assert!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
})
>= 1,
"No graft message got created to non-explicit peer"
);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"A graft message got created to an explicit peer"
);
}
#[test]
fn no_gossip_gets_sent_to_explicit_peers() {
let (mut gs, peers, topic_hashes) = inject_nodes1()
.peer_no(2)
.topics(vec![String::from("topic1"), String::from("topic2")])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(1)
.create_network();
let local_id = PeerId::random();
let message = RawGossipsubMessage {
source: Some(peers[1].clone()),
data: vec![],
sequence_number: Some(0),
topic: topic_hashes[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(message.clone(), &local_id);
for _ in 0..3 {
gs.emit_gossip();
}
assert_eq!(
gs.control_pool
.get(&peers[0])
.unwrap_or(&Vec::new())
.iter()
.filter(|m| match m {
GossipsubControlAction::IHave { .. } => true,
_ => false,
})
.count(),
0,
"Gossip got emitted to explicit peer"
);
}
#[test]
fn test_mesh_addition() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n() + 1)
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
let to_remove_peers = config.mesh_n() + 1 - config.mesh_n_low() - 1;
for index in 0..to_remove_peers {
gs.handle_prune(
&peers[index],
topics.iter().map(|h| (h.clone(), vec![], None)).collect(),
);
}
assert_eq!(
gs.mesh.get(&topics[0]).unwrap().len(),
config.mesh_n_low() - 1
);
gs.heartbeat();
assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n());
}
#[test]
fn test_mesh_subtraction() {
let config = GossipsubConfig::default();
let n = config.mesh_n_high() + 10;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(n)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.outbound(n)
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
gs.heartbeat();
assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), config.mesh_n());
}
#[test]
fn test_connect_to_px_peers_on_handle_prune() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
let mut px = Vec::new();
for _ in 0..config.prune_peers() + 5 {
px.push(PeerInfo {
peer_id: Some(PeerId::random()),
});
}
gs.handle_prune(
&peers[0],
vec![(
topics[0].clone(),
px.clone(),
Some(config.prune_backoff().as_secs()),
)],
);
let dials: Vec<_> = gs
.events
.iter()
.filter_map(|e| match e {
NetworkBehaviourAction::DialPeer {
peer_id,
condition: DialPeerCondition::Disconnected,
} => Some(peer_id.clone()),
_ => None,
})
.collect();
assert_eq!(dials.len(), config.prune_peers());
let dials_set: HashSet<_> = dials.into_iter().collect();
assert_eq!(dials_set.len(), config.prune_peers());
assert!(dials_set.is_subset(&HashSet::from_iter(
px.iter().map(|i| i.peer_id.as_ref().unwrap().clone())
)));
}
#[test]
fn test_send_px_and_backoff_in_prune() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.prune_peers() + 1)
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
gs.send_graft_prune(
HashMap::new(),
vec![(peers[0].clone(), vec![topics[0].clone()])]
.into_iter()
.collect(),
HashSet::new(),
);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Prune {
topic_hash,
peers,
backoff,
} =>
topic_hash == &topics[0] &&
peers.len() == config.prune_peers() &&
peers.iter().collect::<HashSet<_>>().len() ==
config.prune_peers() &&
backoff.unwrap() == config.prune_backoff().as_secs(),
_ => false,
}),
1
);
}
#[test]
fn test_prune_backoffed_peer_on_graft() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.prune_peers() + 1)
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
gs.mesh.get_mut(&topics[0]).unwrap().remove(&peers[0]);
gs.send_graft_prune(
HashMap::new(),
vec![(peers[0].clone(), vec![topics[0].clone()])]
.into_iter()
.collect(),
HashSet::new(),
);
gs.events.clear();
gs.handle_graft(&peers[0], vec![topics[0].clone()]);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Prune {
topic_hash,
peers,
backoff,
} =>
topic_hash == &topics[0] &&
peers.is_empty() &&
backoff.unwrap() == config.prune_backoff().as_secs(),
_ => false,
}),
1
);
}
#[test]
fn test_do_not_graft_within_backoff_period() {
let config = GossipsubConfigBuilder::default()
.backoff_slack(1)
.heartbeat_interval(Duration::from_millis(100))
.build()
.unwrap();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config)
.create_network();
gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), Some(1))]);
flush_events(&mut gs);
gs.heartbeat();
for _ in 0..10 {
sleep(Duration::from_millis(100));
gs.heartbeat();
}
assert_eq!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"Graft message created too early within backoff period"
);
sleep(Duration::from_millis(100));
gs.heartbeat();
assert!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}) > 0,
"No graft message was created after backoff period"
);
}
#[test]
fn test_do_not_graft_within_default_backoff_period_after_receiving_prune_without_backoff() {
let config = GossipsubConfigBuilder::default()
.prune_backoff(Duration::from_millis(90))
.backoff_slack(1)
.heartbeat_interval(Duration::from_millis(100))
.build()
.unwrap();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config)
.create_network();
gs.handle_prune(&peers[0], vec![(topics[0].clone(), Vec::new(), None)]);
flush_events(&mut gs);
gs.heartbeat();
sleep(Duration::from_millis(100));
gs.heartbeat();
assert_eq!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}),
0,
"Graft message created too early within backoff period"
);
sleep(Duration::from_millis(100));
gs.heartbeat();
assert!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Graft { .. } => true,
_ => false,
}) > 0,
"No graft message was created after backoff period"
);
}
#[test]
fn test_flood_publish() {
let config: GossipsubConfig = GossipsubConfig::default();
let topic = "test";
let (mut gs, _, _) = inject_nodes1()
.peer_no(config.mesh_n_high() + 10)
.topics(vec![topic.into()])
.to_subscribe(true)
.create_network();
let publish_data = vec![0; 42];
gs.publish(Topic::new(topic), publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push(s.clone());
}
collected_publish
}
_ => collected_publish,
});
let message = &gs
.data_transform
.inbound_transform(
publishes
.first()
.expect("Should contain > 0 entries")
.clone(),
)
.unwrap();
let msg_id = gs.config.message_id(&message);
let config: GossipsubConfig = GossipsubConfig::default();
assert_eq!(
publishes.len(),
config.mesh_n_high() + 10,
"Should send a publish message to all known peers"
);
assert!(
gs.mcache.get(&msg_id).is_some(),
"Message cache should contain published message"
);
}
#[test]
fn test_gossip_to_at_least_gossip_lazy_peers() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(config.mesh_n_low() + config.gossip_lazy() + 1)
.topics(vec!["topic".into()])
.to_subscribe(true)
.create_network();
let raw_message = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![],
sequence_number: Some(0),
topic: topic_hashes[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(raw_message.clone(), &PeerId::random());
gs.emit_gossip();
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
assert_eq!(
count_control_msgs(&gs, |_, action| match action {
GossipsubControlAction::IHave {
topic_hash,
message_ids,
} => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id),
_ => false,
}),
config.gossip_lazy()
);
}
#[test]
fn test_gossip_to_at_most_gossip_factor_peers() {
let config: GossipsubConfig = GossipsubConfig::default();
let m =
config.mesh_n_low() + config.gossip_lazy() * (2.0 / config.gossip_factor()) as usize;
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(m)
.topics(vec!["topic".into()])
.to_subscribe(true)
.create_network();
let raw_message = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![],
sequence_number: Some(0),
topic: topic_hashes[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(raw_message.clone(), &PeerId::random());
gs.emit_gossip();
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
assert_eq!(
count_control_msgs(&gs, |_, action| match action {
GossipsubControlAction::IHave {
topic_hash,
message_ids,
} => topic_hash == &topic_hashes[0] && message_ids.iter().any(|id| id == &msg_id),
_ => false,
}),
((m - config.mesh_n_low()) as f64 * config.gossip_factor()) as usize
);
}
#[test]
fn test_accept_only_outbound_peer_grafts_when_mesh_full() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high());
let inbound = add_peer(&mut gs, &topics, false, false);
let outbound = add_peer(&mut gs, &topics, true, false);
gs.handle_graft(&inbound, vec![topics[0].clone()]);
gs.handle_graft(&outbound, vec![topics[0].clone()]);
assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high() + 1);
assert!(!gs.mesh[&topics[0]].contains(&inbound));
assert!(gs.mesh[&topics[0]].contains(&outbound));
}
#[test]
fn test_do_not_remove_too_many_outbound_peers() {
let m = 50;
let n = 2 * m;
let config = GossipsubConfigBuilder::default()
.mesh_n_high(n)
.mesh_n(n)
.mesh_n_low(n)
.mesh_outbound_min(m)
.build()
.unwrap();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(n)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let mut outbound = HashSet::new();
for _ in 0..m {
let peer = add_peer(&mut gs, &topics, true, false);
outbound.insert(peer.clone());
gs.handle_graft(&peer, topics.clone());
}
assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n + m);
gs.heartbeat();
assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), n);
assert!(outbound.iter().all(|p| gs.mesh[&topics[0]].contains(p)));
}
#[test]
fn test_add_outbound_peers_if_min_is_not_satisfied() {
let config: GossipsubConfig = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
for _ in 0..config.mesh_outbound_min() {
add_peer(&mut gs, &topics, true, false);
}
assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n_high());
gs.heartbeat();
assert_eq!(
gs.mesh[&topics[0]].len(),
config.mesh_n_high() + config.mesh_outbound_min()
);
}
#[test]
fn test_prune_negative_scored_peers() {
let config = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1);
gs.heartbeat();
assert!(gs.mesh[&topics[0]].is_empty());
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[0]
&& match m {
GossipsubControlAction::Prune {
topic_hash,
peers,
backoff,
} =>
topic_hash == &topics[0] &&
peers.is_empty() &&
backoff.unwrap() == config.prune_backoff().as_secs(),
_ => false,
}),
1
);
}
#[test]
fn test_dont_graft_to_negative_scored_peers() {
let config = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 1);
for p in peers {
gs.handle_prune(&p, vec![(topics[0].clone(), Vec::new(), None)]);
}
gs.heartbeat();
assert_eq!(gs.mesh.get(&topics[0]).unwrap().len(), 1);
assert!(gs.mesh.get(&topics[0]).unwrap().contains(&p2));
}
#[test]
fn test_ignore_px_from_negative_scored_peer() {
let config = GossipsubConfig::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1);
let px = vec![PeerInfo {
peer_id: Some(PeerId::random()),
}];
gs.handle_prune(
&peers[0],
vec![(
topics[0].clone(),
px.clone(),
Some(config.prune_backoff().as_secs()),
)],
);
assert_eq!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer { .. } => true,
_ => false,
})
.count(),
0
);
}
#[test]
fn test_only_send_nonnegative_scoring_peers_in_px() {
let config = GossipsubConfigBuilder::default()
.prune_peers(16)
.do_px()
.build()
.unwrap();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(3)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
gs.peer_score.as_mut().unwrap().0.add_penalty(&peers[0], 1);
gs.send_graft_prune(
HashMap::new(),
vec![(peers[1].clone(), vec![topics[0].clone()])]
.into_iter()
.collect(),
HashSet::new(),
);
assert_eq!(
count_control_msgs(&gs, |peer_id, m| peer_id == &peers[1]
&& match m {
GossipsubControlAction::Prune {
topic_hash,
peers: px,
..
} =>
topic_hash == &topics[0]
&& px.len() == 1
&& px[0].peer_id.as_ref().unwrap() == &peers[2],
_ => false,
}),
1
);
}
#[test]
fn test_do_not_gossip_to_peers_below_gossip_threshold() {
let config = GossipsubConfig::default();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
let raw_message = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![],
sequence_number: Some(0),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(raw_message.clone(), &PeerId::random());
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
gs.emit_gossip();
assert_eq!(
count_control_msgs(&gs, |peer, action| match action {
GossipsubControlAction::IHave {
topic_hash,
message_ids,
} => {
if topic_hash == &topics[0] && message_ids.iter().any(|id| id == &msg_id) {
assert_eq!(peer, &p2);
true
} else {
false
}
}
_ => false,
}),
1
);
}
#[test]
fn test_iwant_msg_from_peer_below_gossip_threshold_gets_ignored() {
let config = GossipsubConfig::default();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
let raw_message = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![],
sequence_number: Some(0),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
gs.handle_received_message(raw_message.clone(), &PeerId::random());
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
gs.handle_iwant(&p1, vec![msg_id.clone()]);
gs.handle_iwant(&p2, vec![msg_id.clone()]);
let sent_messages = gs
.events
.iter()
.fold(vec![], |mut collected_messages, e| match e {
NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => {
let event = proto_to_message(event);
for c in &event.messages {
collected_messages.push((peer_id.clone(), c.clone()))
}
collected_messages
}
_ => collected_messages,
});
assert!(sent_messages
.iter()
.map(|(peer_id, msg)| (
peer_id,
gs.data_transform.inbound_transform(msg.clone()).unwrap()
))
.any(|(peer_id, msg)| peer_id == &p2 && &gs.config.message_id(&msg) == &msg_id));
assert!(sent_messages
.iter()
.map(|(peer_id, msg)| (
peer_id,
gs.data_transform.inbound_transform(msg.clone()).unwrap()
))
.all(|(peer_id, msg)| !(peer_id == &p1 && &gs.config.message_id(&msg) == &msg_id)));
}
#[test]
fn test_ihave_msg_from_peer_below_gossip_threshold_gets_ignored() {
let config = GossipsubConfig::default();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
let raw_message = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![],
sequence_number: Some(0),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
let msg_id = gs.config.message_id(&message);
gs.handle_ihave(&p1, vec![(topics[0].clone(), vec![msg_id.clone()])]);
gs.handle_ihave(&p2, vec![(topics[0].clone(), vec![msg_id.clone()])]);
assert_eq!(
count_control_msgs(&gs, |peer, c| match c {
GossipsubControlAction::IWant { message_ids } =>
if message_ids.iter().any(|m| m == &msg_id) {
assert_eq!(peer, &p2);
true
} else {
false
},
_ => false,
}),
1
);
}
#[test]
fn test_do_not_publish_to_peer_below_publish_threshold() {
let config = GossipsubConfigBuilder::default()
.flood_publish(false)
.build()
.unwrap();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 0.5 * peer_score_params.behaviour_penalty_weight;
peer_score_thresholds.publish_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, _, _) = inject_nodes1()
.gs_config(config.clone())
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let topic = Topic::new("test");
let topics = vec![topic.hash()];
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
gs.heartbeat();
let publish_data = vec![0; 42];
gs.publish(topic, publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push((peer_id.clone(), s.clone()));
}
collected_publish
}
_ => collected_publish,
});
assert_eq!(publishes.len(), 1);
assert_eq!(publishes[0].0, p2);
}
#[test]
fn test_do_not_flood_publish_to_peer_below_publish_threshold() {
let config = GossipsubConfig::default();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 0.5 * peer_score_params.behaviour_penalty_weight;
peer_score_thresholds.publish_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, _, topics) = inject_nodes1()
.topics(vec!["test".into()])
.gs_config(config.clone())
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
gs.heartbeat();
let publish_data = vec![0; 42];
gs.publish(Topic::new("test"), publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push((peer_id.clone(), s.clone()));
}
collected_publish
}
_ => collected_publish,
});
assert_eq!(publishes.len(), 1);
assert!(publishes[0].0 == p2);
}
#[test]
fn test_ignore_rpc_from_peers_below_graylist_threshold() {
let config = GossipsubConfig::default();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.gossip_threshold = 0.5 * peer_score_params.behaviour_penalty_weight;
peer_score_thresholds.publish_threshold = 0.5 * peer_score_params.behaviour_penalty_weight;
peer_score_thresholds.graylist_threshold = 3.0 * peer_score_params.behaviour_penalty_weight;
let (mut gs, _, topics) = inject_nodes1()
.topics(vec!["test".into()])
.gs_config(config.clone())
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p1, 2);
gs.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
let raw_message1 = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![1, 2, 3, 4],
sequence_number: Some(1u64),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
let raw_message2 = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![1, 2, 3, 4, 5],
sequence_number: Some(2u64),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
let raw_message3 = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![1, 2, 3, 4, 5, 6],
sequence_number: Some(3u64),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
let raw_message4 = RawGossipsubMessage {
source: Some(PeerId::random()),
data: vec![1, 2, 3, 4, 5, 6, 7],
sequence_number: Some(4u64),
topic: topics[0].clone(),
signature: None,
key: None,
validated: true,
};
let message2 = &gs
.data_transform
.inbound_transform(raw_message2.clone())
.unwrap();
let message4 = &gs
.data_transform
.inbound_transform(raw_message4.clone())
.unwrap();
let subscription = GossipsubSubscription {
action: GossipsubSubscriptionAction::Subscribe,
topic_hash: topics[0].clone(),
};
let control_action = GossipsubControlAction::IHave {
topic_hash: topics[0].clone(),
message_ids: vec![config.message_id(&message2)],
};
gs.events.clear();
gs.inject_event(
p1.clone(),
ConnectionId::new(0),
HandlerEvent::Message {
rpc: GossipsubRpc {
messages: vec![raw_message1],
subscriptions: vec![subscription.clone()],
control_msgs: vec![control_action],
},
invalid_messages: Vec::new(),
},
);
assert_eq!(gs.events.len(), 1);
assert!(match &gs.events[0] {
NetworkBehaviourAction::GenerateEvent(event) => match event {
GossipsubEvent::Subscribed { .. } => true,
_ => false,
},
_ => false,
});
let control_action = GossipsubControlAction::IHave {
topic_hash: topics[0].clone(),
message_ids: vec![config.message_id(&message4)],
};
gs.inject_event(
p2.clone(),
ConnectionId::new(0),
HandlerEvent::Message {
rpc: GossipsubRpc {
messages: vec![raw_message3],
subscriptions: vec![subscription.clone()],
control_msgs: vec![control_action],
},
invalid_messages: Vec::new(),
},
);
assert!(gs.events.len() > 1);
}
#[test]
fn test_ignore_px_from_peers_below_accept_px_threshold() {
let config = GossipsubConfigBuilder::default()
.prune_peers(16)
.build()
.unwrap();
let peer_score_params = PeerScoreParams::default();
let mut peer_score_thresholds = PeerScoreThresholds::default();
peer_score_thresholds.accept_px_threshold = peer_score_params.app_specific_weight;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(2)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
gs.set_application_score(&peers[0], 0.99);
gs.set_application_score(&peers[1], 1.0);
let px = vec![PeerInfo {
peer_id: Some(PeerId::random()),
}];
gs.handle_prune(
&peers[0],
vec![(
topics[0].clone(),
px.clone(),
Some(config.prune_backoff().as_secs()),
)],
);
assert_eq!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer { .. } => true,
_ => false,
})
.count(),
0
);
let px = vec![PeerInfo {
peer_id: Some(PeerId::random()),
}];
gs.handle_prune(
&peers[1],
vec![(
topics[0].clone(),
px.clone(),
Some(config.prune_backoff().as_secs()),
)],
);
assert!(
gs.events
.iter()
.filter(|e| match e {
NetworkBehaviourAction::DialPeer { .. } => true,
_ => false,
})
.count()
> 0
);
}
#[test]
fn test_keep_best_scoring_peers_on_oversubscription() {
let config = GossipsubConfigBuilder::default()
.mesh_n_low(15)
.mesh_n(30)
.mesh_n_high(60)
.retain_scores(29)
.build()
.unwrap();
let n = config.mesh_n_high() + 1;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(n)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(n)
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
for peer in &peers {
gs.handle_graft(peer, topics.clone());
}
for (index, peer) in peers.iter().enumerate() {
gs.set_application_score(peer, index as f64);
}
assert_eq!(gs.mesh[&topics[0]].len(), n);
gs.heartbeat();
assert_eq!(gs.mesh[&topics[0]].len(), config.mesh_n());
assert!(gs.mesh[&topics[0]].is_superset(
&peers[(n - config.retain_scores())..]
.iter()
.cloned()
.collect()
));
}
#[test]
fn test_scoring_p1() {
let config = GossipsubConfig::default();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 2.0;
topic_params.time_in_mesh_quantum = Duration::from_millis(50);
topic_params.time_in_mesh_cap = 10.0;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, _) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
sleep(topic_params.time_in_mesh_quantum * 2);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0])
>= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight,
"score should be at least 2 * time_in_mesh_weight * topic_weight"
);
assert!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0])
< 3.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight,
"score should be less than 3 * time_in_mesh_weight * topic_weight"
);
sleep(topic_params.time_in_mesh_quantum * 2);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0])
>= 2.0 * topic_params.time_in_mesh_weight * topic_params.topic_weight,
"score should be at least 4 * time_in_mesh_weight * topic_weight"
);
sleep(topic_params.time_in_mesh_quantum * (topic_params.time_in_mesh_cap - 3.0) as u32);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
topic_params.time_in_mesh_cap
* topic_params.time_in_mesh_weight
* topic_params.topic_weight,
"score should be exactly time_in_mesh_cap * time_in_mesh_weight * topic_weight"
);
}
fn random_message(seq: &mut u64, topics: &Vec<TopicHash>) -> RawGossipsubMessage {
let mut rng = rand::thread_rng();
*seq += 1;
RawGossipsubMessage {
source: Some(PeerId::random()),
data: (0..rng.gen_range(10, 30))
.into_iter()
.map(|_| rng.gen())
.collect(),
sequence_number: Some(*seq),
topic: topics[rng.gen_range(0, topics.len())].clone(),
signature: None,
key: None,
validated: true,
}
}
#[test]
fn test_scoring_p2() {
let config = GossipsubConfig::default();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 2.0;
topic_params.first_message_deliveries_cap = 10.0;
topic_params.first_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(2)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
deliver_message(&mut gs, 1, m1.clone());
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
1.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight,
"score should be exactly first_message_deliveries_weight * topic_weight"
);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
0.0,
"there should be no score for second message deliveries * topic_weight"
);
deliver_message(&mut gs, 1, random_message(&mut seq, &topics));
deliver_message(&mut gs, 1, random_message(&mut seq, &topics));
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
2.0 * topic_params.first_message_deliveries_weight * topic_params.topic_weight,
"score should be exactly 2 * first_message_deliveries_weight * topic_weight"
);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
1.0 * topic_params.first_message_deliveries_decay
* topic_params.first_message_deliveries_weight
* topic_params.topic_weight,
"score should be exactly first_message_deliveries_decay * \
first_message_deliveries_weight * topic_weight"
);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
2.0 * topic_params.first_message_deliveries_decay
* topic_params.first_message_deliveries_weight
* topic_params.topic_weight,
"score should be exactly 2 * first_message_deliveries_decay * \
first_message_deliveries_weight * topic_weight"
);
for _ in 0..topic_params.first_message_deliveries_cap as u64 {
deliver_message(&mut gs, 1, random_message(&mut seq, &topics));
}
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
topic_params.first_message_deliveries_cap
* topic_params.first_message_deliveries_weight
* topic_params.topic_weight,
"score should be exactly first_message_deliveries_cap * \
first_message_deliveries_weight * topic_weight"
);
}
#[test]
fn test_scoring_p3() {
let config = GossipsubConfig::default();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = -2.0;
topic_params.mesh_message_deliveries_decay = 0.9;
topic_params.mesh_message_deliveries_cap = 10.0;
topic_params.mesh_message_deliveries_threshold = 5.0;
topic_params.mesh_message_deliveries_activation = Duration::from_secs(1);
topic_params.mesh_message_deliveries_window = Duration::from_millis(100);
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(2)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let mut expected_message_deliveries = 0.0;
let m1 = random_message(&mut seq, &topics);
let m2 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 1, m1.clone());
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
expected_message_deliveries += 2.0;
sleep(Duration::from_millis(60));
deliver_message(&mut gs, 1, m2.clone());
sleep(Duration::from_millis(70));
deliver_message(&mut gs, 0, m1);
deliver_message(&mut gs, 0, m2);
expected_message_deliveries += 1.0;
sleep(Duration::from_millis(900));
gs.peer_score.as_mut().unwrap().0.refresh_scores();
expected_message_deliveries *= 0.9;
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
(5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7
);
for _ in 0..20 {
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
}
expected_message_deliveries = 10.0;
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
for _ in 0..10 {
gs.peer_score.as_mut().unwrap().0.refresh_scores();
expected_message_deliveries *= 0.9; }
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
(5f64 - expected_message_deliveries).powi(2) * -2.0 * 0.7
);
}
#[test]
fn test_scoring_p3b() {
let config = GossipsubConfigBuilder::default()
.prune_backoff(Duration::from_millis(100))
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = -2.0;
topic_params.mesh_message_deliveries_decay = 0.9;
topic_params.mesh_message_deliveries_cap = 10.0;
topic_params.mesh_message_deliveries_threshold = 5.0;
topic_params.mesh_message_deliveries_activation = Duration::from_secs(1);
topic_params.mesh_message_deliveries_window = Duration::from_millis(100);
topic_params.mesh_failure_penalty_weight = -3.0;
topic_params.mesh_failure_penalty_decay = 0.95;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let mut expected_message_deliveries = 0.0;
gs.peer_score
.as_mut()
.unwrap()
.0
.set_application_score(&peers[0], 100.0);
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
expected_message_deliveries += 2.0;
sleep(Duration::from_millis(1050));
gs.peer_score.as_mut().unwrap().0.refresh_scores();
expected_message_deliveries *= 0.9;
gs.handle_prune(&peers[0], vec![(topics[0].clone(), vec![], None)]);
sleep(Duration::from_millis(130));
gs.handle_graft(&peers[0], topics.clone());
let mut expected_b3 = (5f64 - expected_message_deliveries).powi(2);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
100.0 + expected_b3 * -3.0 * 0.7
);
deliver_message(&mut gs, 0, random_message(&mut seq, &topics));
expected_message_deliveries += 1.0;
sleep(Duration::from_millis(1050));
gs.peer_score.as_mut().unwrap().0.refresh_scores();
expected_message_deliveries *= 0.9; expected_b3 *= 0.95;
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
100.0
+ (expected_b3 * -3.0 + (5f64 - expected_message_deliveries).powi(2) * -2.0) * 0.7
);
}
#[test]
fn test_scoring_p4_valid_message() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Accept,
)
.unwrap();
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
}
#[test]
fn test_scoring_p4_invalid_signature() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let m = random_message(&mut seq, &topics);
gs.inject_event(
peers[0].clone(),
ConnectionId::new(0),
HandlerEvent::Message {
rpc: GossipsubRpc {
messages: vec![],
subscriptions: vec![],
control_msgs: vec![],
},
invalid_messages: vec![(m, ValidationError::InvalidSignature)],
},
);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
-2.0 * 0.7
);
}
#[test]
fn test_scoring_p4_message_from_self() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let mut m = random_message(&mut seq, &topics);
m.source = Some(gs.publish_config.get_own_id().unwrap().clone());
deliver_message(&mut gs, 0, m.clone());
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
-2.0 * 0.7
);
}
#[test]
fn test_scoring_p4_ignored_message() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Ignore,
)
.unwrap();
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
}
#[test]
fn test_scoring_p4_application_invalidated_message() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
-2.0 * 0.7
);
}
#[test]
fn test_scoring_p4_application_invalid_message_from_two_peers() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(2)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
deliver_message(&mut gs, 1, m1.clone());
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[1]), 0.0);
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
-2.0 * 0.7
);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
-2.0 * 0.7
);
}
#[test]
fn test_scoring_p4_three_application_invalid_messages() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
let m2 = random_message(&mut seq, &topics);
let m3 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
deliver_message(&mut gs, 0, m2.clone());
deliver_message(&mut gs, 0, m3.clone());
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
let message2 = &gs.data_transform.inbound_transform(m2.clone()).unwrap();
let message3 = &gs.data_transform.inbound_transform(m3.clone()).unwrap();
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
gs.report_message_validation_result(
&config.message_id(&message2),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
gs.report_message_validation_result(
&config.message_id(&message3),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
9.0 * -2.0 * 0.7
);
}
#[test]
fn test_scoring_p4_decay() {
let config = GossipsubConfigBuilder::default()
.validate_messages()
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
let topic = Topic::new("test");
let topic_hash = topic.hash();
let mut topic_params = TopicScoreParams::default();
topic_params.time_in_mesh_weight = 0.0; topic_params.first_message_deliveries_weight = 0.0; topic_params.mesh_message_deliveries_weight = 0.0; topic_params.mesh_failure_penalty_weight = 0.0; topic_params.invalid_message_deliveries_weight = -2.0;
topic_params.invalid_message_deliveries_decay = 0.9;
topic_params.topic_weight = 0.7;
peer_score_params
.topics
.insert(topic_hash.clone(), topic_params.clone());
peer_score_params.app_specific_weight = 1.0;
let peer_score_thresholds = PeerScoreThresholds::default();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, peer_score_thresholds)))
.create_network();
let mut seq = 0;
let deliver_message = |gs: &mut Gossipsub, index: usize, msg: RawGossipsubMessage| {
gs.handle_received_message(msg, &peers[index]);
};
let m1 = random_message(&mut seq, &topics);
deliver_message(&mut gs, 0, m1.clone());
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&peers[0]), 0.0);
gs.report_message_validation_result(
&config.message_id(&message1),
&peers[0],
MessageAcceptance::Reject,
)
.unwrap();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
-2.0 * 0.7
);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
0.9 * 0.9 * -2.0 * 0.7
);
}
#[test]
fn test_scoring_p5() {
let mut peer_score_params = PeerScoreParams::default();
peer_score_params.app_specific_weight = 2.0;
let (mut gs, peers, _) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.gs_config(GossipsubConfig::default())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, PeerScoreThresholds::default())))
.create_network();
gs.set_application_score(&peers[0], 1.1);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
1.1 * 2.0
);
}
#[test]
fn test_scoring_p6() {
let mut peer_score_params = PeerScoreParams::default();
peer_score_params.ip_colocation_factor_threshold = 5.0;
peer_score_params.ip_colocation_factor_weight = -2.0;
let (mut gs, _, _) = inject_nodes1()
.peer_no(0)
.topics(vec![])
.to_subscribe(false)
.gs_config(GossipsubConfig::default())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, PeerScoreThresholds::default())))
.create_network();
let addr = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 3));
let peers = vec![
add_peer_with_addr(&mut gs, &vec![], false, false, addr.clone()),
add_peer_with_addr(&mut gs, &vec![], false, false, addr.clone()),
add_peer_with_addr(&mut gs, &vec![], true, false, addr.clone()),
add_peer_with_addr(&mut gs, &vec![], true, false, addr.clone()),
add_peer_with_addr(&mut gs, &vec![], true, true, addr.clone()),
];
let addr2 = Multiaddr::from(Ipv4Addr::new(10, 1, 2, 4));
let others = vec![
add_peer_with_addr(&mut gs, &vec![], false, false, addr2.clone()),
add_peer_with_addr(&mut gs, &vec![], false, false, addr2.clone()),
add_peer_with_addr(&mut gs, &vec![], true, false, addr2.clone()),
add_peer_with_addr(&mut gs, &vec![], true, false, addr2.clone()),
];
for peer in peers.iter().chain(others.iter()) {
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0);
}
for i in 0..3 {
gs.inject_connection_established(
&others[i],
&ConnectionId::new(0),
&ConnectedPoint::Dialer {
address: addr.clone(),
},
);
}
for peer in peers.iter().chain(others.iter().take(3)) {
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0);
}
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(&others[3]), 0.0);
for i in 0..3 {
gs.inject_connection_established(
&peers[i],
&ConnectionId::new(0),
&ConnectedPoint::Dialer {
address: addr2.clone(),
},
);
}
for peer in peers.iter().take(3).chain(others.iter().take(3)) {
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(peer),
(9.0 + 4.0) * -2.0
);
}
for peer in peers.iter().skip(3) {
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0);
}
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&others[3]),
4.0 * -2.0
);
gs.inject_connection_established(
&peers[0],
&ConnectionId::new(0),
&ConnectedPoint::Dialer {
address: addr.clone(),
},
);
for peer in peers.iter().take(3).chain(others.iter().take(3)) {
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(peer),
(9.0 + 4.0) * -2.0
);
}
for peer in peers.iter().skip(3) {
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 9.0 * -2.0);
}
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&others[3]),
4.0 * -2.0
);
}
#[test]
fn test_scoring_p7_grafts_before_backoff() {
let config = GossipsubConfigBuilder::default()
.prune_backoff(Duration::from_millis(200))
.graft_flood_threshold(Duration::from_millis(100))
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
peer_score_params.behaviour_penalty_weight = -2.0;
peer_score_params.behaviour_penalty_decay = 0.9;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(2)
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config)
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, PeerScoreThresholds::default())))
.create_network();
for i in 0..2 {
gs.mesh.get_mut(&topics[0]).unwrap().remove(&peers[i]);
gs.send_graft_prune(
HashMap::new(),
vec![(peers[i].clone(), vec![topics[0].clone()])]
.into_iter()
.collect(),
HashSet::new(),
);
}
sleep(Duration::from_millis(50));
gs.handle_graft(&peers[0], vec![topics[0].clone()]);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
4.0 * -2.0
);
sleep(Duration::from_millis(100));
gs.handle_graft(&peers[1], vec![topics[0].clone()]);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
1.0 * -2.0
);
gs.peer_score.as_mut().unwrap().0.refresh_scores();
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[0]),
4.0 * 0.9 * 0.9 * -2.0
);
assert_eq!(
gs.peer_score.as_ref().unwrap().0.score(&peers[1]),
1.0 * 0.9 * 0.9 * -2.0
);
}
#[test]
fn test_opportunistic_grafting() {
let config = GossipsubConfigBuilder::default()
.mesh_n_low(3)
.mesh_n(5)
.mesh_n_high(7)
.mesh_outbound_min(0) .opportunistic_graft_ticks(2)
.opportunistic_graft_peers(2)
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
peer_score_params.app_specific_weight = 1.0;
let mut thresholds = PeerScoreThresholds::default();
thresholds.opportunistic_graft_threshold = 2.0;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(5)
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config)
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, thresholds)))
.create_network();
for peer in &peers {
gs.handle_graft(peer, topics.clone());
}
let others: Vec<_> = (0..5)
.into_iter()
.map(|_| add_peer(&mut gs, &topics, false, false))
.collect();
assert_eq!(gs.mesh[&topics[0]], peers.iter().cloned().collect());
for i in 0..5 {
gs.set_application_score(&peers[i], 0.0 + i as f64);
}
for i in 0..5 {
gs.set_application_score(&others[i], 0.0 + i as f64);
}
gs.heartbeat();
gs.heartbeat();
assert_eq!(
gs.mesh[&topics[0]].len(),
5,
"should not apply opportunistic grafting"
);
gs.set_application_score(&peers[2], 1.0);
gs.heartbeat();
assert_eq!(
gs.mesh[&topics[0]].len(),
5,
"should not apply opportunistic grafting after first tick"
);
gs.heartbeat();
assert_eq!(
gs.mesh[&topics[0]].len(),
7,
"opportunistic grafting should have added 2 peers"
);
assert!(
gs.mesh[&topics[0]].is_superset(&peers.iter().cloned().collect()),
"old peers are still part of the mesh"
);
assert!(
gs.mesh[&topics[0]].is_disjoint(&others.iter().cloned().take(2).collect()),
"peers below or equal to median should not be added in opportunistic grafting"
);
}
#[test]
fn test_ignore_graft_from_unknown_topic() {
let (mut gs, _, _) = inject_nodes1()
.peer_no(0)
.topics(vec![])
.to_subscribe(false)
.create_network();
gs.handle_graft(&PeerId::random(), vec![Topic::new("test").hash()]);
assert_eq!(
count_control_msgs(&gs, |_, a| match a {
GossipsubControlAction::Prune { .. } => true,
_ => false,
}),
0,
"we should not prune after graft in unknown topic"
);
}
#[test]
fn test_ignore_too_many_iwants_from_same_peer_for_same_message() {
let config = GossipsubConfig::default();
let (mut gs, _, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(false)
.create_network();
let peer = add_peer(&mut gs, &topics, false, false);
let mut seq = 0;
let m1 = random_message(&mut seq, &topics);
let message1 = &gs.data_transform.inbound_transform(m1.clone()).unwrap();
let id = config.message_id(&message1);
gs.handle_received_message(m1.clone(), &PeerId::random());
gs.events.clear();
for _ in 0..(2 * config.gossip_retransimission() + 10) {
gs.handle_iwant(&peer, vec![id.clone()]);
}
assert_eq!(
gs.events
.iter()
.map(|e| match e {
NetworkBehaviourAction::NotifyHandler { event, .. } => {
let event = proto_to_message(event);
event.messages.len()
}
_ => 0,
})
.sum::<usize>(),
config.gossip_retransimission() as usize,
"not more then gossip_retransmission many messages get sent back"
);
}
#[test]
fn test_ignore_too_many_ihaves() {
let config = GossipsubConfigBuilder::default()
.max_ihave_messages(10)
.build()
.unwrap();
let (mut gs, _, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config.clone())
.create_network();
let peer = add_peer(&mut gs, &topics, false, false);
let mut seq = 0;
let messages: Vec<_> = (0..20).map(|_| random_message(&mut seq, &topics)).collect();
for raw_message in &messages {
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
gs.handle_ihave(
&peer,
vec![(topics[0].clone(), vec![config.message_id(&message)])],
);
}
let first_ten: HashSet<_> = messages
.iter()
.take(10)
.map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap())
.map(|m| config.message_id(&m))
.collect();
assert_eq!(
count_control_msgs(&gs, |p, action| match action {
GossipsubControlAction::IWant { message_ids } =>
p == &peer && {
assert_eq!(
message_ids.len(),
1,
"each iwant should have one message \
corresponding to one ihave"
);
assert!(first_ten.contains(&message_ids[0]));
true
},
_ => false,
}),
10,
"exactly the first ten ihaves should be processed and one iwant for each created"
);
gs.heartbeat();
for raw_message in messages[10..].iter() {
let message = &gs
.data_transform
.inbound_transform(raw_message.clone())
.unwrap();
gs.handle_ihave(
&peer,
vec![(topics[0].clone(), vec![config.message_id(&message)])],
);
}
assert_eq!(
count_control_msgs(&gs, |p, action| match action {
GossipsubControlAction::IWant { message_ids } =>
p == &peer && {
assert_eq!(
message_ids.len(),
1,
"each iwant should have one message \
corresponding to one ihave"
);
true
},
_ => false,
}),
20,
"all 20 should get sent"
);
}
#[test]
fn test_ignore_too_many_messages_in_ihave() {
let config = GossipsubConfigBuilder::default()
.max_ihave_messages(10)
.max_ihave_length(10)
.build()
.unwrap();
let (mut gs, _, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config.clone())
.create_network();
let peer = add_peer(&mut gs, &topics, false, false);
let mut seq = 0;
let message_ids: Vec<_> = (0..20)
.map(|_| random_message(&mut seq, &topics))
.map(|msg| gs.data_transform.inbound_transform(msg.clone()).unwrap())
.map(|msg| config.message_id(&msg))
.collect();
gs.handle_ihave(
&peer,
vec![(
topics[0].clone(),
message_ids[0..8].iter().cloned().collect(),
)],
);
gs.handle_ihave(
&peer,
vec![(
topics[0].clone(),
message_ids[0..12].iter().cloned().collect(),
)],
);
gs.handle_ihave(
&peer,
vec![(
topics[0].clone(),
message_ids[0..20].iter().cloned().collect(),
)],
);
let first_twelve: HashSet<_> = message_ids.iter().take(12).collect();
let mut sum = 0;
assert_eq!(
count_control_msgs(&gs, |p, action| match action {
GossipsubControlAction::IWant { message_ids } =>
p == &peer && {
assert!(first_twelve.is_superset(&message_ids.iter().collect()));
sum += message_ids.len();
true
},
_ => false,
}),
2,
"the third ihave should get ignored and no iwant sent"
);
assert_eq!(sum, 10, "exactly the first ten ihaves should be processed");
gs.heartbeat();
gs.handle_ihave(
&peer,
vec![(
topics[0].clone(),
message_ids[10..20].iter().cloned().collect(),
)],
);
let mut sum = 0;
assert_eq!(
count_control_msgs(&gs, |p, action| match action {
GossipsubControlAction::IWant { message_ids } =>
p == &peer && {
sum += message_ids.len();
true
},
_ => false,
}),
3
);
assert_eq!(sum, 20, "exactly 20 iwants should get sent");
}
#[test]
fn test_limit_number_of_message_ids_inside_ihave() {
let config = GossipsubConfigBuilder::default()
.max_ihave_messages(10)
.max_ihave_length(100)
.build()
.unwrap();
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config.clone())
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let p1 = add_peer(&mut gs, &topics, false, false);
let p2 = add_peer(&mut gs, &topics, false, false);
let mut seq = 0;
for _ in 0..200 {
gs.handle_received_message(random_message(&mut seq, &topics), &PeerId::random());
}
gs.emit_gossip();
let mut ihaves1 = HashSet::new();
let mut ihaves2 = HashSet::new();
assert_eq!(
count_control_msgs(&gs, |p, action| match action {
GossipsubControlAction::IHave { message_ids, .. } => {
if p == &p1 {
ihaves1 = message_ids.iter().cloned().collect();
true
} else if p == &p2 {
ihaves2 = message_ids.iter().cloned().collect();
true
} else {
false
}
}
_ => false,
}),
2,
"should have emitted one ihave to p1 and one to p2"
);
assert_eq!(
ihaves1.len(),
100,
"should have sent 100 message ids in ihave to p1"
);
assert_eq!(
ihaves2.len(),
100,
"should have sent 100 message ids in ihave to p2"
);
assert!(
ihaves1 != ihaves2,
"should have sent different random messages to p1 and p2 \
(this may fail with a probability < 10^-58"
);
assert!(
ihaves1.intersection(&ihaves2).into_iter().count() > 0,
"should have sent random messages with some common messages to p1 and p2 \
(this may fail with a probability < 10^-58"
);
}
#[test]
fn test_iwant_penalties() {
let config = GossipsubConfigBuilder::default()
.iwant_followup_time(Duration::from_secs(4))
.build()
.unwrap();
let mut peer_score_params = PeerScoreParams::default();
peer_score_params.behaviour_penalty_weight = -1.0;
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(config.mesh_n_high())
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config.clone())
.explicit(0)
.outbound(0)
.scoring(Some((peer_score_params, PeerScoreThresholds::default())))
.create_network();
for peer in peers {
gs.handle_graft(&peer, topics.clone());
}
let other_peers: Vec<_> = (0..100)
.map(|_| add_peer(&mut gs, &topics, false, false))
.collect();
let mut first_messages = Vec::new();
let mut second_messages = Vec::new();
let mut seq = 0;
for peer in &other_peers {
for _ in 0..2 {
let msg1 = random_message(&mut seq, &topics);
let msg2 = random_message(&mut seq, &topics);
let message1 = &gs.data_transform.inbound_transform(msg1.clone()).unwrap();
let message2 = &gs.data_transform.inbound_transform(msg2.clone()).unwrap();
first_messages.push(msg1.clone());
second_messages.push(msg2.clone());
gs.handle_ihave(
peer,
vec![(
topics[0].clone(),
vec![config.message_id(&message1), config.message_id(&message2)],
)],
);
}
}
for message in first_messages {
gs.handle_received_message(message.clone(), &PeerId::random());
}
gs.heartbeat();
for peer in &other_peers {
assert_eq!(gs.peer_score.as_ref().unwrap().0.score(peer), 0.0);
}
for message in second_messages.iter().take(20) {
gs.handle_received_message(message.clone(), &PeerId::random());
}
sleep(Duration::from_secs(4));
gs.heartbeat();
for message in second_messages {
gs.handle_received_message(message.clone(), &PeerId::random());
}
gs.heartbeat();
let mut not_penalized = 0;
let mut single_penalized = 0;
let mut double_penalized = 0;
for (i, peer) in other_peers.iter().enumerate() {
let score = gs.peer_score.as_ref().unwrap().0.score(peer);
if score == 0.0 {
not_penalized += 1;
} else if score == -1.0 {
assert!(i > 9);
single_penalized += 1;
} else if score == -4.0 {
assert!(i > 9);
double_penalized += 1
} else {
assert!(false, "Invalid score of peer")
}
}
assert!(not_penalized > 10);
assert!(single_penalized > 0);
assert!(double_penalized > 0);
}
#[test]
fn test_publish_to_floodsub_peers_without_flood_publish() {
let config = GossipsubConfigBuilder::default()
.flood_publish(false)
.build()
.unwrap();
let (mut gs, _, topics) = inject_nodes1()
.peer_no(config.mesh_n_low() - 1)
.topics(vec!["test".into()])
.to_subscribe(false)
.gs_config(config)
.create_network();
let p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
false,
false,
Multiaddr::empty(),
Some(PeerKind::Floodsub),
);
let p2 =
add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None);
assert!(!gs.mesh[&topics[0]].contains(&p1) && !gs.mesh[&topics[0]].contains(&p2));
let publish_data = vec![0; 42];
gs.publish(Topic::new("test"), publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { peer_id, event, .. } => {
if peer_id == &p1 || peer_id == &p2 {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push(s.clone());
}
}
collected_publish
}
_ => collected_publish,
});
assert_eq!(
publishes.len(),
2,
"Should send a publish message to all floodsub peers"
);
}
#[test]
fn test_do_not_use_floodsub_in_fanout() {
let config = GossipsubConfigBuilder::default()
.flood_publish(false)
.build()
.unwrap();
let (mut gs, _, _) = inject_nodes1()
.peer_no(config.mesh_n_low() - 1)
.topics(Vec::new())
.to_subscribe(false)
.gs_config(config)
.create_network();
let topic = Topic::new("test");
let topics = vec![topic.hash()];
let p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
false,
false,
Multiaddr::empty(),
Some(PeerKind::Floodsub),
);
let p2 =
add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None);
let publish_data = vec![0; 42];
gs.publish(Topic::new("test"), publish_data).unwrap();
let publishes = gs
.events
.iter()
.fold(vec![], |mut collected_publish, e| match e {
NetworkBehaviourAction::NotifyHandler { peer_id, event, .. } => {
if peer_id == &p1 || peer_id == &p2 {
let event = proto_to_message(event);
for s in &event.messages {
collected_publish.push(s.clone());
}
}
collected_publish
}
_ => collected_publish,
});
assert_eq!(
publishes.len(),
2,
"Should send a publish message to all floodsub peers"
);
assert!(
!gs.fanout[&topics[0]].contains(&p1) && !gs.fanout[&topics[0]].contains(&p2),
"Floodsub peers are not allowed in fanout"
);
}
#[test]
fn test_dont_add_floodsub_peers_to_mesh_on_join() {
let (mut gs, _, _) = inject_nodes1()
.peer_no(0)
.topics(Vec::new())
.to_subscribe(false)
.create_network();
let topic = Topic::new("test");
let topics = vec![topic.hash()];
let _p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
false,
false,
Multiaddr::empty(),
Some(PeerKind::Floodsub),
);
let _p2 =
add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None);
gs.join(&topics[0]);
assert!(
gs.mesh[&topics[0]].is_empty(),
"Floodsub peers should not get added to mesh"
);
}
#[test]
fn test_dont_send_px_to_old_gossipsub_peers() {
let (mut gs, _, topics) = inject_nodes1()
.peer_no(0)
.topics(vec!["test".into()])
.to_subscribe(false)
.create_network();
let p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
false,
false,
Multiaddr::empty(),
Some(PeerKind::Gossipsub),
);
gs.send_graft_prune(
HashMap::new(),
vec![(p1.clone(), topics.clone())].into_iter().collect(),
HashSet::new(),
);
assert_eq!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Prune { peers: px, .. } => !px.is_empty(),
_ => false,
}),
0,
"Should not send px to floodsub peers"
);
}
#[test]
fn test_dont_send_floodsub_peers_in_px() {
let (mut gs, peers, topics) = inject_nodes1()
.peer_no(1)
.topics(vec!["test".into()])
.to_subscribe(true)
.create_network();
let _p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
false,
false,
Multiaddr::empty(),
Some(PeerKind::Floodsub),
);
let _p2 =
add_peer_with_addr_and_kind(&mut gs, &topics, false, false, Multiaddr::empty(), None);
gs.send_graft_prune(
HashMap::new(),
vec![(peers[0].clone(), topics.clone())]
.into_iter()
.collect(),
HashSet::new(),
);
assert_eq!(
count_control_msgs(&gs, |_, m| match m {
GossipsubControlAction::Prune { peers: px, .. } => !px.is_empty(),
_ => false,
}),
0,
"Should not include floodsub peers in px"
);
}
#[test]
fn test_dont_add_floodsub_peers_to_mesh_in_heartbeat() {
let (mut gs, _, topics) = inject_nodes1()
.peer_no(0)
.topics(vec!["test".into()])
.to_subscribe(false)
.create_network();
let _p1 = add_peer_with_addr_and_kind(
&mut gs,
&topics,
true,
false,
Multiaddr::empty(),
Some(PeerKind::Floodsub),
);
let _p2 =
add_peer_with_addr_and_kind(&mut gs, &topics, true, false, Multiaddr::empty(), None);
gs.heartbeat();
assert!(
gs.mesh[&topics[0]].is_empty(),
"Floodsub peers should not get added to mesh"
);
}
#[test]
fn test_public_api() {
let (gs, peers, topic_hashes) = inject_nodes1()
.peer_no(4)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.create_network();
let peers = peers.into_iter().collect::<BTreeSet<_>>();
assert_eq!(
gs.topics().cloned().collect::<Vec<_>>(),
topic_hashes,
"Expected topics to match registered topic."
);
assert_eq!(
gs.mesh_peers(&TopicHash::from_raw("topic1"))
.cloned()
.collect::<BTreeSet<_>>(),
peers,
"Expected peers for a registered topic to contain all peers."
);
assert_eq!(
gs.all_mesh_peers().cloned().collect::<BTreeSet<_>>(),
peers,
"Expected all_peers to contain all peers."
);
}
#[test]
fn test_msg_id_fn_only_called_once_with_fast_message_ids() {
struct Pointers {
slow_counter: u32,
fast_counter: u32,
};
let mut counters = Pointers {
slow_counter: 0,
fast_counter: 0,
};
let counters_pointer: *mut Pointers = &mut counters;
let counters_address = counters_pointer as u64;
macro_rules! get_counters_pointer {
($m: expr) => {{
let mut address_bytes: [u8; 8] = Default::default();
address_bytes.copy_from_slice($m.as_slice());
let address = u64::from_be_bytes(address_bytes);
address as *mut Pointers
}};
}
macro_rules! get_counters_and_hash {
($m: expr) => {{
let mut hasher = DefaultHasher::new();
$m.hash(&mut hasher);
let id = hasher.finish().to_be_bytes().into();
(id, get_counters_pointer!($m))
}};
}
let message_id_fn = |m: &GossipsubMessage| -> MessageId {
let (mut id, mut counters_pointer): (MessageId, *mut Pointers) =
get_counters_and_hash!(&m.data);
unsafe {
(*counters_pointer).slow_counter += 1;
}
id.0.reverse();
id
};
let fast_message_id_fn = |m: &RawGossipsubMessage| -> FastMessageId {
let (id, mut counters_pointer) = get_counters_and_hash!(&m.data);
unsafe {
(*counters_pointer).fast_counter += 1;
}
id
};
let config = GossipsubConfigBuilder::default()
.message_id_fn(message_id_fn)
.fast_message_id_fn(fast_message_id_fn)
.build()
.unwrap();
let (mut gs, _, topic_hashes) = inject_nodes1()
.peer_no(0)
.topics(vec![String::from("topic1")])
.to_subscribe(true)
.gs_config(config)
.create_network();
let message = RawGossipsubMessage {
source: None,
data: counters_address.to_be_bytes().to_vec(),
sequence_number: None,
topic: topic_hashes[0].clone(),
signature: None,
key: None,
validated: true,
};
for _ in 0..5 {
gs.handle_received_message(message.clone(), &PeerId::random());
}
assert!(counters.fast_counter <= 5);
assert_eq!(counters.slow_counter, 1);
}
#[test]
fn test_subscribe_to_invalid_topic() {
let t1 = Topic::new("t1");
let t2 = Topic::new("t2");
let (mut gs, _, _) = inject_nodes::<IdentityTransform, _>()
.subscription_filter(WhitelistSubscriptionFilter(
vec![t1.hash()].into_iter().collect(),
))
.to_subscribe(false)
.create_network();
assert!(gs.subscribe(&t1).is_ok());
assert!(gs.subscribe(&t2).is_err());
}
#[test]
fn test_subscribe_and_graft_with_negative_score() {
let (mut gs1, _, topic_hashes) = inject_nodes1()
.topics(vec!["test".into()])
.scoring(Some((
PeerScoreParams::default(),
PeerScoreThresholds::default(),
)))
.create_network();
let (mut gs2, _, _) = inject_nodes1().create_network();
let connection_id = ConnectionId::new(0);
let topic = Topic::new("test");
let p2 = add_peer(&mut gs1, &Vec::new(), true, false);
let p1 = add_peer(&mut gs2, &topic_hashes, false, false);
gs1.peer_score.as_mut().unwrap().0.add_penalty(&p2, 1);
let original_score = gs1.peer_score.as_ref().unwrap().0.score(&p2);
gs2.subscribe(&topic).unwrap();
let forward_messages_to_p1 = |gs1: &mut Gossipsub<_, _>, gs2: &mut Gossipsub<_, _>| {
let messages_to_p1 = gs2.events.drain(..).filter_map(|e| match e {
NetworkBehaviourAction::NotifyHandler { peer_id, event, .. } => {
if &peer_id == &p1 {
Some(event)
} else {
None
}
}
_ => None,
});
for message in messages_to_p1 {
gs1.inject_event(
p2.clone(),
connection_id,
HandlerEvent::Message {
rpc: proto_to_message(&message),
invalid_messages: vec![],
},
);
}
};
forward_messages_to_p1(&mut gs1, &mut gs2);
gs1.heartbeat();
gs2.heartbeat();
forward_messages_to_p1(&mut gs1, &mut gs2);
assert!(gs1.peer_score.as_ref().unwrap().0.score(&p2) >= original_score);
}
}