tatami 0.1.2

A library for creating satellites and interacting with Tatami protocols.
Documentation
#[cfg(feature = "mdns")]
use libp2p::mdns::{MdnsConfig, TokioMdns};
use libp2p::{
	gossipsub::{
		Gossipsub, GossipsubConfigBuilder, GossipsubMessage, MessageAuthenticity, MessageId,
		ValidationMode,
	},
	identity::Keypair,
	kad::{store::MemoryStore, Kademlia, KademliaConfig},
	Multiaddr, NetworkBehaviour, PeerId,
};
use std::hash::Hash;
use std::hash::Hasher;
use std::{collections::hash_map::DefaultHasher, str::FromStr, time::Duration};

use super::event::SatelliteSwamEvent;
use crate::{errors::TatamiError, Satellite};

// "Bootnodes" to bootstrap connections
const BOOTNODES: [&str; 4] = [
	"QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
	"QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
	"QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
	"QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
];

#[derive(NetworkBehaviour)]
#[behaviour(out_event = "SatelliteSwamEvent")]
pub struct SwarmBehaviour {
	pub kademlia: Kademlia<MemoryStore>,
	#[cfg(feature = "mdns")]
	pub mdns: TokioMdns,
	pub gossipsub: Gossipsub,
}

impl SwarmBehaviour {
	pub async fn new(local_key: Keypair, bootstrap: bool) -> Result<Self, TatamiError> {
		let local_public_key = &local_key.public();
		let local_peer_id = PeerId::from_public_key(&local_public_key);
		Ok(Self {
			kademlia: Self::kad(local_peer_id, bootstrap)?,
			#[cfg(feature = "mdns")]
			mdns: Self::mdns().await?,
			gossipsub: Self::gossipsub(local_key)?,
		})
	}

	#[cfg(feature = "mdns")]
	async fn mdns() -> Result<TokioMdns, TatamiError> {
		TokioMdns::new(MdnsConfig::default())
			.await
			.or(Err(TatamiError::Generic))
	}

	fn kad(local_peer_id: PeerId, bootstrap: bool) -> Result<Kademlia<MemoryStore>, TatamiError> {
		let store = MemoryStore::new(local_peer_id);
		let mut cfg = KademliaConfig::default();
		cfg.set_query_timeout(Duration::from_secs(5 * 60));
		let mut kad = Kademlia::with_config(local_peer_id, store, cfg);

		if bootstrap {
			// Add the bootnodes to the local routing table. `libp2p-dns` built
			// into the `transport` resolves the `dnsaddr` when Kademlia tries
			// to dial these nodes.
			let bootaddr = Multiaddr::from_str("/dnsaddr/bootstrap.libp2p.io").unwrap();
			for peer in &BOOTNODES {
				kad.add_address(&PeerId::from_str(peer).unwrap(), bootaddr.clone());
			}
		}

		Ok(kad)
	}

	fn gossipsub(local_key: Keypair) -> Result<Gossipsub, TatamiError> {
		// To content-address message, we can take the hash of message and use it as an ID.
		let message_id_fn = |message: &GossipsubMessage| {
			let mut s = DefaultHasher::new();
			message.data.hash(&mut s);
			MessageId::from(s.finish().to_string())
		};

		// Set a custom gossipsub
		let gossipsub_config = GossipsubConfigBuilder::default()
			.heartbeat_interval(Duration::from_secs(10)) // This is set to aid debugging by not cluttering the log space
			.validation_mode(ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing)
			.message_id_fn(message_id_fn) // content-address messages. No two messages of the
			// same content will be propagated.
			.build()
			.expect("Valid config");

		let mut gossipsub = // build a gossipsub network behaviour
		Gossipsub::new(MessageAuthenticity::Signed(local_key), gossipsub_config)
			.or(Err(TatamiError::Generic))?;

		gossipsub.subscribe(&Satellite::topic()).unwrap();

		Ok(gossipsub)
	}
}