Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Cheema
1a4be539da feat: add bug report to GitHub issue prompt modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 07:31:45 -08:00
23 changed files with 466 additions and 61 deletions

13
Cargo.lock generated
View File

@@ -890,7 +890,7 @@ dependencies = [
"delegate", "delegate",
"env_logger", "env_logger",
"extend", "extend",
"futures-lite", "futures",
"libp2p", "libp2p",
"log", "log",
"networking", "networking",
@@ -914,12 +914,6 @@ dependencies = [
"syn 2.0.111", "syn 2.0.111",
] ]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "ff" name = "ff"
version = "0.13.1" version = "0.13.1"
@@ -1028,10 +1022,7 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [ dependencies = [
"fastrand",
"futures-core", "futures-core",
"futures-io",
"parking",
"pin-project-lite", "pin-project-lite",
] ]
@@ -2762,7 +2753,7 @@ dependencies = [
"delegate", "delegate",
"either", "either",
"extend", "extend",
"futures-lite", "futures",
"futures-timer", "futures-timer",
"keccak-const", "keccak-const",
"libp2p", "libp2p",

View File

@@ -29,13 +29,14 @@ util = { path = "rust/util" }
# Macro dependecies # Macro dependecies
extend = "1.2" extend = "1.2"
delegate = "0.13" delegate = "0.13"
pin-project = "1"
# Utility dependencies # Utility dependencies
keccak-const = "0.2" keccak-const = "0.2"
# Async dependencies # Async dependencies
tokio = "1.46" tokio = "1.46"
futures-lite = "2.6.1" futures = "0.3"
futures-timer = "3.0" futures-timer = "3.0"
# Data structures # Data structures

View File

@@ -0,0 +1,188 @@
<script lang="ts">
import { fade, fly } from "svelte/transition";
import { cubicOut } from "svelte/easing";
interface Props {
isOpen: boolean;
onClose: () => void;
}
let { isOpen, onClose }: Props = $props();
let bugReportId = $state<string | null>(null);
let githubIssueUrl = $state<string | null>(null);
let isLoading = $state(false);
let error = $state<string | null>(null);
async function generateBugReport() {
isLoading = true;
error = null;
try {
const response = await fetch("/bug-report", { method: "POST" });
if (!response.ok) {
error = "Failed to generate bug report. Please try again.";
return;
}
const data = await response.json();
bugReportId = data.bugReportId;
githubIssueUrl = data.githubIssueUrl;
} catch {
error = "Failed to connect to the server. Please try again.";
} finally {
isLoading = false;
}
}
function handleClose() {
bugReportId = null;
githubIssueUrl = null;
error = null;
isLoading = false;
onClose();
}
// Generate bug report when modal opens
$effect(() => {
if (isOpen && !bugReportId && !isLoading) {
generateBugReport();
}
});
</script>
{#if isOpen}
<!-- Backdrop -->
<div
class="fixed inset-0 z-50 bg-black/80 backdrop-blur-sm"
transition:fade={{ duration: 200 }}
onclick={handleClose}
role="presentation"
></div>
<!-- Modal -->
<div
class="fixed z-50 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[min(90vw,480px)] bg-exo-dark-gray border border-exo-yellow/10 rounded-lg shadow-2xl overflow-hidden flex flex-col"
transition:fly={{ y: 20, duration: 300, easing: cubicOut }}
role="dialog"
aria-modal="true"
aria-label="Bug Report"
>
<!-- Header -->
<div
class="flex items-center justify-between px-5 py-4 border-b border-exo-medium-gray/30"
>
<div class="flex items-center gap-2">
<svg
class="w-5 h-5 text-exo-yellow"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<h2 class="text-sm font-mono text-exo-yellow tracking-wider uppercase">
Report a Bug
</h2>
</div>
<button
onclick={handleClose}
class="text-exo-light-gray hover:text-white transition-colors cursor-pointer"
aria-label="Close"
>
<svg
class="w-5 h-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<!-- Body -->
<div class="px-5 py-5 space-y-4">
{#if isLoading}
<div class="flex items-center justify-center py-6">
<div
class="w-5 h-5 border-2 border-exo-yellow/30 border-t-exo-yellow rounded-full animate-spin"
></div>
<span class="ml-3 text-sm text-exo-light-gray font-mono"
>Generating bug report...</span
>
</div>
{:else if error}
<div
class="text-sm text-red-400 font-mono bg-red-400/10 border border-red-400/20 rounded px-4 py-3"
>
{error}
</div>
<button
onclick={generateBugReport}
class="w-full px-4 py-2.5 bg-exo-medium-gray/50 border border-exo-yellow/30 rounded text-sm font-mono text-exo-yellow hover:border-exo-yellow/60 transition-colors cursor-pointer"
>
Try Again
</button>
{:else if bugReportId && githubIssueUrl}
<p class="text-sm text-exo-light-gray leading-relaxed">
Would you like to create a GitHub issue? This would help us track and
fix the issue for you.
</p>
<!-- Bug Report ID -->
<div
class="bg-exo-black/50 border border-exo-medium-gray/30 rounded px-4 py-3"
>
<div
class="text-[11px] text-exo-light-gray/60 font-mono tracking-wider uppercase mb-1"
>
Bug Report ID
</div>
<div class="text-sm text-exo-yellow font-mono tracking-wide">
{bugReportId}
</div>
<div class="text-[11px] text-exo-light-gray/50 font-mono mt-1">
Include this ID when communicating with the team.
</div>
</div>
<p class="text-xs text-exo-light-gray/60 leading-relaxed">
No diagnostic data is attached. The issue template contains
placeholder fields for you to fill in.
</p>
<!-- Actions -->
<div class="flex gap-3 pt-1">
<a
href={githubIssueUrl}
target="_blank"
rel="noopener noreferrer"
class="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-exo-yellow/10 border border-exo-yellow/40 rounded text-sm font-mono text-exo-yellow hover:bg-exo-yellow/20 hover:border-exo-yellow/60 transition-colors"
>
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
/>
</svg>
Create GitHub Issue
</a>
<button
onclick={handleClose}
class="px-4 py-2.5 border border-exo-medium-gray/40 rounded text-sm font-mono text-exo-light-gray hover:border-exo-medium-gray/60 transition-colors cursor-pointer"
>
Close
</button>
</div>
{/if}
</div>
</div>
{/if}

View File

@@ -74,6 +74,7 @@
perSystem = perSystem =
{ config, self', inputs', pkgs, lib, system, ... }: { config, self', inputs', pkgs, lib, system, ... }:
let let
fenixToolchain = inputs'.fenix.packages.complete;
# Use pinned nixpkgs for swift-format (swift is broken on x86_64-linux in newer nixpkgs) # Use pinned nixpkgs for swift-format (swift is broken on x86_64-linux in newer nixpkgs)
pkgsSwift = import inputs.nixpkgs-swift { inherit system; }; pkgsSwift = import inputs.nixpkgs-swift { inherit system; };
in in

2
rust/clippy.toml Normal file
View File

@@ -0,0 +1,2 @@
# we can manually exclude false-positive lint errors for dual packages (if in dependencies)
#allowed-duplicate-crates = ["hashbrown"]

View File

@@ -27,7 +27,7 @@ networking = { workspace = true }
# interop # interop
pyo3 = { version = "0.27.2", features = [ pyo3 = { version = "0.27.2", features = [
# "abi3-py313", # tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.13 # "abi3-py313", # tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.13
# "nightly", # enables better-supported GIL integration "nightly", # enables better-supported GIL integration
"experimental-async", # async support in #[pyfunction] & #[pymethods] "experimental-async", # async support in #[pyfunction] & #[pymethods]
#"experimental-inspect", # inspection of generated binary => easier to automate type-hint generation #"experimental-inspect", # inspection of generated binary => easier to automate type-hint generation
#"py-clone", # adding Clone-ing of `Py<T>` without GIL (may cause panics - remove if panics happen) #"py-clone", # adding Clone-ing of `Py<T>` without GIL (may cause panics - remove if panics happen)
@@ -45,10 +45,11 @@ pyo3-log = "0.13.2"
# macro dependencies # macro dependencies
extend = { workspace = true } extend = { workspace = true }
delegate = { workspace = true } delegate = { workspace = true }
pin-project = { workspace = true }
# async runtime # async runtime
tokio = { workspace = true, features = ["full", "tracing"] } tokio = { workspace = true, features = ["full", "tracing"] }
futures-lite = { workspace = true } futures = { workspace = true }
# utility dependencies # utility dependencies
util = { workspace = true } util = { workspace = true }
@@ -59,4 +60,3 @@ env_logger = "0.11"
# Networking # Networking
libp2p = { workspace = true, features = ["full"] } libp2p = { workspace = true, features = ["full"] }
pin-project = "1.1.10"

View File

@@ -2,6 +2,7 @@
//! //!
use pin_project::pin_project; use pin_project::pin_project;
use pyo3::marker::Ungil;
use pyo3::prelude::*; use pyo3::prelude::*;
use std::{ use std::{
future::Future, future::Future,
@@ -25,8 +26,8 @@ where
impl<F> Future for AllowThreads<F> impl<F> Future for AllowThreads<F>
where where
F: Future + Send, F: Future + Ungil,
F::Output: Send, F::Output: Ungil,
{ {
type Output = F::Output; type Output = F::Output;

View File

@@ -4,12 +4,25 @@
//! //!
//! //!
mod allow_threading; // enable Rust-unstable features for convenience
mod ident; #![feature(trait_alias)]
mod networking; #![feature(tuple_trait)]
#![feature(unboxed_closures)]
// #![feature(stmt_expr_attributes)]
// #![feature(assert_matches)]
// #![feature(async_fn_in_dyn_trait)]
// #![feature(async_for_loop)]
// #![feature(auto_traits)]
// #![feature(negative_impls)]
extern crate core;
mod allow_threading;
pub(crate) mod networking;
pub(crate) mod pylibp2p;
use crate::ident::ident_submodule;
use crate::networking::networking_submodule; use crate::networking::networking_submodule;
use crate::pylibp2p::ident::ident_submodule;
use crate::pylibp2p::multiaddr::multiaddr_submodule;
use pyo3::prelude::PyModule; use pyo3::prelude::PyModule;
use pyo3::{Bound, PyResult, pyclass, pymodule}; use pyo3::{Bound, PyResult, pyclass, pymodule};
use pyo3_stub_gen::define_stub_info_gatherer; use pyo3_stub_gen::define_stub_info_gatherer;
@@ -19,6 +32,14 @@ pub(crate) mod r#const {
pub const MPSC_CHANNEL_SIZE: usize = 1024; pub const MPSC_CHANNEL_SIZE: usize = 1024;
} }
/// Namespace for all the type/trait aliases used by this crate.
pub(crate) mod alias {
use std::marker::Tuple;
pub trait SendFn<Args: Tuple + Send + 'static, Output> =
Fn<Args, Output = Output> + Send + 'static;
}
/// Namespace for crate-wide extension traits/methods /// Namespace for crate-wide extension traits/methods
pub(crate) mod ext { pub(crate) mod ext {
use crate::allow_threading::AllowThreads; use crate::allow_threading::AllowThreads;
@@ -159,6 +180,7 @@ fn main_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
// work with maturin, where the types generate correctly, in the right folder, without // work with maturin, where the types generate correctly, in the right folder, without
// too many importing issues... // too many importing issues...
ident_submodule(m)?; ident_submodule(m)?;
multiaddr_submodule(m)?;
networking_submodule(m)?; networking_submodule(m)?;
// top-level constructs // top-level constructs

View File

@@ -8,8 +8,8 @@
use crate::r#const::MPSC_CHANNEL_SIZE; use crate::r#const::MPSC_CHANNEL_SIZE;
use crate::ext::{ByteArrayExt as _, FutureExt, PyErrExt as _}; use crate::ext::{ByteArrayExt as _, FutureExt, PyErrExt as _};
use crate::ext::{ResultExt as _, TokioMpscReceiverExt as _, TokioMpscSenderExt as _}; use crate::ext::{ResultExt as _, TokioMpscReceiverExt as _, TokioMpscSenderExt as _};
use crate::ident::{PyKeypair, PyPeerId};
use crate::pyclass; use crate::pyclass;
use crate::pylibp2p::ident::{PyKeypair, PyPeerId};
use libp2p::futures::StreamExt as _; use libp2p::futures::StreamExt as _;
use libp2p::gossipsub; use libp2p::gossipsub;
use libp2p::gossipsub::{IdentTopic, Message, MessageId, PublishError}; use libp2p::gossipsub::{IdentTopic, Message, MessageId, PublishError};

View File

@@ -0,0 +1,8 @@
//! A module for exposing Rust's libp2p datatypes over Pyo3
//!
//! TODO: right now we are coupled to libp2p's identity, but eventually we want to create our own
//! independent identity type of some kind or another. This may require handshaking.
//!
pub mod ident;
pub mod multiaddr;

View File

@@ -0,0 +1,81 @@
use crate::ext::ResultExt as _;
use libp2p::Multiaddr;
use pyo3::prelude::{PyBytesMethods as _, PyModule, PyModuleMethods as _};
use pyo3::types::PyBytes;
use pyo3::{Bound, PyResult, Python, pyclass, pymethods};
use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods};
use std::str::FromStr as _;
/// Representation of a Multiaddr.
#[gen_stub_pyclass]
#[pyclass(name = "Multiaddr", frozen)]
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct PyMultiaddr(pub Multiaddr);
#[gen_stub_pymethods]
#[pymethods]
#[allow(clippy::needless_pass_by_value)]
impl PyMultiaddr {
/// Create a new, empty multiaddress.
#[staticmethod]
fn empty() -> Self {
Self(Multiaddr::empty())
}
/// Create a new, empty multiaddress with the given capacity.
#[staticmethod]
fn with_capacity(n: usize) -> Self {
Self(Multiaddr::with_capacity(n))
}
/// Parse a `Multiaddr` value from its byte slice representation.
#[staticmethod]
fn from_bytes(bytes: Bound<'_, PyBytes>) -> PyResult<Self> {
let bytes = Vec::from(bytes.as_bytes());
Ok(Self(Multiaddr::try_from(bytes).pyerr()?))
}
/// Parse a `Multiaddr` value from its string representation.
#[staticmethod]
fn from_string(string: String) -> PyResult<Self> {
Ok(Self(Multiaddr::from_str(&string).pyerr()?))
}
/// Return the length in bytes of this multiaddress.
fn len(&self) -> usize {
self.0.len()
}
/// Returns true if the length of this multiaddress is 0.
fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Return a copy of this [`Multiaddr`]'s byte representation.
fn to_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
let bytes = self.0.to_vec();
PyBytes::new(py, &bytes)
}
/// Convert a Multiaddr to a string.
fn to_string(&self) -> String {
self.0.to_string()
}
#[gen_stub(skip)]
fn __repr__(&self) -> String {
format!("Multiaddr({})", self.0)
}
#[gen_stub(skip)]
fn __str__(&self) -> String {
self.to_string()
}
}
pub fn multiaddr_submodule(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyMultiaddr>()?;
Ok(())
}

View File

@@ -22,7 +22,7 @@ delegate = { workspace = true }
# async # async
tokio = { workspace = true, features = ["full"] } tokio = { workspace = true, features = ["full"] }
futures-lite = { workspace = true } futures = { workspace = true }
futures-timer = { workspace = true } futures-timer = { workspace = true }
# utility dependencies # utility dependencies

View File

@@ -1,4 +1,4 @@
use futures_lite::StreamExt; use futures::stream::StreamExt as _;
use libp2p::{gossipsub, identity, swarm::SwarmEvent}; use libp2p::{gossipsub, identity, swarm::SwarmEvent};
use networking::{discovery, swarm}; use networking::{discovery, swarm};
use tokio::{io, io::AsyncBufReadExt as _, select}; use tokio::{io, io::AsyncBufReadExt as _, select};
@@ -38,19 +38,19 @@ async fn main() {
println!("Publish error: {e:?}"); println!("Publish error: {e:?}");
} }
} }
event = swarm.next() => match event { event = swarm.select_next_some() => match event {
// on gossipsub incoming // on gossipsub incoming
Some(SwarmEvent::Behaviour(swarm::BehaviourEvent::Gossipsub(gossipsub::Event::Message { SwarmEvent::Behaviour(swarm::BehaviourEvent::Gossipsub(gossipsub::Event::Message {
propagation_source: peer_id, propagation_source: peer_id,
message_id: id, message_id: id,
message, message,
}))) => println!( })) => println!(
"\n\nGot message: '{}' with id: {id} from peer: {peer_id}\n\n", "\n\nGot message: '{}' with id: {id} from peer: {peer_id}\n\n",
String::from_utf8_lossy(&message.data), String::from_utf8_lossy(&message.data),
), ),
// on discovery // on discovery
Some(SwarmEvent::Behaviour(swarm::BehaviourEvent::Discovery(e)) )=> match e { SwarmEvent::Behaviour(swarm::BehaviourEvent::Discovery(e)) => match e {
discovery::Event::ConnectionEstablished { discovery::Event::ConnectionEstablished {
peer_id, connection_id, remote_ip, remote_tcp_port peer_id, connection_id, remote_ip, remote_tcp_port
} => { } => {
@@ -64,7 +64,7 @@ async fn main() {
} }
// ignore outgoing errors: those are normal // ignore outgoing errors: those are normal
e@Some(SwarmEvent::OutgoingConnectionError { .. }) => { log::debug!("Outgoing connection error: {e:?}"); } e@SwarmEvent::OutgoingConnectionError { .. } => { log::debug!("Outgoing connection error: {e:?}"); }
// otherwise log any other event // otherwise log any other event
e => { log::info!("Other event {e:?}"); } e => { log::info!("Other event {e:?}"); }

View File

@@ -0,0 +1,127 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
use futures::stream::StreamExt;
use libp2p::{
gossipsub, mdns, noise,
swarm::{NetworkBehaviour, SwarmEvent},
tcp, yamux,
};
use std::error::Error;
use std::time::Duration;
use tokio::{io, io::AsyncBufReadExt, select};
use tracing_subscriber::EnvFilter;
// We create a custom network behaviour that combines Gossipsub and Mdns.
#[derive(NetworkBehaviour)]
struct MyBehaviour {
gossipsub: gossipsub::Behaviour,
mdns: mdns::tokio::Behaviour,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let _ = tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.try_init();
let mut swarm = libp2p::SwarmBuilder::with_new_identity()
.with_tokio()
.with_tcp(
tcp::Config::default(),
noise::Config::new,
yamux::Config::default,
)?
.with_behaviour(|key| {
// Set a custom gossipsub configuration
let gossipsub_config = gossipsub::ConfigBuilder::default()
.heartbeat_interval(Duration::from_secs(10))
.validation_mode(gossipsub::ValidationMode::Strict) // This sets the kind of message validation. The default is Strict (enforce message signing)
.build()
.map_err(io::Error::other)?; // Temporary hack because `build` does not return a proper `std::error::Error`.
// build a gossipsub network behaviour
let gossipsub = gossipsub::Behaviour::new(
gossipsub::MessageAuthenticity::Signed(key.clone()),
gossipsub_config,
)?;
let mdns =
mdns::tokio::Behaviour::new(mdns::Config::default(), key.public().to_peer_id())?;
Ok(MyBehaviour { gossipsub, mdns })
})?
.build();
println!("Running swarm with identity {}", swarm.local_peer_id());
// Create a Gossipsub topic
let topic = gossipsub::IdentTopic::new("test-net");
// subscribes to our topic
swarm.behaviour_mut().gossipsub.subscribe(&topic)?;
// Read full lines from stdin
let mut stdin = io::BufReader::new(io::stdin()).lines();
// Listen on all interfaces and whatever port the OS assigns
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
println!("Enter messages via STDIN and they will be sent to connected peers using Gossipsub");
// Kick it off
loop {
select! {
Ok(Some(line)) = stdin.next_line() => {
if let Err(e) = swarm
.behaviour_mut().gossipsub
.publish(topic.clone(), line.as_bytes()) {
println!("Publish error: {e:?}");
}
}
event = swarm.select_next_some() => match event {
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => {
for (peer_id, multiaddr) in list {
println!("mDNS discovered a new peer: {peer_id} on {multiaddr}");
swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);
}
},
SwarmEvent::Behaviour(MyBehaviourEvent::Mdns(mdns::Event::Expired(list))) => {
for (peer_id, multiaddr) in list {
println!("mDNS discover peer has expired: {peer_id} on {multiaddr}");
swarm.behaviour_mut().gossipsub.remove_explicit_peer(&peer_id);
}
},
SwarmEvent::Behaviour(MyBehaviourEvent::Gossipsub(gossipsub::Event::Message {
propagation_source: peer_id,
message_id: id,
message,
})) => println!(
"Got message: '{}' with id: {id} from peer: {peer_id}",
String::from_utf8_lossy(&message.data),
),
SwarmEvent::NewListenAddr { address, .. } => {
println!("Local node is listening on {address}");
}
e => {
println!("Other swarm event: {:?}", e);
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
use crate::ext::MultiaddrExt; use crate::ext::MultiaddrExt;
use delegate::delegate; use delegate::delegate;
use either::Either; use either::Either;
use futures_lite::FutureExt; use futures::FutureExt;
use futures_timer::Delay; use futures_timer::Delay;
use libp2p::core::transport::PortUse; use libp2p::core::transport::PortUse;
use libp2p::core::{ConnectedPoint, Endpoint}; use libp2p::core::{ConnectedPoint, Endpoint};
@@ -362,7 +362,7 @@ impl NetworkBehaviour for Behaviour {
} }
// retry connecting to all mDNS peers periodically (fails safely if already connected) // retry connecting to all mDNS peers periodically (fails safely if already connected)
if self.retry_delay.poll(cx).is_ready() { if self.retry_delay.poll_unpin(cx).is_ready() {
for (p, mas) in self.mdns_discovered.clone() { for (p, mas) in self.mdns_discovered.clone() {
for ma in mas { for ma in mas {
self.dial(p, ma) self.dial(p, ma)

View File

@@ -31,7 +31,7 @@ pub fn create_swarm(keypair: identity::Keypair) -> alias::AnyResult<Swarm> {
mod transport { mod transport {
use crate::alias; use crate::alias;
use crate::swarm::{NETWORK_VERSION, OVERRIDE_VERSION_ENV_VAR}; use crate::swarm::{NETWORK_VERSION, OVERRIDE_VERSION_ENV_VAR};
use futures_lite::{AsyncRead, AsyncWrite}; use futures::{AsyncRead, AsyncWrite};
use keccak_const::Sha3_256; use keccak_const::Sha3_256;
use libp2p::core::muxing; use libp2p::core::muxing;
use libp2p::core::transport::Boxed; use libp2p::core::transport::Boxed;

View File

@@ -1,10 +1,11 @@
{ inputs, ... }: { inputs, ... }:
{ {
perSystem = perSystem =
{ inputs', pkgs, lib, ... }: { config, self', inputs', pkgs, lib, ... }:
let let
# Fenix nightly toolchain with all components # Fenix nightly toolchain with all components
rustToolchain = inputs'.fenix.packages.stable.withComponents [ fenixPkgs = inputs'.fenix.packages;
rustToolchain = fenixPkgs.complete.withComponents [
"cargo" "cargo"
"rustc" "rustc"
"clippy" "clippy"

2
rust/rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View File

@@ -338,17 +338,7 @@ class DownloadCoordinator:
), ),
) )
elif progress.status in ["in_progress", "not_started"]: elif progress.status in ["in_progress", "not_started"]:
if ( if progress.downloaded_bytes_this_session.in_bytes == 0:
progress.downloaded_bytes.in_bytes
>= progress.total_bytes.in_bytes
> 0
):
status = DownloadCompleted(
node_id=self.node_id,
shard_metadata=progress.shard,
total_bytes=progress.total_bytes,
)
elif progress.downloaded_bytes_this_session.in_bytes == 0:
status = DownloadPending( status = DownloadPending(
node_id=self.node_id, node_id=self.node_id,
shard_metadata=progress.shard, shard_metadata=progress.shard,

View File

@@ -258,7 +258,7 @@ def main():
target = min(max(soft, 65535), hard) target = min(max(soft, 65535), hard)
resource.setrlimit(resource.RLIMIT_NOFILE, (target, hard)) resource.setrlimit(resource.RLIMIT_NOFILE, (target, hard))
mp.set_start_method("spawn", force=True) mp.set_start_method("spawn")
# TODO: Refactor the current verbosity system # TODO: Refactor the current verbosity system
logger_setup(EXO_LOG, args.verbosity) logger_setup(EXO_LOG, args.verbosity)
logger.info("Starting EXO") logger.info("Starting EXO")

View File

@@ -241,11 +241,6 @@ class Worker:
cancelled_task_id=cancelled_task_id, runner_id=runner_id cancelled_task_id=cancelled_task_id, runner_id=runner_id
): ):
await self.runners[runner_id].cancel_task(cancelled_task_id) await self.runners[runner_id].cancel_task(cancelled_task_id)
await self.event_sender.send(
TaskStatusUpdated(
task_id=task.task_id, task_status=TaskStatus.Complete
)
)
case ImageEdits() if task.task_params.total_input_chunks > 0: case ImageEdits() if task.task_params.total_input_chunks > 0:
# Assemble image from chunks and inject into task # Assemble image from chunks and inject into task
cmd_id = task.command_id cmd_id = task.command_id

View File

@@ -98,15 +98,10 @@ class RunnerSupervisor:
def shutdown(self): def shutdown(self):
logger.info("Runner supervisor shutting down") logger.info("Runner supervisor shutting down")
with contextlib.suppress(ClosedResourceError):
self._ev_recv.close() self._ev_recv.close()
with contextlib.suppress(ClosedResourceError):
self._task_sender.close() self._task_sender.close()
with contextlib.suppress(ClosedResourceError):
self._event_sender.close() self._event_sender.close()
with contextlib.suppress(ClosedResourceError):
self._cancel_sender.send(TaskId("CANCEL_CURRENT_TASK")) self._cancel_sender.send(TaskId("CANCEL_CURRENT_TASK"))
with contextlib.suppress(ClosedResourceError):
self._cancel_sender.close() self._cancel_sender.close()
self.runner_process.join(5) self.runner_process.join(5)
if not self.runner_process.is_alive(): if not self.runner_process.is_alive():