Skip to content

Commit

Permalink
feat: implement wasm logic, except relay-client
Browse files Browse the repository at this point in the history
  • Loading branch information
youngjoon-lee committed May 10, 2023
1 parent 5baea65 commit 7a9aa17
Show file tree
Hide file tree
Showing 19 changed files with 6,354 additions and 0 deletions.
11 changes: 11 additions & 0 deletions jiri-wasm/.appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
install:
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
- rustc -V
- cargo -V

build: false

test_script:
- cargo test --locked
6 changes: 6 additions & 0 deletions jiri-wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
69 changes: 69 additions & 0 deletions jiri-wasm/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
language: rust
sudo: false

cache: cargo

matrix:
include:

# Builds with wasm-pack.
- rust: beta
env: RUST_BACKTRACE=1
addons:
firefox: latest
chrome: stable
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
script:
- cargo generate --git . --name testing
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
# in any of our parent dirs is problematic.
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- wasm-pack build
- wasm-pack test --chrome --firefox --headless

# Builds on nightly.
- rust: nightly
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"

# Builds on beta.
- rust: beta
env: RUST_BACKTRACE=1
before_script:
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
- cargo install-update -a
- rustup target add wasm32-unknown-unknown
script:
- cargo generate --git . --name testing
- mv Cargo.toml Cargo.toml.tmpl
- cd testing
- cargo check
- cargo check --target wasm32-unknown-unknown
- cargo check --no-default-features
- cargo check --target wasm32-unknown-unknown --no-default-features
- cargo check --no-default-features --features console_error_panic_hook
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
# Note: no enabling the `wee_alloc` feature here because it requires
# nightly for now.
53 changes: 53 additions & 0 deletions jiri-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[package]
name = "jiri-wasm"
version = "0.1.0"
authors = ["Youngjoon Lee <taxihighway@gmail.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.63"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }

libp2p = { git = "https://github.com/youngjoon-lee/rust-libp2p.git", branch = "gossipsub-wasm", features = [
"identify",
"gossipsub",
"macros",
"relay",
"kad",
"rsa",
"ed25519",
"tcp",
"noise",
"yamux",
"wasm-bindgen",
"wasm-ext",
"wasm-ext-websocket",
]}
gloo = { version = "0.8.0", features = ["futures"] }
futures = "0.3.28"
futures-util = "0.3.28"
wasm-bindgen-futures = "0.4.35"
getrandom = { version = "0.2.9", features = ["js"] }

[dev-dependencies]
wasm-bindgen-test = "0.3.13"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
5 changes: 5 additions & 0 deletions jiri-wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# JIRI WASM

```bash
wasm-pack build
```
231 changes: 231 additions & 0 deletions jiri-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
mod utils;

extern crate alloc;

use std::{
collections::hash_map::DefaultHasher,
error::Error,
hash::{Hash, Hasher},
time::Duration,
};

use futures_util::StreamExt;
use libp2p::{
core::upgrade,
gossipsub, identify, identity,
kad::{store::MemoryStore, Kademlia, KademliaConfig},
multiaddr::{Multiaddr, Protocol},
noise,
swarm::{keep_alive, AddressScore, NetworkBehaviour, SwarmBuilder, SwarmEvent},
wasm_ext::{ffi::websocket_transport, ExtTransport},
yamux, PeerId, StreamProtocol, Swarm, Transport,
};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

macro_rules! log {
($($arg:expr),+) => {
gloo::console::externs::log(::alloc::boxed::Box::from([$(gloo::console::__macro::JsValue::from($arg),)+]))
}
}

const KADEMLIA_PROTOCOL_NAME: &str = "/jiri/lan/kad/1.0.0";
const GOSSIPSUB_TOPIC: &str = "jiri";

#[wasm_bindgen(start)]
pub fn start() {
utils::set_panic_hook();

spawn_local(async {
run().await;
})
}

pub async fn run() {
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
log!(format_args!("Local peer id: {}", local_peer_id).to_string());

let mut swarm = create_swarm(local_key, local_peer_id).unwrap();

let remote_peer_multiaddr =
"/ip4/127.0.0.1/tcp/9091/ws/p2p/12D3KooWSTiScugFjjNxJcL7GqVvDHDvWkiSNYfRMZ2iFvXNZuiA"
.parse::<Multiaddr>()
.unwrap();
swarm.dial(remote_peer_multiaddr).unwrap();

loop {
match swarm.next().await.unwrap() {
SwarmEvent::NewListenAddr { address, .. } => {
let p2p_address = address.with(Protocol::P2p((*swarm.local_peer_id()).into()));
log!(format!("Listen p2p address: {:?}", p2p_address));
}
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
log!(format!("Connected to {}", peer_id));
}
SwarmEvent::OutgoingConnectionError { peer_id, error } => {
log!(format!("Failed to dial {:?}: {}", peer_id, error));
}
SwarmEvent::ConnectionClosed { peer_id, cause, .. } => {
log!(format!("Connection to {} closed: {:?}", peer_id, cause));
swarm.behaviour_mut().kademlia.remove_peer(&peer_id);
log!("Removed {peer_id} from the routing table (if it was in there).");
}
SwarmEvent::Behaviour(BehaviourEvent::Gossipsub(
libp2p::gossipsub::Event::Message {
message_id: _,
propagation_source: _,
message,
},
)) => {
log!(format!(
"Received message from {:?}: {}",
message.source,
String::from_utf8(message.data).unwrap()
));
}
SwarmEvent::Behaviour(BehaviourEvent::Gossipsub(
libp2p::gossipsub::Event::Subscribed { peer_id, topic },
)) => {
log!(format!("{} subscribed to {}", peer_id, topic));
}
SwarmEvent::Behaviour(BehaviourEvent::Identify(e)) => {
log!("BehaviourEvent::Identify {e:?}");

if let identify::Event::Error { peer_id, error } = e {
match error {
libp2p::swarm::StreamUpgradeError::Timeout => {
// When a browser tab closes, we don't get a swarm event
// maybe there's a way to get this with TransportEvent
// but for now remove the peer from routing table if there's an Identify timeout
swarm.behaviour_mut().kademlia.remove_peer(&peer_id);
log!("Removed {peer_id} from the routing table (if it was in there).");
}
_ => {
log!("StreamUpgradeError: {error}");
}
}
} else if let identify::Event::Received {
peer_id,
info:
identify::Info {
listen_addrs,
protocols,
observed_addr,
..
},
} = e
{
log!(format!(
"identify::Event::Received observed_addr: {}",
observed_addr
));

swarm.add_external_address(observed_addr, AddressScore::Infinite);

if protocols
.iter()
.any(|p| p.to_string() == KADEMLIA_PROTOCOL_NAME)
{
for addr in listen_addrs {
log!(format!("identify::Event::Received listen addr: {}", addr));
// TODO (fixme): the below doesn't work because the address is still missing /webrtc/p2p even after https://github.com/libp2p/js-libp2p-webrtc/pull/121
swarm
.behaviour_mut()
.kademlia
.add_address(&peer_id, addr.clone());

// let webrtc_address = addr
// .with(Protocol::WebRTCDirect)
// .with(Protocol::P2p(peer_id.into()));

swarm
.behaviour_mut()
.kademlia
// .add_address(&peer_id, webrtc_address.clone());
.add_address(&peer_id, addr.clone());
// log!("Added {webrtc_address} to the routing table.");
log!("Added {addr} to the routing table.");
}
}
}
}
SwarmEvent::Behaviour(BehaviourEvent::Kademlia(e)) => {
log!(format!("Kademlia event: {:?}", e));
}
event => {
log!(format!("Other type of event: {:?}", event));
}
}
}
}

fn create_swarm(
local_key: identity::Keypair,
local_peer_id: PeerId,
) -> Result<Swarm<Behaviour>, Box<dyn Error>> {
// To content-address message, we can take the hash of message and use it as an ID.
let message_id_fn = |message: &gossipsub::Message| {
let mut s = DefaultHasher::new();
message.data.hash(&mut s);
gossipsub::MessageId::from(s.finish().to_string())
};

// Set a custom gossipsub configuration
let gossipsub_config = gossipsub::ConfigBuilder::default()
.validation_mode(gossipsub::ValidationMode::Permissive) // 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.
.mesh_outbound_min(1)
.mesh_n_low(1)
.flood_publish(true)
.build()
.expect("Valid config");

// build a gossipsub network behaviour
let mut gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(local_key.clone()),
gossipsub_config,
)
.expect("Correct configuration");

gossipsub.subscribe(&gossipsub::IdentTopic::new(GOSSIPSUB_TOPIC))?;

let transport = ExtTransport::new(websocket_transport())
.upgrade(upgrade::Version::V1)
.authenticate(noise::Config::new(&local_key).unwrap())
.multiplex(yamux::Config::default())
.timeout(Duration::from_secs(10))
.boxed();

let identify = identify::Behaviour::new(
identify::Config::new("/ipfs/0.1.0".into(), local_key.public())
.with_interval(Duration::from_secs(60)), // do this so we can get timeouts for dropped WebRTC connections
);

// Create a Kademlia behaviour.
let mut cfg = KademliaConfig::default();
cfg.set_protocol_names(vec![StreamProtocol::new(KADEMLIA_PROTOCOL_NAME)]);
let kademlia = Kademlia::with_config(local_peer_id, MemoryStore::new(local_peer_id), cfg);

let behaviour = Behaviour {
gossipsub,
identify,
kademlia,
keep_alive: keep_alive::Behaviour::default(),
};
Ok(SwarmBuilder::with_wasm_executor(transport, behaviour, local_peer_id).build())
}

#[derive(NetworkBehaviour)]
struct Behaviour {
gossipsub: gossipsub::Behaviour,
identify: identify::Behaviour,
kademlia: Kademlia<MemoryStore>,
keep_alive: keep_alive::Behaviour,
}
10 changes: 10 additions & 0 deletions jiri-wasm/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

0 comments on commit 7a9aa17

Please sign in to comment.