Closed cy6erninja closed 1 year ago
Trying out an approach described here https://simonwillison.net/2022/Nov/26/productivity/
Investigating the following:
Tic Tac Toe functionality includes:
Further improvements:
What dependencies do we need for the start and why?
In order to better understand what libp2p offers and maybe pick some more knowledge on P2P in general, I will look through the suggested article https://docs.libp2p.io/concepts/ .
Multiaddresses allow addresses in format: /ip4/7.7.7.7/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N It is a combination of location addressing and identity addressing
https://docs.libp2p.io/concepts/nat/autonat/ Let's nodes request a dial-in from other peers to verify if receiving incomming connection works well.
Of a peer is unable to access the other node in the network because it has no public access of there some other barriers, it is possible to use a relay node using circuit relay protocol. The relay node would then propagate the message to the destination. The process is not anonymous. Everybody is aware of a relay presence and the data is encrypted. Example of a full address with relay included: /ip4/7.7.7.7/tcp/55555/p2p/QmRelay/p2p-circuit/p2p/QmAlice
Things start getting complicated. In general, hole punching means that if one peer can not dial in to another, it looks for peers who can and establishes the connection using that middleware peer as a relay.
Pub/Sub seems to be a bit different from a usual approach. It seems that the pub/sub that I know is more focused on a producer and the producer is able to issue several types of messages, so subscribers subscribe to those message types. In a p2p world, judging solely from the libp2p doc, pub/sub if more focused on a topic that is being produced rather than on a producer itself. It fits the decentralised nature of P2P perfectly. As the article says, the topic is something that is being shared between peers, being that a chat where all the members write to and receive messages from OR it may be some file sharing functionality where topics are pieces of files.
Peer discovery methods to read about:
There are 2 types of peering between the nodes: full message and metadata-only. Full message peers have a desired amount of peers they want to be connected too. It allows the network to balance between bandwidth load and stability of a chain. Metadata-only peers help supporting the network of full message peers by gossiping what messages are available and doing other support stuff.
What subsequent parts a P2P app consists of?
From the official example:
use libp2p::{identity, PeerId};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
Ok(())
}
We need a topic which will be a central point of communication between peers. As mentioned in the article, Topic is a concept from floodsub module of libp2p.
use libp2p::floodsub::Topic;
...
let topic = Topic::new('tic-tac-toe');
...
Just a simple struct that will hold info about the move.
#[derive(Debug, Serialize, Deserialize)]
struct Move {
squareCode: &str
}
Just following the article's logic, I will create wrappers around data we send. Not sure if it is really needed for this app, but will create anyway. For now I need only one type of message to actually issue one single move:
#[derive(Debug, Serialize, Deserialize)]
struct MoveMessage {
move: Move
}
~What is the difference between channel and transport? What is the point of creating a channel?~ Probably channels also come from futures crate and async programming in general
We clearly need transport in order to send bits back and forth. I will go with a plain transport from libp2p tutorial and maybe upgrade later to something more sophisticated. I will definitely need to read more about multiplexing, starting at crate::core::muxing and yamux.
use libp2p::development_transport;
...
let transport = development_transport(keypair);
...
The original example contains block_on statement also, but I will get to that when I actually write down the code.
This part is a bit confusing, it is likely that i need a default Floodsub behaviour, but we will start with Ping behaviour and later replace it.
use libp2p::ping::{Ping, PingConfig};
...
let behaviour = Ping::new(PingConfig::new().with_keep_alive(true));
...
The doc says "Controls a state of the network and the way it should behave." Swarm is the thing that executes everything, using transport to pass data and applies Network Behaviour. So if we take a concept of a simple web server, swarm is like an infinite loop that has logic of accepting connections. The code is taken from libp2p example. I'm intentionally making it not mutable for now in order to fix this when I
use libp2p::swarm::Swarm;
...
let swarm = Swarm::new(transport, network_behaviour, local_peer_id);
// Listen on all the interfaces
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
Or from logrocket article
Swarm::listen_on(
&mut swarm,
"/ip4/0.0.0.0/tcp/0"
.parse()
.expect("can get a local socket"),
)
.expect("swarm can be started");
let mut listening = false;
block_on(future::poll_fn(move |cx| loop {
match swarm.poll_next_unpin(cx) {
Poll::Ready(Some(event)) => println!("{:?}", event),
Poll::Ready(None) => return Poll::Ready(()),
Poll::Pending => {
if !listening {
for addr in Swarm::listeners(&swarm) {
println!("Listening on {}", addr);
listening = true;
}
}
return Poll::Pending;
}
}
}));
loop {
let evt = {
tokio::select! {
line = stdin.next_line() => Some(EventType::Input(line.expect("can get line").expect("can read line from stdin"))),
event = swarm.next() => {
info!("Unhandled Swarm Event: {:?}", event);
None
},
response = response_rcv.recv() => Some(EventType::Response(response.expect("response exists"))),
}
};
...
}
fn main() -> Result<(), Box<dyn Error>> {
return type error is wrapped like this?
Before investigation
Plan: