Skip to content

Commit

Permalink
Merge pull request #47 from iotaledger/feat-p2p
Browse files Browse the repository at this point in the history
Feat p2p
  • Loading branch information
elenaf9 authored Dec 8, 2020
2 parents 0c68dd0 + e209893 commit e7f9357
Show file tree
Hide file tree
Showing 17 changed files with 2,382 additions and 16 deletions.
4 changes: 4 additions & 0 deletions .changes/add-libp2p-communication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
"stronghold-communication": minor
---
Introduction of the libp2p communication subsystem for Stronghold.
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[workspace]
members = [
"engine",
"engine/vault",
"engine/primitives",
"engine/random",
"engine/crypto",
"engine/snapshot",
"runtime",
"engine",
"engine/vault",
"engine/primitives",
"engine/random",
"engine/crypto",
"engine/snapshot",
"communication",
"runtime",
]
exclude = [
"products/commandline",
Expand Down
25 changes: 22 additions & 3 deletions communication/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
[package]
name = "stronghold-communication"
version = "0.1.0"
authors = ["Daniel Thompson-Yvetot <daniel.yvetot@iota.org>"]
authors = ["Elena Frank <elena.frank@iota.org>"]
edition = "2018"
license = "Apache-2.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
readme = "README.md"

[dependencies]
async-std = "1.6.2"
async-trait = "0.1.40"
bytes = "0.5.6"
clap = { version = "3.0.0-beta.1", features = ["yaml"] }
futures = "0.3.1"
libp2p = {version = "0.28.1", default-features = false, features = ["dns", "identify", "mdns-async-std", "mplex", "noise", "request-response", "tcp-async-std", "yamux", "websocket"]}
prost = {version = "0.6.1", default-features = false, features = ["prost-derive"] }
regex = "1.3.9"
thiserror = "1.0.21"
serde = { version = "1.0.117", default-features = false, features = ["alloc", "derive"] }
serde_json = { version = "1.0.59", default-features = false, features = ["alloc"] }
riker = "0.4"

[features]
default = ["mdns"]

mdns = []

[build-dependencies]
prost-build = "0.6.1"
42 changes: 42 additions & 0 deletions communication/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## Introduction

This library enables strongholds on different devices and in different networks to communicate with each other.
The main basis for its functionality is the [rust-libp2p](https://github.com/libp2p/rust-libp2p) library, which is a system of protocols, specifications and
libraries that enable the development of peer-to-peer network applications (https://libp2p.io/).

Libp2p was originally the network protocol of IPFS and has evolved into a modular system with implementations in
Node.js, Go and Rust. It is important to note that at the current status, the Rust implementation doesn't have all features
yet and especially peer discovery in different networks, NAT Traversal and Firewalls pose a problem that we solved
for stronghold by using a mailbox concept that is described later.

## Transport and the Swarm

Libp2p uses the `transport` as the lowest layer, that is responsible for sending and receiving data over a network.
The current rust implementation supports tcp and websockets, and apart from that provides the option to upgrade a
connection with protocols for multiplexing and authentication.

The second important concept of libp2p is its `Swarm` (in newer implementations and documents also called `Switch`).
The swarm is responsible for negotiating protocols, managing transports and sending and receiving messages via different
protocols. It is possible to combine different protocols into a so called `NetworkBehaviour`, which is what this library is doing.
Stronghold-communication uses multicast DNS (mDNS) for peer discovery in a local network and the RequestResponse protocol in order to send / receive
custom messages and parse them.

## Multiplexing and Noise-encryption

The transport of stronghold-communication is upgraded with yamux for multiplexing and the noise protocol, this noise protocol uses the XX-Handshake and ensures authentification and encryption.

## Stronghold-Communication

Similar to the swarm in libp2p, the stronghold-communication creates the `P2PNetworkBehaviour` struct that manages sending messages and reacting upon the outcome of the operation.
Upon creating a new instance, a transport is created and upgraded, and combined with a the P2PNetworkBehaviour into a ExpandedSwarm. This Swarm is returned to the caller and serves as entrypoint for all communication to other peers. It implements methods for listening to the swarm, sending outbound messages, and manually adding and dialing peers. Incoming `P2PEvent` can be handled by polling from the swarm, e.g. via the `poll_next_unpin` method.
Due to libp2ps concept of `multiaddresses`, the swarm has multiple listening addresses that encode different addressing schemes for different
protocols. Apart from IPv4 and IPv6 Addresses, these multiaddresses can also be dns addresses, which is relevant if a peer is listening
to such an address on a server. The listed multiaddresses are only the ones within the same local network, but if port forwarding was configured,
the local /ip4/my-local-address/tcp/12345 Address can be replaced by the public one or by `/dns/my.public.server.address/tcp/12345`, where the
`/tcp/12345` part describes the port.

## Mailbox Concept

Since not all peers can be dialed directly e.g. because they are behind a firewall, stronghold-communication includes methods for using
a mailbox. The mailbox is a peer running on a server with public IP Address that can be reached by all other peers. If can be
used to deposit records for unavailable remote peers by sending a `Request::PutRecord` message with the record to the mailbox, and that can then return the Records to remote peers upon receiving a `Request::GetRecord` request.
2 changes: 2 additions & 0 deletions communication/config/riker.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[log]
level = "info"
25 changes: 25 additions & 0 deletions communication/examples/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2020 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

FROM rust:latest as build
RUN USER=root cargo new --bin stronghold-communication
WORKDIR /stronghold-communication

COPY ./Cargo.toml ./Cargo.toml

# cache dependencies
RUN cargo build --release
RUN rm src/*.rs
COPY ./src ./src
COPY ./examples ./examples
RUN cargo clean

# build mailbox
RUN cargo build --example mailbox
FROM rust:latest
WORKDIR /app

# copy the build artifact from the build stage
COPY --from=build /stronghold-communication/target/debug/examples/mailbox .
ENTRYPOINT ["/app/mailbox"]
CMD ["start-mailbox"]
77 changes: 77 additions & 0 deletions communication/examples/actor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2020 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use core::time::Duration;
use libp2p::core::identity::Keypair;
use riker::actors::*;
use serde::{Deserialize, Serialize};
use stronghold_communication::{
actor::{CommunicationActor, CommunicationEvent},
behaviour::message::P2PReqResEvent,
};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Request {
Ping,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Response {
Pong,
}

struct TestActor {
chan: ChannelRef<CommunicationEvent<Request, Response>>,
}

impl ActorFactoryArgs<ChannelRef<CommunicationEvent<Request, Response>>> for TestActor {
fn create_args(chan: ChannelRef<CommunicationEvent<Request, Response>>) -> Self {
TestActor { chan }
}
}

impl Actor for TestActor {
type Msg = CommunicationEvent<Request, Response>;

fn pre_start(&mut self, ctx: &Context<Self::Msg>) {
let topic = Topic::from("from_swarm");
let sub = Box::new(ctx.myself());
self.chan.tell(Subscribe { actor: sub, topic }, None);
}

fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, _sender: Sender) {
println!("{}: -> got msg: {:?}", ctx.myself.name(), msg);
if let CommunicationEvent::Message(P2PReqResEvent::Req {
peer_id,
request_id: Some(request_id),
request: Request::Ping,
}) = msg
{
let response = CommunicationEvent::Message(P2PReqResEvent::Res {
peer_id,
request_id,
response: Response::Pong,
});
self.chan.tell(
Publish {
msg: response,
topic: Topic::from("to_swarm"),
},
None,
);
}
}
}

fn main() {
let local_keys = Keypair::generate_ed25519();
let sys = ActorSystem::new().unwrap();
let chan: ChannelRef<CommunicationEvent<Request, Response>> = channel("p2p", &sys).unwrap();
sys.actor_of_args::<CommunicationActor<Request, Response>, _>(
"communication-actor",
(local_keys, chan.clone(), None),
)
.unwrap();
sys.actor_of_args::<TestActor, _>("test-actor", chan).unwrap();
std::thread::sleep(Duration::from_secs(600));
}
56 changes: 56 additions & 0 deletions communication/examples/cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Communication CLI
version: '1.0'
author: Elena Frank <elena.frank@iota.org>
about: Encrypts data into the Engine Vault. Creates snapshots and can load from snapshots.
subcommands:
- start-mailbox:
about: Start a mailbox that publishes records in kademlia upon receiving them in a request
args:
- port:
short: p
long: port
value_name: listening-port
about: the listening port for the peer, default is 16384
required: false
takes_value: true
- put-mailbox:
about: Put record into the mailbox
args:
- mailbox_addr:
short: a
long: mail-addr
value_name: mailbox-multi-addr
about: the multiaddr of the mailbox
required: true
takes_value: true
- key:
short: k
long: key
value_name: record-key
about: the key for the record
required: true
takes_value: true
- value:
short: v
long: value
value_name: record-value
about: the value for the record
required: true
takes_value: true
- get-record:
about: Get record from local or the mailbox
args:
- mailbox_addr:
short: a
long: mail-addr
value_name: mailbox-multi-addr
about: the multi-address of the mailbox
required: true
takes_value: true
- key:
short: k
long: key
value_name: record-key
about: the key for the record
required: true
takes_value: true
Loading

0 comments on commit e7f9357

Please sign in to comment.