Note multicasting

This is an initial implementation of note multicast, which sends posted
notes to other notedecks on the same network.

This came about after I nerd sniped myself thinking about p2p nostr on
local networks[1]

You can test this exclusively without joining any other relays by
passing -r multicast on the command line.

[1] https://damus.io/note1j50pseqwma38g3aqrsnhvld0m0ysdgppw6fjnvvcj0haeulgswgq80lpca

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-01-01 18:49:46 -06:00
parent f5afdd04a6
commit fe6206c546
16 changed files with 406 additions and 125 deletions

View File

@@ -19,7 +19,7 @@ use crate::{
use notedeck::{Accounts, AppContext, DataPath, DataPathType, FilterState, ImageCache, UnknownIds};
use enostr::{ClientMessage, Keypair, Pubkey, RelayEvent, RelayMessage, RelayPool};
use enostr::{ClientMessage, Keypair, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool};
use uuid::Uuid;
use egui_extras::{Size, StripBuilder};
@@ -124,7 +124,9 @@ fn try_process_event(
RelayEvent::Closed => warn!("{} connection closed", &ev.relay),
RelayEvent::Error(e) => error!("{}: {}", &ev.relay, e),
RelayEvent::Other(msg) => trace!("other event {:?}", &msg),
RelayEvent::Message(msg) => process_message(damus, app_ctx, &ev.relay, &msg),
RelayEvent::Message(msg) => {
process_message(damus, app_ctx, &ev.relay, &msg);
}
}
}
@@ -212,16 +214,6 @@ fn update_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ctx: &egui::Con
}
}
fn process_event(ndb: &Ndb, _subid: &str, event: &str) {
#[cfg(feature = "profiling")]
puffin::profile_function!();
//info!("processing event {}", event);
if let Err(_err) = ndb.process_event(event) {
error!("error processing event {}", event);
}
}
fn handle_eose(
damus: &mut Damus,
ctx: &mut AppContext<'_>,
@@ -314,7 +306,29 @@ fn handle_eose(
fn process_message(damus: &mut Damus, ctx: &mut AppContext<'_>, relay: &str, msg: &RelayMessage) {
match msg {
RelayMessage::Event(subid, ev) => process_event(ctx.ndb, subid, ev),
RelayMessage::Event(_subid, ev) => {
let relay = if let Some(relay) = ctx.pool.relays.iter().find(|r| r.url() == relay) {
relay
} else {
error!("couldn't find relay {} for note processing!?", relay);
return;
};
match relay {
PoolRelay::Websocket(_) => {
//info!("processing event {}", event);
if let Err(err) = ctx.ndb.process_event(ev) {
error!("error processing event {ev}: {err}");
}
}
PoolRelay::Multicast(_) => {
// multicast events are client events
if let Err(err) = ctx.ndb.process_client_event(ev) {
error!("error processing multicast event {ev}: {err}");
}
}
}
}
RelayMessage::Notice(msg) => warn!("Notice from {}: {}", relay, msg),
RelayMessage::OK(cr) => info!("OK {:?}", cr),
RelayMessage::Eose(sid) => {

View File

@@ -9,7 +9,7 @@ pub struct RelayPoolManager<'a> {
pub struct RelayInfo<'a> {
pub relay_url: &'a str,
pub status: &'a RelayStatus,
pub status: RelayStatus,
}
impl<'a> RelayPoolManager<'a> {
@@ -22,8 +22,8 @@ impl<'a> RelayPoolManager<'a> {
.relays
.iter()
.map(|relay| RelayInfo {
relay_url: &relay.relay.url,
status: &relay.relay.status,
relay_url: relay.url(),
status: relay.status(),
})
.collect()
}

View File

@@ -15,7 +15,7 @@ use std::fmt;
use std::sync::atomic::{AtomicU32, Ordering};
use egui_virtual_list::VirtualList;
use enostr::{Pubkey, Relay, RelayPool};
use enostr::{PoolRelay, Pubkey, RelayPool};
use nostrdb::{Filter, Ndb, Note, Subscription, Transaction};
use std::cell::RefCell;
use std::hash::Hash;
@@ -423,7 +423,7 @@ pub fn setup_new_timeline(
}
for relay in &mut pool.relays {
send_initial_timeline_filter(ndb, since_optimize, subs, &mut relay.relay, timeline);
send_initial_timeline_filter(ndb, since_optimize, subs, relay, timeline);
}
}
@@ -440,11 +440,7 @@ pub fn send_initial_timeline_filters(
relay_id: &str,
) -> Option<()> {
info!("Sending initial filters to {}", relay_id);
let relay = &mut pool
.relays
.iter_mut()
.find(|r| r.relay.url == relay_id)?
.relay;
let relay = &mut pool.relays.iter_mut().find(|r| r.url() == relay_id)?;
for timeline in columns.timelines_mut() {
send_initial_timeline_filter(ndb, since_optimize, subs, relay, timeline);
@@ -457,10 +453,10 @@ pub fn send_initial_timeline_filter(
ndb: &Ndb,
can_since_optimize: bool,
subs: &mut Subscriptions,
relay: &mut Relay,
relay: &mut PoolRelay,
timeline: &mut Timeline,
) {
let filter_state = timeline.filter.get(&relay.url);
let filter_state = timeline.filter.get(relay.url());
match filter_state {
FilterState::Broken(err) => {
@@ -510,7 +506,9 @@ pub fn send_initial_timeline_filter(
let sub_id = subscriptions::new_sub_id();
subs.subs.insert(sub_id.clone(), SubKind::Initial);
relay.subscribe(sub_id, new_filters);
if let Err(err) = relay.subscribe(sub_id, new_filters) {
error!("error subscribing: {err}");
}
}
// we need some data first
@@ -524,7 +522,7 @@ fn fetch_contact_list(
filter: Vec<Filter>,
ndb: &Ndb,
subs: &mut Subscriptions,
relay: &mut Relay,
relay: &mut PoolRelay,
timeline: &mut Timeline,
) {
let sub_kind = SubKind::FetchingContactList(timeline.id);
@@ -532,14 +530,16 @@ fn fetch_contact_list(
let local_sub = ndb.subscribe(&filter).expect("sub");
timeline.filter.set_relay_state(
relay.url.clone(),
relay.url().to_string(),
FilterState::fetching_remote(sub_id.clone(), local_sub),
);
subs.subs.insert(sub_id.clone(), sub_kind);
info!("fetching contact list from {}", &relay.url);
relay.subscribe(sub_id, filter);
info!("fetching contact list from {}", relay.url());
if let Err(err) = relay.subscribe(sub_id, filter) {
error!("error subscribing: {err}");
}
}
fn setup_initial_timeline(

View File

@@ -6,7 +6,6 @@ use egui::widgets::text_edit::TextEdit;
use egui::{Frame, Layout};
use enostr::{FilledKeypair, FullKeypair, NoteId, RelayPool};
use nostrdb::{Ndb, Transaction};
use tracing::info;
use notedeck::{ImageCache, NoteCache};
@@ -62,9 +61,7 @@ impl PostAction {
}
};
let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
info!("sending {}", raw_msg);
pool.send(&enostr::ClientMessage::raw(raw_msg));
pool.send(&enostr::ClientMessage::event(note));
drafts.get_from_post_type(&self.post_type).clear();
Ok(())

View File

@@ -104,7 +104,7 @@ impl<'a> RelayView<'a> {
}
}
fn get_right_side_width(status: &RelayStatus) -> f32 {
fn get_right_side_width(status: RelayStatus) -> f32 {
match status {
RelayStatus::Connected => 150.0,
RelayStatus::Connecting => 160.0,
@@ -138,7 +138,7 @@ fn relay_frame(ui: &mut Ui) -> Frame {
.stroke(ui.style().visuals.noninteractive().bg_stroke)
}
fn show_connection_status(ui: &mut Ui, status: &RelayStatus) {
fn show_connection_status(ui: &mut Ui, status: RelayStatus) {
let fg_color = match status {
RelayStatus::Connected => ui.visuals().selection.bg_fill,
RelayStatus::Connecting => ui.visuals().warn_fg_color,
@@ -163,7 +163,7 @@ fn show_connection_status(ui: &mut Ui, status: &RelayStatus) {
});
}
fn get_connection_icon(status: &RelayStatus) -> egui::Image<'static> {
fn get_connection_icon(status: RelayStatus) -> egui::Image<'static> {
let img_data = match status {
RelayStatus::Connected => {
egui::include_image!("../../../../assets/icons/connected_icon_4x.png")