cy6erninja / p2p-tic-tac-toe-rs

Noob implementation of cli p2p tictactoe in Rust
0 stars 0 forks source link

Plan initial implementation #1

Closed cy6erninja closed 1 year ago

cy6erninja commented 1 year ago

Before investigation

Plan:

cy6erninja commented 1 year ago

Trying out an approach described here https://simonwillison.net/2022/Nov/26/productivity/

cy6erninja commented 1 year ago

Investigating the following:

Tic Tac Toe functionality includes:

Further improvements:

cy6erninja commented 1 year ago

What dependencies do we need for the start and why?

cy6erninja commented 1 year ago

LIBP2P

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/ .

Addressing

Multiaddresses allow addresses in format: /ip4/7.7.7.7/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N It is a combination of location addressing and identity addressing

AutoNAT

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.

Circuit Relay

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

Hole Punching

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.

Publish/Subscribe

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.

cy6erninja commented 1 year ago

What subsequent parts a P2P app consists of?

cy6erninja commented 1 year ago

Breaking down each part of P2P further

PeerId

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(())
}

Topic

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');
...

Move struct

Just a simple struct that will hold info about the move.

#[derive(Debug, Serialize, Deserialize)]
struct Move {
    squareCode: &str
}

Messages

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
}

Channel

~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

Transport

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.

Network Behaviour

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));
...

Swarm

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");
cy6erninja commented 1 year ago

Two examples of polling the swarm

From the official example

 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;
            }
        }
    }));

From the logrocket article

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"))),
            }
        };
        ...
    }
cy6erninja commented 1 year ago

Concepts to learn more about

  1. Play with futures
  2. Play with tokio . Tokio vs Futures vs Other Async
  3. Why in fn main() -> Result<(), Box<dyn Error>> { return type error is wrapped like this?
  4. Play with constructing different transports.
  5. Read more about multiplexing, starting at crate::core::muxing and yamux.
  6. Read more about NAT
  7. Read more about mdns or mdns
  8. Figure out what is mpsc(such structs are present in futures crate and used in logrocket article)
  9. Play with constructing a NetworkBehaviour)
  10. Play with constructing Swarm
  11. Dive more into pubsub and play with different pubsub mechanisms