ghost-walker/src/walker.rs
Uncle Stretch b3cd0d6386
initial draft for the ghost-walker
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2024-12-26 14:41:16 +03:00

313 lines
11 KiB
Rust

use std::{
collections::{hash_map::Entry, HashMap, HashSet},
time::Duration,
error::Error,
};
use futures::StreamExt;
use codec::Decode;
use ip_network::IpNetwork;
use primitive_types::H256;
use libp2p::{
identify::Info, identity, kad::{
Event as KademliaEvent, GetClosestPeersError, GetClosestPeersOk,
QueryId, QueryResult,
},
multiaddr::Protocol,
swarm::{Config as SwarmConfig, SwarmEvent},
Multiaddr, PeerId, Swarm
};
use crate::{
locator::Locator,
p2p::{notifications::behaviour::NotificationsToSwarm, BehaviourEvent},
};
use super::p2p::{
discovery::DiscoveryBuilder,
peer_behaviour::{PeerInfoEvent, PeerBehaviour},
transport::{TransportBuilder, MIB},
notifications::{
behaviour::{Notifications, ProtocolsData},
messages::ProtocolRole,
},
Behaviour,
};
pub struct Walker {
genesis: String,
timeout: Duration,
swarm: Swarm<Behaviour>,
queries: HashSet<QueryId>,
discovered_with_address: HashMap<PeerId, HashSet<Multiaddr>>,
peer_details: HashMap<PeerId, Info>,
peer_role: HashMap<PeerId, ProtocolRole>,
dialed_peers: HashMap<PeerId, usize>,
}
impl Walker {
pub fn new() -> WalkerBuilder {
WalkerBuilder::default()
}
fn insert_queries(&mut self, num: usize) {
for _ in 0..num {
self.queries.insert(
self.swarm
.behaviour_mut()
.discovery
.get_closest_peers(PeerId::random()));
}
}
fn dialed_peer(&mut self, peer_id: Option<PeerId>) {
let Some(peer_id) = peer_id else { return };
self.dialed_peers
.entry(peer_id)
.and_modify(|num| *num += 1)
.or_insert(0);
}
async fn inner_walk(&mut self) {
self.insert_queries(128);
let mut previous_tracing_time = std::time::Instant::now();
loop {
let event = self.swarm.select_next_some().await;
match event {
SwarmEvent::Dialing { peer_id, .. } => self.dialed_peer(peer_id),
SwarmEvent::Behaviour(BehaviourEvent::Discovery(event)) => match event {
KademliaEvent::OutboundQueryProgressed {
id,
result: QueryResult::GetClosestPeers(result),
..
} => {
self.queries.remove(&id);
let peers = match result {
Ok(GetClosestPeersOk { peers, ..}) => peers,
Err(GetClosestPeersError::Timeout { peers, .. }) => peers
};
let num_discovered = peers.len();
let now = std::time::Instant::now();
if now.duration_since(previous_tracing_time) > Duration::from_secs(10) {
previous_tracing_time = now;
tracing::info!("...Discovery in progress last query #{num_discovered}");
}
if self.queries.is_empty() {
self.insert_queries(128);
}
}
KademliaEvent::RoutingUpdated { peer, addresses, .. } => {
match self.discovered_with_address.entry(peer) {
Entry::Occupied(mut occupied) => {
occupied.get_mut().extend(addresses.into_vec());
},
Entry::Vacant(vacant) => {
vacant.insert(addresses.iter().cloned().collect());
},
};
}
KademliaEvent::RoutablePeer { peer, address } | KademliaEvent::PendingRoutablePeer { peer, address } => {
match self.discovered_with_address.entry(peer) {
Entry::Occupied(mut occupied) => {
occupied.get_mut().insert(address);
},
Entry::Vacant(vacant) => {
let mut set = HashSet::new();
set.insert(address);
vacant.insert(set);
},
};
}
_ => (),
},
SwarmEvent::Behaviour(BehaviourEvent::PeerInfo(info_event)) => match info_event {
PeerInfoEvent::Identified { peer_id, info } => {
tracing::debug!("Identified peer_id={:?} info={:?}", peer_id, info);
self.peer_details.insert(peer_id, info);
}
},
SwarmEvent::Behaviour(BehaviourEvent::Notifications(
NotificationsToSwarm::CustomProtocolOpen {
peer_id,
index,
received_handshake,
inbound,
..
}
)) => {
if let Ok(role) = ProtocolRole::decode(&mut &received_handshake[..]) {
tracing::debug!("Identified peer_id={:?} role={:?}", peer_id, role);
self.peer_role.insert(peer_id, role);
}
tracing::debug!(
"Protocol open peer={:?} index={:?} handshake={:?} inbound={:?}",
peer_id,
index,
received_handshake,
inbound,
);
}
_ => (),
}
}
}
fn is_public_adress(&self, addr: &Multiaddr) -> bool {
let ip = match addr.iter().next() {
Some(Protocol::Ip4(ip)) => IpNetwork::from(ip),
Some(Protocol::Ip6(ip)) => IpNetwork::from(ip),
Some(Protocol::Dns(_)) | Some(Protocol::Dns4(_)) | Some(Protocol::Dns6(_)) => return true,
_ => return false,
};
ip.is_global()
}
pub async fn walk_around(&mut self) -> Result<(), Box<dyn Error>> {
let _ = tokio::time::timeout(self.timeout, self.inner_walk()).await;
println!("Number of dialed peers: {}", self.dialed_peers.len());
println!("Total peers discovered: {}", self.discovered_with_address.len());
println!("Peers with local iden.: {}", self.peer_details.len());
let infos: HashMap<_, _> = self
.peer_details
.iter()
.filter(|(_, info)| {
info.protocols
.iter()
.any(|stream_proto| stream_proto.as_ref().contains(&self.genesis))
})
.collect();
println!("Support correct genesis {}: {}", &self.genesis, infos.len());
let peers_with_public: HashMap<_, _> = infos
.iter()
.filter(|(_, info)| info.listen_addrs
.iter()
.any(|a| self.is_public_adress(&a)))
.collect();
println!("Peers with public address: {}", peers_with_public.len());
println!("Peers with private address: {}", infos.len() - peers_with_public.len());
println!("Peers with associated role: {}", self.peer_role.len());
Locator::new(&infos).locate_peers().await;
Ok(())
}
}
pub struct WalkerBuilder {
genesis: String,
bootnodes: Vec<String>,
timeout: Duration,
}
impl Default for WalkerBuilder {
fn default() -> Self {
Self {
genesis: Default::default(),
bootnodes: Default::default(),
timeout: Default::default(),
}
}
}
impl WalkerBuilder {
fn create_swarm(&self) -> Result<Swarm<Behaviour>, Box<dyn Error>> {
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
tracing::info!("Local peer Id {:?}", local_peer_id);
let bootnodes = self.bootnodes
.iter()
.map(|bootnode| {
let parts: Vec<_> = bootnode.split('/').collect();
let peer = parts.last().expect("Bootnode peer should be valid; qed");
let multiaddress: Multiaddr = bootnode
.parse()
.expect("Bootnode multiaddress should be valid; qed");
let peer_id: PeerId = peer
.parse()
.expect("Bootnode should have valid peer ID; qed");
tracing::info!("Bootnode peer={:?}", peer_id);
(peer_id, multiaddress)
})
.collect::<Vec<_>>();
let protocol_data = ProtocolsData {
genesis_hash: H256::from_slice(hex::decode(self.genesis.clone())?.as_slice()),
node_role: ProtocolRole::FullNode,
};
let mut swarm: Swarm<Behaviour> = {
let transport = TransportBuilder::new()
.yamux_maximum_buffer_size(256 * MIB)
.build(local_key.clone());
let discovery = DiscoveryBuilder::new()
.record_ttl(Some(Duration::from_secs(0)))
.provider_ttl(Some(Duration::from_secs(0)))
.query_timeout(Duration::from_secs(5 * 60))
.build(local_peer_id, &self.genesis);
let peer_info = PeerBehaviour::new(local_key.public());
let notifications = Notifications::new(protocol_data);
let behavior = Behaviour {
notifications,
peer_info,
discovery,
};
let config = SwarmConfig::with_tokio_executor();
Swarm::new(transport, behavior, local_peer_id, config)
};
for (peer, multiaddress) in &bootnodes {
swarm
.behaviour_mut()
.discovery
.add_address(peer, multiaddress.clone());
}
Ok(swarm)
}
pub fn with_genesis(mut self, genesis: String) -> Self {
self.genesis = genesis.trim_start_matches("0x").to_string();
self
}
pub fn with_bootnodes(mut self, bootnodes: Vec<String>) -> Self {
self.bootnodes = bootnodes;
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn build(&self) -> Result<Walker, Box<dyn Error>> {
let swarm = self.create_swarm()?;
Ok(Walker {
swarm,
genesis: self.genesis.clone(),
timeout: self.timeout,
queries: HashSet::with_capacity(1024),
discovered_with_address: HashMap::with_capacity(1024),
peer_details: HashMap::with_capacity(1024),
peer_role: HashMap::with_capacity(1024),
dialed_peers: HashMap::with_capacity(1024),
})
}
}