atagen

vineyard

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/vineyard.git

Blame: src/federation.rs

25d14e49ab68atagen2026-09-24 11:07 UTC1
use std::collections::{BTreeMap, HashMap, HashSet};
25d14e49ab68atagen2026-09-24 11:07 UTC2
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
25d14e49ab68atagen2026-09-24 11:07 UTC3
use std::sync::Arc;
25d14e49ab68atagen2026-09-24 11:07 UTC4
use std::time::{Duration, SystemTime, UNIX_EPOCH};
25d14e49ab68atagen2026-09-24 11:07 UTC5
25d14e49ab68atagen2026-09-24 11:07 UTC6
use anyhow::{Context as _, Result, bail};
25d14e49ab68atagen2026-09-24 11:07 UTC7
use futures_util::{SinkExt as _, StreamExt as _};
25d14e49ab68atagen2026-09-24 11:07 UTC8
use nostr::event::{Event, Kind};
25d14e49ab68atagen2026-09-24 11:07 UTC9
use nostr::filter::{Filter, MatchEventOptions};
25d14e49ab68atagen2026-09-24 11:07 UTC10
use nostr::key::PublicKey;
25d14e49ab68atagen2026-09-24 11:07 UTC11
use nostr::message::MachineReadablePrefix;
25d14e49ab68atagen2026-09-24 11:07 UTC12
use nostr_database::{NostrDatabase as _, RejectedReason, SaveEventStatus};
25d14e49ab68atagen2026-09-24 11:07 UTC13
use serde_json::{Value, json};
25d14e49ab68atagen2026-09-24 11:07 UTC14
use tokio::net::{TcpStream, lookup_host};
25d14e49ab68atagen2026-09-24 11:07 UTC15
use tokio::time::{Instant, sleep, timeout, timeout_at};
25d14e49ab68atagen2026-09-24 11:07 UTC16
use tokio_tungstenite::tungstenite::{Message, protocol::WebSocketConfig};
25d14e49ab68atagen2026-09-24 11:07 UTC17
use tokio_tungstenite::{
25d14e49ab68atagen2026-09-24 11:07 UTC18
    MaybeTlsStream, WebSocketStream, client_async_tls_with_config, connect_async_with_config,
25d14e49ab68atagen2026-09-24 11:07 UTC19
};
25d14e49ab68atagen2026-09-24 11:07 UTC20
use tokio_util::sync::CancellationToken;
25d14e49ab68atagen2026-09-24 11:07 UTC21
use tokio_util::task::TaskTracker;
25d14e49ab68atagen2026-09-24 11:07 UTC22
25d14e49ab68atagen2026-09-24 11:07 UTC23
use crate::config::{Config, Peer, relay_url};
25d14e49ab68atagen2026-09-24 11:07 UTC24
use crate::policy::{Policy, WritePolicyResult, coordinate};
25d14e49ab68atagen2026-09-24 11:07 UTC25
use crate::relay::Relay;
25d14e49ab68atagen2026-09-24 11:07 UTC26
use crate::store::Store;
25d14e49ab68atagen2026-09-24 11:07 UTC27
25d14e49ab68atagen2026-09-24 11:07 UTC28
const KINDS: [u16; 10] = [5, 1617, 1621, 1630, 1631, 1632, 10002, 30382, 30617, 30618];
25d14e49ab68atagen2026-09-24 11:07 UTC29
const OVERLAP: u64 = 7 * 24 * 60 * 60;
25d14e49ab68atagen2026-09-24 11:07 UTC30
const RESPONSE_TIMEOUT: Duration = Duration::from_secs(30);
25d14e49ab68atagen2026-09-24 11:07 UTC31
type Socket = WebSocketStream<MaybeTlsStream<TcpStream>>;
25d14e49ab68atagen2026-09-24 11:07 UTC32
25d14e49ab68atagen2026-09-24 11:07 UTC33
#[derive(Clone)]
25d14e49ab68atagen2026-09-24 11:07 UTC34
struct Link {
25d14e49ab68atagen2026-09-24 11:07 UTC35
    peer: Peer,
25d14e49ab68atagen2026-09-24 11:07 UTC36
    config: Arc<Config>,
25d14e49ab68atagen2026-09-24 11:07 UTC37
    store: Arc<Store>,
25d14e49ab68atagen2026-09-24 11:07 UTC38
    relay: Relay,
25d14e49ab68atagen2026-09-24 11:07 UTC39
    policy: Policy,
25d14e49ab68atagen2026-09-24 11:07 UTC40
    discovered: bool,
25d14e49ab68atagen2026-09-24 11:07 UTC41
    stop: CancellationToken,
25d14e49ab68atagen2026-09-24 11:07 UTC42
}
25d14e49ab68atagen2026-09-24 11:07 UTC43
25d14e49ab68atagen2026-09-24 11:07 UTC44
struct Connection {
25d14e49ab68atagen2026-09-24 11:07 UTC45
    socket: Socket,
25d14e49ab68atagen2026-09-24 11:07 UTC46
    stop: CancellationToken,
25d14e49ab68atagen2026-09-24 11:07 UTC47
    write_timeout: Duration,
25d14e49ab68atagen2026-09-24 11:07 UTC48
    request_sequence: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC49
}
25d14e49ab68atagen2026-09-24 11:07 UTC50
25d14e49ab68atagen2026-09-24 11:07 UTC51
pub fn spawn(
25d14e49ab68atagen2026-09-24 11:07 UTC52
    config: Arc<Config>,
25d14e49ab68atagen2026-09-24 11:07 UTC53
    store: Arc<Store>,
25d14e49ab68atagen2026-09-24 11:07 UTC54
    relay: Relay,
25d14e49ab68atagen2026-09-24 11:07 UTC55
    policy: Policy,
25d14e49ab68atagen2026-09-24 11:07 UTC56
    task_tracker: &TaskTracker,
25d14e49ab68atagen2026-09-24 11:07 UTC57
    stop: CancellationToken,
25d14e49ab68atagen2026-09-24 11:07 UTC58
) {
25d14e49ab68atagen2026-09-24 11:07 UTC59
    let tasks = task_tracker.clone();
25d14e49ab68atagen2026-09-24 11:07 UTC60
    task_tracker
25d14e49ab68atagen2026-09-24 11:07 UTC61
        .clone()
25d14e49ab68atagen2026-09-24 11:07 UTC62
        .spawn(manage(config, store, relay, policy, tasks, stop));
25d14e49ab68atagen2026-09-24 11:07 UTC63
}
25d14e49ab68atagen2026-09-24 11:07 UTC64
25d14e49ab68atagen2026-09-24 11:07 UTC65
async fn manage(
25d14e49ab68atagen2026-09-24 11:07 UTC66
    config: Arc<Config>,
25d14e49ab68atagen2026-09-24 11:07 UTC67
    store: Arc<Store>,
25d14e49ab68atagen2026-09-24 11:07 UTC68
    relay: Relay,
25d14e49ab68atagen2026-09-24 11:07 UTC69
    policy: Policy,
25d14e49ab68atagen2026-09-24 11:07 UTC70
    tasks: TaskTracker,
25d14e49ab68atagen2026-09-24 11:07 UTC71
    stop: CancellationToken,
25d14e49ab68atagen2026-09-24 11:07 UTC72
) {
25d14e49ab68atagen2026-09-24 11:07 UTC73
    let mut active = HashMap::<String, CancellationToken>::new();
25d14e49ab68atagen2026-09-24 11:07 UTC74
    loop {
25d14e49ab68atagen2026-09-24 11:07 UTC75
        let mut desired = BTreeMap::new();
25d14e49ab68atagen2026-09-24 11:07 UTC76
        for configured_peer in &config.peers {
25d14e49ab68atagen2026-09-24 11:07 UTC77
            let mut normalized_peer = configured_peer.clone();
25d14e49ab68atagen2026-09-24 11:07 UTC78
            let Some(url) = relay_url(&normalized_peer.url) else {
25d14e49ab68atagen2026-09-24 11:07 UTC79
                continue;
25d14e49ab68atagen2026-09-24 11:07 UTC80
            };
25d14e49ab68atagen2026-09-24 11:07 UTC81
            normalized_peer.url = url.to_string();
25d14e49ab68atagen2026-09-24 11:07 UTC82
            desired.insert(normalized_peer.url.clone(), (normalized_peer, false));
25d14e49ab68atagen2026-09-24 11:07 UTC83
        }
25d14e49ab68atagen2026-09-24 11:07 UTC84
        match store
25d14e49ab68atagen2026-09-24 11:07 UTC85
            .journal
25d14e49ab68atagen2026-09-24 11:07 UTC86
            .discovered(config.limits.max_discovered_peers)
25d14e49ab68atagen2026-09-24 11:07 UTC87
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC88
        {
25d14e49ab68atagen2026-09-24 11:07 UTC89
            Ok(urls) => {
25d14e49ab68atagen2026-09-24 11:07 UTC90
                for raw in urls {
25d14e49ab68atagen2026-09-24 11:07 UTC91
                    if let Some(peer) = discovered_peer(&config, &raw) {
25d14e49ab68atagen2026-09-24 11:07 UTC92
                        desired.entry(peer.url.clone()).or_insert((peer, true));
25d14e49ab68atagen2026-09-24 11:07 UTC93
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC94
                }
25d14e49ab68atagen2026-09-24 11:07 UTC95
            }
25d14e49ab68atagen2026-09-24 11:07 UTC96
            Err(error) => tracing::error!(%error, "loading discovered peers failed"),
25d14e49ab68atagen2026-09-24 11:07 UTC97
        }
25d14e49ab68atagen2026-09-24 11:07 UTC98
25d14e49ab68atagen2026-09-24 11:07 UTC99
        active.retain(|url, peer_stop| {
25d14e49ab68atagen2026-09-24 11:07 UTC100
            if desired.contains_key(url) {
25d14e49ab68atagen2026-09-24 11:07 UTC101
                true
25d14e49ab68atagen2026-09-24 11:07 UTC102
            } else {
25d14e49ab68atagen2026-09-24 11:07 UTC103
                peer_stop.cancel();
25d14e49ab68atagen2026-09-24 11:07 UTC104
                false
25d14e49ab68atagen2026-09-24 11:07 UTC105
            }
25d14e49ab68atagen2026-09-24 11:07 UTC106
        });
25d14e49ab68atagen2026-09-24 11:07 UTC107
        for (url, (peer, discovered)) in desired {
25d14e49ab68atagen2026-09-24 11:07 UTC108
            if active.contains_key(&url) {
25d14e49ab68atagen2026-09-24 11:07 UTC109
                continue;
25d14e49ab68atagen2026-09-24 11:07 UTC110
            }
25d14e49ab68atagen2026-09-24 11:07 UTC111
            let peer_stop = stop.child_token();
25d14e49ab68atagen2026-09-24 11:07 UTC112
            let link = Link {
25d14e49ab68atagen2026-09-24 11:07 UTC113
                peer,
25d14e49ab68atagen2026-09-24 11:07 UTC114
                config: Arc::clone(&config),
25d14e49ab68atagen2026-09-24 11:07 UTC115
                store: Arc::clone(&store),
25d14e49ab68atagen2026-09-24 11:07 UTC116
                relay: relay.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC117
                policy: policy.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC118
                discovered,
25d14e49ab68atagen2026-09-24 11:07 UTC119
                stop: peer_stop.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC120
            };
25d14e49ab68atagen2026-09-24 11:07 UTC121
            tasks.spawn(run(link.clone(), true));
25d14e49ab68atagen2026-09-24 11:07 UTC122
            tasks.spawn(run(link, false));
25d14e49ab68atagen2026-09-24 11:07 UTC123
            active.insert(url, peer_stop);
25d14e49ab68atagen2026-09-24 11:07 UTC124
        }
25d14e49ab68atagen2026-09-24 11:07 UTC125
25d14e49ab68atagen2026-09-24 11:07 UTC126
        tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC127
            () = stop.cancelled() => break,
25d14e49ab68atagen2026-09-24 11:07 UTC128
            () = sleep(Duration::from_secs(10)) => {}
25d14e49ab68atagen2026-09-24 11:07 UTC129
        }
25d14e49ab68atagen2026-09-24 11:07 UTC130
    }
25d14e49ab68atagen2026-09-24 11:07 UTC131
    for peer_stop in active.into_values() {
25d14e49ab68atagen2026-09-24 11:07 UTC132
        peer_stop.cancel();
25d14e49ab68atagen2026-09-24 11:07 UTC133
    }
25d14e49ab68atagen2026-09-24 11:07 UTC134
}
25d14e49ab68atagen2026-09-24 11:07 UTC135
25d14e49ab68atagen2026-09-24 11:07 UTC136
fn discovered_peer(config: &Config, raw: &str) -> Option<Peer> {
25d14e49ab68atagen2026-09-24 11:07 UTC137
    let url = relay_url(raw)?;
25d14e49ab68atagen2026-09-24 11:07 UTC138
    if (!config.allow_private_discovery && url.scheme() != "wss")
25d14e49ab68atagen2026-09-24 11:07 UTC139
        || url == relay_url(&config.public_url)?
25d14e49ab68atagen2026-09-24 11:07 UTC140
    {
25d14e49ab68atagen2026-09-24 11:07 UTC141
        return None;
25d14e49ab68atagen2026-09-24 11:07 UTC142
    }
25d14e49ab68atagen2026-09-24 11:07 UTC143
    Some(Peer {
25d14e49ab68atagen2026-09-24 11:07 UTC144
        url: url.to_string(),
25d14e49ab68atagen2026-09-24 11:07 UTC145
        query_limit: config.limits.max_query_results,
25d14e49ab68atagen2026-09-24 11:07 UTC146
        rescan_from: None,
25d14e49ab68atagen2026-09-24 11:07 UTC147
    })
25d14e49ab68atagen2026-09-24 11:07 UTC148
}
25d14e49ab68atagen2026-09-24 11:07 UTC149
25d14e49ab68atagen2026-09-24 11:07 UTC150
async fn run(link: Link, outbound: bool) {
25d14e49ab68atagen2026-09-24 11:07 UTC151
    let direction = if outbound { "outbound" } else { "inbound" };
25d14e49ab68atagen2026-09-24 11:07 UTC152
    let mut backoff = 5;
25d14e49ab68atagen2026-09-24 11:07 UTC153
    while !link.stop.is_cancelled() {
25d14e49ab68atagen2026-09-24 11:07 UTC154
        let started = Instant::now();
25d14e49ab68atagen2026-09-24 11:07 UTC155
        let result = if outbound {
25d14e49ab68atagen2026-09-24 11:07 UTC156
            publish(&link).await
25d14e49ab68atagen2026-09-24 11:07 UTC157
        } else {
25d14e49ab68atagen2026-09-24 11:07 UTC158
            subscribe(&link).await
25d14e49ab68atagen2026-09-24 11:07 UTC159
        };
25d14e49ab68atagen2026-09-24 11:07 UTC160
        if link.stop.is_cancelled() {
25d14e49ab68atagen2026-09-24 11:07 UTC161
            break;
25d14e49ab68atagen2026-09-24 11:07 UTC162
        }
25d14e49ab68atagen2026-09-24 11:07 UTC163
        if let Err(error) = result {
25d14e49ab68atagen2026-09-24 11:07 UTC164
            tracing::warn!(peer = %link.peer.url, %direction, %error, "peer connection interrupted");
25d14e49ab68atagen2026-09-24 11:07 UTC165
        }
25d14e49ab68atagen2026-09-24 11:07 UTC166
        if started.elapsed() > Duration::from_secs(60) {
25d14e49ab68atagen2026-09-24 11:07 UTC167
            backoff = 5;
25d14e49ab68atagen2026-09-24 11:07 UTC168
        }
25d14e49ab68atagen2026-09-24 11:07 UTC169
        tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC170
            () = link.stop.cancelled() => break,
25d14e49ab68atagen2026-09-24 11:07 UTC171
            () = sleep(Duration::from_secs(backoff)) => {},
25d14e49ab68atagen2026-09-24 11:07 UTC172
        }
25d14e49ab68atagen2026-09-24 11:07 UTC173
        backoff = (backoff * 2).min(60);
25d14e49ab68atagen2026-09-24 11:07 UTC174
    }
25d14e49ab68atagen2026-09-24 11:07 UTC175
}
25d14e49ab68atagen2026-09-24 11:07 UTC176
25d14e49ab68atagen2026-09-24 11:07 UTC177
impl Connection {
25d14e49ab68atagen2026-09-24 11:07 UTC178
    async fn open(link: &Link) -> Result<Self> {
25d14e49ab68atagen2026-09-24 11:07 UTC179
        let config = WebSocketConfig::default()
25d14e49ab68atagen2026-09-24 11:07 UTC180
            .max_message_size(Some(link.config.limits.max_message_bytes))
25d14e49ab68atagen2026-09-24 11:07 UTC181
            .max_frame_size(Some(link.config.limits.max_message_bytes));
25d14e49ab68atagen2026-09-24 11:07 UTC182
        let connected = async {
25d14e49ab68atagen2026-09-24 11:07 UTC183
            if link.discovered {
25d14e49ab68atagen2026-09-24 11:07 UTC184
                connect_discovered(link, config).await
25d14e49ab68atagen2026-09-24 11:07 UTC185
            } else {
25d14e49ab68atagen2026-09-24 11:07 UTC186
                connect_async_with_config(&link.peer.url, Some(config), false)
25d14e49ab68atagen2026-09-24 11:07 UTC187
                    .await
25d14e49ab68atagen2026-09-24 11:07 UTC188
                    .map(|(socket, _)| socket)
25d14e49ab68atagen2026-09-24 11:07 UTC189
                    .map_err(Into::into)
25d14e49ab68atagen2026-09-24 11:07 UTC190
            }
25d14e49ab68atagen2026-09-24 11:07 UTC191
        };
25d14e49ab68atagen2026-09-24 11:07 UTC192
        let socket = tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC193
            () = link.stop.cancelled() => bail!("stopping"),
25d14e49ab68atagen2026-09-24 11:07 UTC194
            result = timeout(RESPONSE_TIMEOUT, connected) => {
25d14e49ab68atagen2026-09-24 11:07 UTC195
                result.context("peer connection timed out")??
25d14e49ab68atagen2026-09-24 11:07 UTC196
            }
25d14e49ab68atagen2026-09-24 11:07 UTC197
        };
25d14e49ab68atagen2026-09-24 11:07 UTC198
        Ok(Self {
25d14e49ab68atagen2026-09-24 11:07 UTC199
            socket,
25d14e49ab68atagen2026-09-24 11:07 UTC200
            stop: link.stop.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC201
            write_timeout: Duration::from_secs(link.config.limits.write_timeout_seconds),
25d14e49ab68atagen2026-09-24 11:07 UTC202
            request_sequence: 0,
25d14e49ab68atagen2026-09-24 11:07 UTC203
        })
25d14e49ab68atagen2026-09-24 11:07 UTC204
    }
25d14e49ab68atagen2026-09-24 11:07 UTC205
25d14e49ab68atagen2026-09-24 11:07 UTC206
    async fn send(&mut self, value: Value) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC207
        self.send_message(Message::Text(value.to_string().into()))
25d14e49ab68atagen2026-09-24 11:07 UTC208
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC209
    }
25d14e49ab68atagen2026-09-24 11:07 UTC210
25d14e49ab68atagen2026-09-24 11:07 UTC211
    async fn send_message(&mut self, message: Message) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC212
        tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC213
            () = self.stop.cancelled() => bail!("stopping"),
25d14e49ab68atagen2026-09-24 11:07 UTC214
            result = timeout(self.write_timeout, self.socket.send(message)) => {
25d14e49ab68atagen2026-09-24 11:07 UTC215
                result.context("peer write timed out")??;
25d14e49ab68atagen2026-09-24 11:07 UTC216
                Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC217
            }
25d14e49ab68atagen2026-09-24 11:07 UTC218
        }
25d14e49ab68atagen2026-09-24 11:07 UTC219
    }
25d14e49ab68atagen2026-09-24 11:07 UTC220
25d14e49ab68atagen2026-09-24 11:07 UTC221
    async fn next(&mut self, deadline: Instant) -> Result<Option<Value>> {
25d14e49ab68atagen2026-09-24 11:07 UTC222
        loop {
25d14e49ab68atagen2026-09-24 11:07 UTC223
            let message = tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC224
                () = self.stop.cancelled() => bail!("stopping"),
25d14e49ab68atagen2026-09-24 11:07 UTC225
                result = timeout_at(deadline, self.socket.next()) => match result {
25d14e49ab68atagen2026-09-24 11:07 UTC226
                    Err(_) => return Ok(None),
25d14e49ab68atagen2026-09-24 11:07 UTC227
                    Ok(Some(message)) => message?,
25d14e49ab68atagen2026-09-24 11:07 UTC228
                    Ok(None) => bail!("peer disconnected"),
25d14e49ab68atagen2026-09-24 11:07 UTC229
                }
25d14e49ab68atagen2026-09-24 11:07 UTC230
            };
25d14e49ab68atagen2026-09-24 11:07 UTC231
            match message {
25d14e49ab68atagen2026-09-24 11:07 UTC232
                Message::Text(text) => return Ok(Some(serde_json::from_str(&text)?)),
25d14e49ab68atagen2026-09-24 11:07 UTC233
                Message::Ping(payload) => self.send_message(Message::Pong(payload)).await?,
25d14e49ab68atagen2026-09-24 11:07 UTC234
                Message::Close(_) => bail!("peer closed the connection"),
25d14e49ab68atagen2026-09-24 11:07 UTC235
                Message::Binary(_) | Message::Pong(_) | Message::Frame(_) => {}
25d14e49ab68atagen2026-09-24 11:07 UTC236
            }
25d14e49ab68atagen2026-09-24 11:07 UTC237
        }
25d14e49ab68atagen2026-09-24 11:07 UTC238
    }
25d14e49ab68atagen2026-09-24 11:07 UTC239
}
25d14e49ab68atagen2026-09-24 11:07 UTC240
25d14e49ab68atagen2026-09-24 11:07 UTC241
async fn connect_discovered(link: &Link, config: WebSocketConfig) -> Result<Socket> {
25d14e49ab68atagen2026-09-24 11:07 UTC242
    let url = url::Url::parse(&link.peer.url)?;
25d14e49ab68atagen2026-09-24 11:07 UTC243
    let host = url.host_str().context("discovered peer has no host")?;
25d14e49ab68atagen2026-09-24 11:07 UTC244
    let port = url
25d14e49ab68atagen2026-09-24 11:07 UTC245
        .port_or_known_default()
25d14e49ab68atagen2026-09-24 11:07 UTC246
        .context("discovered peer has no port")?;
25d14e49ab68atagen2026-09-24 11:07 UTC247
    let mut last_error = None;
25d14e49ab68atagen2026-09-24 11:07 UTC248
    for address in lookup_host((host, port)).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC249
        if !link.config.allow_private_discovery && !public_ip(address.ip()) {
25d14e49ab68atagen2026-09-24 11:07 UTC250
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC251
        }
25d14e49ab68atagen2026-09-24 11:07 UTC252
        match TcpStream::connect(address).await {
25d14e49ab68atagen2026-09-24 11:07 UTC253
            Ok(stream) => {
25d14e49ab68atagen2026-09-24 11:07 UTC254
                return client_async_tls_with_config(&link.peer.url, stream, Some(config), None)
25d14e49ab68atagen2026-09-24 11:07 UTC255
                    .await
25d14e49ab68atagen2026-09-24 11:07 UTC256
                    .map(|(socket, _)| socket)
25d14e49ab68atagen2026-09-24 11:07 UTC257
                    .map_err(Into::into);
25d14e49ab68atagen2026-09-24 11:07 UTC258
            }
25d14e49ab68atagen2026-09-24 11:07 UTC259
            Err(error) => last_error = Some(error),
25d14e49ab68atagen2026-09-24 11:07 UTC260
        }
25d14e49ab68atagen2026-09-24 11:07 UTC261
    }
25d14e49ab68atagen2026-09-24 11:07 UTC262
    match last_error {
25d14e49ab68atagen2026-09-24 11:07 UTC263
        Some(error) => Err(error.into()),
25d14e49ab68atagen2026-09-24 11:07 UTC264
        None => bail!("discovered peer did not resolve to an allowed address"),
25d14e49ab68atagen2026-09-24 11:07 UTC265
    }
25d14e49ab68atagen2026-09-24 11:07 UTC266
}
25d14e49ab68atagen2026-09-24 11:07 UTC267
25d14e49ab68atagen2026-09-24 11:07 UTC268
fn public_ip(address: IpAddr) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC269
    match address {
25d14e49ab68atagen2026-09-24 11:07 UTC270
        IpAddr::V4(ipv4) => public_ipv4(ipv4),
25d14e49ab68atagen2026-09-24 11:07 UTC271
        IpAddr::V6(ipv6) => public_ipv6(ipv6),
25d14e49ab68atagen2026-09-24 11:07 UTC272
    }
25d14e49ab68atagen2026-09-24 11:07 UTC273
}
25d14e49ab68atagen2026-09-24 11:07 UTC274
25d14e49ab68atagen2026-09-24 11:07 UTC275
fn public_ipv4(ip: Ipv4Addr) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC276
    let [first, second, _, _] = ip.octets();
25d14e49ab68atagen2026-09-24 11:07 UTC277
    !(ip.is_private()
25d14e49ab68atagen2026-09-24 11:07 UTC278
        || ip.is_loopback()
25d14e49ab68atagen2026-09-24 11:07 UTC279
        || ip.is_link_local()
25d14e49ab68atagen2026-09-24 11:07 UTC280
        || ip.is_broadcast()
25d14e49ab68atagen2026-09-24 11:07 UTC281
        || ip.is_documentation()
25d14e49ab68atagen2026-09-24 11:07 UTC282
        || ip.is_unspecified()
25d14e49ab68atagen2026-09-24 11:07 UTC283
        || ip.is_multicast()
25d14e49ab68atagen2026-09-24 11:07 UTC284
        || first == 0
25d14e49ab68atagen2026-09-24 11:07 UTC285
        || (first == 100 && (64..=127).contains(&second))
25d14e49ab68atagen2026-09-24 11:07 UTC286
        || (first == 198 && matches!(second, 18 | 19)))
25d14e49ab68atagen2026-09-24 11:07 UTC287
}
25d14e49ab68atagen2026-09-24 11:07 UTC288
25d14e49ab68atagen2026-09-24 11:07 UTC289
fn public_ipv6(ip: Ipv6Addr) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC290
    if let Some(ipv4) = ip.to_ipv4_mapped() {
25d14e49ab68atagen2026-09-24 11:07 UTC291
        return public_ipv4(ipv4);
25d14e49ab68atagen2026-09-24 11:07 UTC292
    }
25d14e49ab68atagen2026-09-24 11:07 UTC293
    let segments = ip.segments();
25d14e49ab68atagen2026-09-24 11:07 UTC294
    !(ip.is_loopback()
25d14e49ab68atagen2026-09-24 11:07 UTC295
        || ip.is_unspecified()
25d14e49ab68atagen2026-09-24 11:07 UTC296
        || ip.is_unique_local()
25d14e49ab68atagen2026-09-24 11:07 UTC297
        || ip.is_unicast_link_local()
25d14e49ab68atagen2026-09-24 11:07 UTC298
        || ip.is_multicast()
25d14e49ab68atagen2026-09-24 11:07 UTC299
        || (segments[0] == 0x2001 && segments[1] == 0x0db8))
25d14e49ab68atagen2026-09-24 11:07 UTC300
}
25d14e49ab68atagen2026-09-24 11:07 UTC301
25d14e49ab68atagen2026-09-24 11:07 UTC302
async fn publish(link: &Link) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC303
    let mut connection = Connection::open(link).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC304
    let mut announced = HashSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC305
    loop {
25d14e49ab68atagen2026-09-24 11:07 UTC306
        let pending = link.store.journal.pending(&link.peer.url, 32).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC307
        if pending.is_empty() {
25d14e49ab68atagen2026-09-24 11:07 UTC308
            connection
25d14e49ab68atagen2026-09-24 11:07 UTC309
                .next(Instant::now() + Duration::from_secs(1))
25d14e49ab68atagen2026-09-24 11:07 UTC310
                .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC311
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC312
        }
25d14e49ab68atagen2026-09-24 11:07 UTC313
        for (sequence, id) in pending {
25d14e49ab68atagen2026-09-24 11:07 UTC314
            if announced.len() >= link.config.limits.max_repositories {
25d14e49ab68atagen2026-09-24 11:07 UTC315
                announced.clear();
25d14e49ab68atagen2026-09-24 11:07 UTC316
            }
25d14e49ab68atagen2026-09-24 11:07 UTC317
            if let Some(event) = link.store.event_by_id(&id).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC318
                let repository_event = repository_coordinate(&event).is_some();
25d14e49ab68atagen2026-09-24 11:07 UTC319
                if let Some(announcement) = announcement_for(link, &event).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC320
                    if announced.insert(announcement.id) {
25d14e49ab68atagen2026-09-24 11:07 UTC321
                        if let Some(error) = publish_event(&mut connection, &announcement).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC322
                            announced.remove(&announcement.id);
25d14e49ab68atagen2026-09-24 11:07 UTC323
                            defer(link, sequence, &error).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC324
                            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC325
                        }
25d14e49ab68atagen2026-09-24 11:07 UTC326
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC327
                } else if repository_event {
25d14e49ab68atagen2026-09-24 11:07 UTC328
                    link.store
25d14e49ab68atagen2026-09-24 11:07 UTC329
                        .journal
25d14e49ab68atagen2026-09-24 11:07 UTC330
                        .acknowledge(&link.peer.url, sequence)
25d14e49ab68atagen2026-09-24 11:07 UTC331
                        .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC332
                    continue;
25d14e49ab68atagen2026-09-24 11:07 UTC333
                }
25d14e49ab68atagen2026-09-24 11:07 UTC334
                if let Some(error) = publish_event(&mut connection, &event).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC335
                    if link.store.event_by_id(&id).await?.is_some() {
25d14e49ab68atagen2026-09-24 11:07 UTC336
                        defer(link, sequence, &error).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC337
                        continue;
25d14e49ab68atagen2026-09-24 11:07 UTC338
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC339
                } else if event.kind == Kind::from(30617) {
25d14e49ab68atagen2026-09-24 11:07 UTC340
                    announced.insert(event.id);
25d14e49ab68atagen2026-09-24 11:07 UTC341
                }
25d14e49ab68atagen2026-09-24 11:07 UTC342
            }
25d14e49ab68atagen2026-09-24 11:07 UTC343
            link.store
25d14e49ab68atagen2026-09-24 11:07 UTC344
                .journal
25d14e49ab68atagen2026-09-24 11:07 UTC345
                .acknowledge(&link.peer.url, sequence)
25d14e49ab68atagen2026-09-24 11:07 UTC346
                .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC347
        }
25d14e49ab68atagen2026-09-24 11:07 UTC348
    }
25d14e49ab68atagen2026-09-24 11:07 UTC349
}
25d14e49ab68atagen2026-09-24 11:07 UTC350
25d14e49ab68atagen2026-09-24 11:07 UTC351
async fn defer(link: &Link, sequence: u64, error: &str) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC352
    let retry_at = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() + 300;
25d14e49ab68atagen2026-09-24 11:07 UTC353
    link.store
25d14e49ab68atagen2026-09-24 11:07 UTC354
        .journal
25d14e49ab68atagen2026-09-24 11:07 UTC355
        .defer(&link.peer.url, sequence, error, retry_at)
25d14e49ab68atagen2026-09-24 11:07 UTC356
        .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC357
    tracing::warn!(peer = %link.peer.url, sequence, %error, "peer delivery deferred");
25d14e49ab68atagen2026-09-24 11:07 UTC358
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC359
}
25d14e49ab68atagen2026-09-24 11:07 UTC360
25d14e49ab68atagen2026-09-24 11:07 UTC361
async fn publish_event(connection: &mut Connection, event: &Event) -> Result<Option<String>> {
25d14e49ab68atagen2026-09-24 11:07 UTC362
    tokio::select! {
25d14e49ab68atagen2026-09-24 11:07 UTC363
        () = connection.stop.cancelled() => bail!("stopping"),
25d14e49ab68atagen2026-09-24 11:07 UTC364
        () = sleep(Duration::from_secs(1)) => {},
25d14e49ab68atagen2026-09-24 11:07 UTC365
    }
25d14e49ab68atagen2026-09-24 11:07 UTC366
    connection.send(json!(["EVENT", event])).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC367
    let id = event.id.to_hex();
25d14e49ab68atagen2026-09-24 11:07 UTC368
    let deadline = Instant::now() + RESPONSE_TIMEOUT;
25d14e49ab68atagen2026-09-24 11:07 UTC369
    loop {
25d14e49ab68atagen2026-09-24 11:07 UTC370
        let frame = connection
25d14e49ab68atagen2026-09-24 11:07 UTC371
            .next(deadline)
25d14e49ab68atagen2026-09-24 11:07 UTC372
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC373
            .context("peer acknowledgement timed out")?;
25d14e49ab68atagen2026-09-24 11:07 UTC374
        if frame[0] == "OK" && frame[1].as_str() == Some(&id) {
25d14e49ab68atagen2026-09-24 11:07 UTC375
            let reason = frame[3].as_str().unwrap_or("peer rejected event");
25d14e49ab68atagen2026-09-24 11:07 UTC376
            return Ok(if frame[2] == true || reason.starts_with("duplicate:") {
25d14e49ab68atagen2026-09-24 11:07 UTC377
                None
25d14e49ab68atagen2026-09-24 11:07 UTC378
            } else {
25d14e49ab68atagen2026-09-24 11:07 UTC379
                Some(reason.chars().take(512).collect())
25d14e49ab68atagen2026-09-24 11:07 UTC380
            });
25d14e49ab68atagen2026-09-24 11:07 UTC381
        }
25d14e49ab68atagen2026-09-24 11:07 UTC382
    }
25d14e49ab68atagen2026-09-24 11:07 UTC383
}
25d14e49ab68atagen2026-09-24 11:07 UTC384
25d14e49ab68atagen2026-09-24 11:07 UTC385
async fn announcement_for(link: &Link, event: &Event) -> Result<Option<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC386
    let Some((author, identifier)) = repository_coordinate(event) else {
25d14e49ab68atagen2026-09-24 11:07 UTC387
        return Ok(None);
25d14e49ab68atagen2026-09-24 11:07 UTC388
    };
25d14e49ab68atagen2026-09-24 11:07 UTC389
    Ok(link
25d14e49ab68atagen2026-09-24 11:07 UTC390
        .store
25d14e49ab68atagen2026-09-24 11:07 UTC391
        .query(
25d14e49ab68atagen2026-09-24 11:07 UTC392
            Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC393
                .kind(Kind::from(30617))
25d14e49ab68atagen2026-09-24 11:07 UTC394
                .author(author)
25d14e49ab68atagen2026-09-24 11:07 UTC395
                .identifier(identifier)
25d14e49ab68atagen2026-09-24 11:07 UTC396
                .limit(1),
25d14e49ab68atagen2026-09-24 11:07 UTC397
        )
25d14e49ab68atagen2026-09-24 11:07 UTC398
        .await?
25d14e49ab68atagen2026-09-24 11:07 UTC399
        .into_iter()
25d14e49ab68atagen2026-09-24 11:07 UTC400
        .next())
25d14e49ab68atagen2026-09-24 11:07 UTC401
}
25d14e49ab68atagen2026-09-24 11:07 UTC402
25d14e49ab68atagen2026-09-24 11:07 UTC403
fn repository_coordinate(event: &Event) -> Option<(PublicKey, String)> {
25d14e49ab68atagen2026-09-24 11:07 UTC404
    if event.kind == Kind::from(30618) {
25d14e49ab68atagen2026-09-24 11:07 UTC405
        event
25d14e49ab68atagen2026-09-24 11:07 UTC406
            .tags
25d14e49ab68atagen2026-09-24 11:07 UTC407
            .identifier()
25d14e49ab68atagen2026-09-24 11:07 UTC408
            .map(|identifier| (event.pubkey, identifier))
25d14e49ab68atagen2026-09-24 11:07 UTC409
    } else if matches!(event.kind.as_u16(), 1617 | 1621 | 1630..=1632) {
25d14e49ab68atagen2026-09-24 11:07 UTC410
        event.tags.iter().find_map(|tag| {
25d14e49ab68atagen2026-09-24 11:07 UTC411
            let values = tag.as_slice();
25d14e49ab68atagen2026-09-24 11:07 UTC412
            if values.first().map(String::as_str) != Some("a") {
25d14e49ab68atagen2026-09-24 11:07 UTC413
                return None;
25d14e49ab68atagen2026-09-24 11:07 UTC414
            }
25d14e49ab68atagen2026-09-24 11:07 UTC415
            let (kind, author, identifier) = coordinate(values.get(1)?)?;
25d14e49ab68atagen2026-09-24 11:07 UTC416
            if kind != 30617 {
25d14e49ab68atagen2026-09-24 11:07 UTC417
                return None;
25d14e49ab68atagen2026-09-24 11:07 UTC418
            }
25d14e49ab68atagen2026-09-24 11:07 UTC419
            Some((author, identifier.to_owned()))
25d14e49ab68atagen2026-09-24 11:07 UTC420
        })
25d14e49ab68atagen2026-09-24 11:07 UTC421
    } else {
25d14e49ab68atagen2026-09-24 11:07 UTC422
        None
25d14e49ab68atagen2026-09-24 11:07 UTC423
    }
25d14e49ab68atagen2026-09-24 11:07 UTC424
}
25d14e49ab68atagen2026-09-24 11:07 UTC425
25d14e49ab68atagen2026-09-24 11:07 UTC426
async fn import(link: &Link, value: Value) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC427
    let event: Event = serde_json::from_value(value)?;
25d14e49ab68atagen2026-09-24 11:07 UTC428
    if event.verify().is_err() {
25d14e49ab68atagen2026-09-24 11:07 UTC429
        tracing::debug!(peer = %link.peer.url, event = %event.id, "ignored invalid peer event");
25d14e49ab68atagen2026-09-24 11:07 UTC430
        return Ok(());
25d14e49ab68atagen2026-09-24 11:07 UTC431
    }
25d14e49ab68atagen2026-09-24 11:07 UTC432
    match link.policy.admit_peer(&event).await {
25d14e49ab68atagen2026-09-24 11:07 UTC433
        WritePolicyResult::Accept => {}
25d14e49ab68atagen2026-09-24 11:07 UTC434
        WritePolicyResult::Respond { status: true, .. } => return Ok(()),
25d14e49ab68atagen2026-09-24 11:07 UTC435
        WritePolicyResult::Respond {
25d14e49ab68atagen2026-09-24 11:07 UTC436
            prefix: MachineReadablePrefix::Error | MachineReadablePrefix::RateLimited,
25d14e49ab68atagen2026-09-24 11:07 UTC437
            message,
25d14e49ab68atagen2026-09-24 11:07 UTC438
            ..
25d14e49ab68atagen2026-09-24 11:07 UTC439
        } => bail!("peer import paused by {message}"),
25d14e49ab68atagen2026-09-24 11:07 UTC440
        WritePolicyResult::Respond { message, .. } => {
25d14e49ab68atagen2026-09-24 11:07 UTC441
            tracing::debug!(peer = %link.peer.url, event = %event.id, %message, "ignored out of scope peer event");
25d14e49ab68atagen2026-09-24 11:07 UTC442
            return Ok(());
25d14e49ab68atagen2026-09-24 11:07 UTC443
        }
25d14e49ab68atagen2026-09-24 11:07 UTC444
    }
25d14e49ab68atagen2026-09-24 11:07 UTC445
    match link.relay.add_event(event).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC446
        SaveEventStatus::Success
25d14e49ab68atagen2026-09-24 11:07 UTC447
        | SaveEventStatus::Rejected(
25d14e49ab68atagen2026-09-24 11:07 UTC448
            RejectedReason::Duplicate
25d14e49ab68atagen2026-09-24 11:07 UTC449
            | RejectedReason::Deleted
25d14e49ab68atagen2026-09-24 11:07 UTC450
            | RejectedReason::Expired
25d14e49ab68atagen2026-09-24 11:07 UTC451
            | RejectedReason::Replaced
25d14e49ab68atagen2026-09-24 11:07 UTC452
            | RejectedReason::InvalidDelete
25d14e49ab68atagen2026-09-24 11:07 UTC453
            | RejectedReason::Vanished,
25d14e49ab68atagen2026-09-24 11:07 UTC454
        ) => Ok(()),
25d14e49ab68atagen2026-09-24 11:07 UTC455
        status @ SaveEventStatus::Rejected(_) => {
25d14e49ab68atagen2026-09-24 11:07 UTC456
            bail!("peer event was not stored with status {status:?}")
25d14e49ab68atagen2026-09-24 11:07 UTC457
        }
25d14e49ab68atagen2026-09-24 11:07 UTC458
    }
25d14e49ab68atagen2026-09-24 11:07 UTC459
}
25d14e49ab68atagen2026-09-24 11:07 UTC460
25d14e49ab68atagen2026-09-24 11:07 UTC461
async fn subscribe(link: &Link) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC462
    let mut connection = Connection::open(link).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC463
    connection
25d14e49ab68atagen2026-09-24 11:07 UTC464
        .send(json!(["REQ", "live", {"kinds": KINDS, "limit": 0_u8}]))
25d14e49ab68atagen2026-09-24 11:07 UTC465
        .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC466
    let deadline = Instant::now() + RESPONSE_TIMEOUT;
25d14e49ab68atagen2026-09-24 11:07 UTC467
    loop {
25d14e49ab68atagen2026-09-24 11:07 UTC468
        let frame = connection
25d14e49ab68atagen2026-09-24 11:07 UTC469
            .next(deadline)
25d14e49ab68atagen2026-09-24 11:07 UTC470
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC471
            .context("live subscription timed out")?;
25d14e49ab68atagen2026-09-24 11:07 UTC472
        if frame[0] == "EOSE" && frame[1] == "live" {
25d14e49ab68atagen2026-09-24 11:07 UTC473
            break;
25d14e49ab68atagen2026-09-24 11:07 UTC474
        }
25d14e49ab68atagen2026-09-24 11:07 UTC475
        handle_live(link, frame).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC476
    }
25d14e49ab68atagen2026-09-24 11:07 UTC477
    let mut completed = HashSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC478
    let mut next_replay = Instant::now();
25d14e49ab68atagen2026-09-24 11:07 UTC479
    loop {
25d14e49ab68atagen2026-09-24 11:07 UTC480
        let replay_all = Instant::now() >= next_replay;
25d14e49ab68atagen2026-09-24 11:07 UTC481
        let until = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
25d14e49ab68atagen2026-09-24 11:07 UTC482
        for (scope, filter) in [
25d14e49ab68atagen2026-09-24 11:07 UTC483
            ("announcements".to_owned(), json!({"kinds": [30_617_u16]})),
25d14e49ab68atagen2026-09-24 11:07 UTC484
            ("bindings".to_owned(), json!({"kinds": [30_382_u16]})),
25d14e49ab68atagen2026-09-24 11:07 UTC485
            ("relays".to_owned(), json!({"kinds": [10_002_u16]})),
25d14e49ab68atagen2026-09-24 11:07 UTC486
        ] {
25d14e49ab68atagen2026-09-24 11:07 UTC487
            if replay_all || !completed.contains(&scope) {
25d14e49ab68atagen2026-09-24 11:07 UTC488
                replay(link, &mut connection, &scope, filter, until).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC489
                completed.insert(scope);
25d14e49ab68atagen2026-09-24 11:07 UTC490
            }
25d14e49ab68atagen2026-09-24 11:07 UTC491
        }
25d14e49ab68atagen2026-09-24 11:07 UTC492
        let mut active_scopes = HashSet::from([
25d14e49ab68atagen2026-09-24 11:07 UTC493
            "announcements".to_owned(),
25d14e49ab68atagen2026-09-24 11:07 UTC494
            "bindings".to_owned(),
25d14e49ab68atagen2026-09-24 11:07 UTC495
            "relays".to_owned(),
25d14e49ab68atagen2026-09-24 11:07 UTC496
        ]);
25d14e49ab68atagen2026-09-24 11:07 UTC497
        for (author, identifier) in link
25d14e49ab68atagen2026-09-24 11:07 UTC498
            .store
25d14e49ab68atagen2026-09-24 11:07 UTC499
            .journal
25d14e49ab68atagen2026-09-24 11:07 UTC500
            .repositories(link.config.limits.max_repositories)
25d14e49ab68atagen2026-09-24 11:07 UTC501
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC502
        {
25d14e49ab68atagen2026-09-24 11:07 UTC503
            let address = format!("30617:{author}:{identifier}");
25d14e49ab68atagen2026-09-24 11:07 UTC504
            for (scope, filter) in [
25d14e49ab68atagen2026-09-24 11:07 UTC505
                (
25d14e49ab68atagen2026-09-24 11:07 UTC506
                    format!("state:{address}"),
25d14e49ab68atagen2026-09-24 11:07 UTC507
                    json!({"kinds": [30_618_u16], "authors": [author], "#d": [identifier]}),
25d14e49ab68atagen2026-09-24 11:07 UTC508
                ),
25d14e49ab68atagen2026-09-24 11:07 UTC509
                (
25d14e49ab68atagen2026-09-24 11:07 UTC510
                    format!("contributions:{address}"),
25d14e49ab68atagen2026-09-24 11:07 UTC511
                    json!({"kinds": [1_617_u16, 1_621_u16, 1_630_u16, 1_631_u16, 1_632_u16], "#a": [address]}),
25d14e49ab68atagen2026-09-24 11:07 UTC512
                ),
25d14e49ab68atagen2026-09-24 11:07 UTC513
            ] {
25d14e49ab68atagen2026-09-24 11:07 UTC514
                active_scopes.insert(scope.clone());
25d14e49ab68atagen2026-09-24 11:07 UTC515
                if replay_all || !completed.contains(&scope) {
25d14e49ab68atagen2026-09-24 11:07 UTC516
                    replay(link, &mut connection, &scope, filter, until).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC517
                    completed.insert(scope);
25d14e49ab68atagen2026-09-24 11:07 UTC518
                }
25d14e49ab68atagen2026-09-24 11:07 UTC519
            }
25d14e49ab68atagen2026-09-24 11:07 UTC520
        }
25d14e49ab68atagen2026-09-24 11:07 UTC521
        completed.retain(|scope| active_scopes.contains(scope));
25d14e49ab68atagen2026-09-24 11:07 UTC522
        if replay_all {
25d14e49ab68atagen2026-09-24 11:07 UTC523
            replay(
25d14e49ab68atagen2026-09-24 11:07 UTC524
                link,
25d14e49ab68atagen2026-09-24 11:07 UTC525
                &mut connection,
25d14e49ab68atagen2026-09-24 11:07 UTC526
                "deletions",
25d14e49ab68atagen2026-09-24 11:07 UTC527
                json!({"kinds": [5_u16]}),
25d14e49ab68atagen2026-09-24 11:07 UTC528
                until,
25d14e49ab68atagen2026-09-24 11:07 UTC529
            )
25d14e49ab68atagen2026-09-24 11:07 UTC530
            .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC531
            next_replay = Instant::now() + Duration::from_secs(300);
25d14e49ab68atagen2026-09-24 11:07 UTC532
        }
25d14e49ab68atagen2026-09-24 11:07 UTC533
        let refresh = Instant::now() + Duration::from_secs(30);
25d14e49ab68atagen2026-09-24 11:07 UTC534
        while let Some(frame) = connection.next(refresh).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC535
            handle_live(link, frame).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC536
        }
25d14e49ab68atagen2026-09-24 11:07 UTC537
    }
25d14e49ab68atagen2026-09-24 11:07 UTC538
}
25d14e49ab68atagen2026-09-24 11:07 UTC539
25d14e49ab68atagen2026-09-24 11:07 UTC540
async fn handle_live(link: &Link, frame: Value) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC541
    if frame[1] != "live" {
25d14e49ab68atagen2026-09-24 11:07 UTC542
        return Ok(());
25d14e49ab68atagen2026-09-24 11:07 UTC543
    }
25d14e49ab68atagen2026-09-24 11:07 UTC544
    match frame[0].as_str() {
25d14e49ab68atagen2026-09-24 11:07 UTC545
        Some("EVENT") => import(link, frame[2].clone()).await,
25d14e49ab68atagen2026-09-24 11:07 UTC546
        Some("CLOSED") => bail!("peer closed live subscription with {}", frame[2]),
25d14e49ab68atagen2026-09-24 11:07 UTC547
        _ => Ok(()),
25d14e49ab68atagen2026-09-24 11:07 UTC548
    }
25d14e49ab68atagen2026-09-24 11:07 UTC549
}
25d14e49ab68atagen2026-09-24 11:07 UTC550
25d14e49ab68atagen2026-09-24 11:07 UTC551
async fn replay(
25d14e49ab68atagen2026-09-24 11:07 UTC552
    link: &Link,
25d14e49ab68atagen2026-09-24 11:07 UTC553
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC554
    scope: &str,
25d14e49ab68atagen2026-09-24 11:07 UTC555
    filter: Value,
25d14e49ab68atagen2026-09-24 11:07 UTC556
    until: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC557
) -> Result<()> {
25d14e49ab68atagen2026-09-24 11:07 UTC558
    let checkpoint = link
25d14e49ab68atagen2026-09-24 11:07 UTC559
        .store
25d14e49ab68atagen2026-09-24 11:07 UTC560
        .journal
25d14e49ab68atagen2026-09-24 11:07 UTC561
        .checkpoint(&link.peer.url, scope)
25d14e49ab68atagen2026-09-24 11:07 UTC562
        .await?
25d14e49ab68atagen2026-09-24 11:07 UTC563
        .unwrap_or(0);
25d14e49ab68atagen2026-09-24 11:07 UTC564
    let overlap_start = checkpoint.saturating_sub(OVERLAP);
25d14e49ab68atagen2026-09-24 11:07 UTC565
    let replay_start = link
25d14e49ab68atagen2026-09-24 11:07 UTC566
        .peer
25d14e49ab68atagen2026-09-24 11:07 UTC567
        .rescan_from
25d14e49ab68atagen2026-09-24 11:07 UTC568
        .map_or(overlap_start, |from| from.min(overlap_start));
25d14e49ab68atagen2026-09-24 11:07 UTC569
    let mut ranges = vec![(replay_start.min(until), until)];
25d14e49ab68atagen2026-09-24 11:07 UTC570
    while let Some((since, range_until)) = ranges.pop() {
25d14e49ab68atagen2026-09-24 11:07 UTC571
        if replay_range(link, connection, filter.clone(), since, range_until).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC572
            link.store
25d14e49ab68atagen2026-09-24 11:07 UTC573
                .journal
25d14e49ab68atagen2026-09-24 11:07 UTC574
                .advance(&link.peer.url, scope, range_until)
25d14e49ab68atagen2026-09-24 11:07 UTC575
                .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC576
        } else {
25d14e49ab68atagen2026-09-24 11:07 UTC577
            if since == range_until {
25d14e49ab68atagen2026-09-24 11:07 UTC578
                bail!(
25d14e49ab68atagen2026-09-24 11:07 UTC579
                    "recovery required for {scope} at {since}, one second exceeds the peer history limit"
25d14e49ab68atagen2026-09-24 11:07 UTC580
                );
25d14e49ab68atagen2026-09-24 11:07 UTC581
            }
25d14e49ab68atagen2026-09-24 11:07 UTC582
            let middle = since + (range_until - since) / 2;
25d14e49ab68atagen2026-09-24 11:07 UTC583
            ranges.push((middle + 1, range_until));
25d14e49ab68atagen2026-09-24 11:07 UTC584
            ranges.push((since, middle));
25d14e49ab68atagen2026-09-24 11:07 UTC585
        }
25d14e49ab68atagen2026-09-24 11:07 UTC586
    }
25d14e49ab68atagen2026-09-24 11:07 UTC587
    Ok(())
25d14e49ab68atagen2026-09-24 11:07 UTC588
}
25d14e49ab68atagen2026-09-24 11:07 UTC589
25d14e49ab68atagen2026-09-24 11:07 UTC590
async fn replay_range(
25d14e49ab68atagen2026-09-24 11:07 UTC591
    link: &Link,
25d14e49ab68atagen2026-09-24 11:07 UTC592
    connection: &mut Connection,
25d14e49ab68atagen2026-09-24 11:07 UTC593
    mut filter: Value,
25d14e49ab68atagen2026-09-24 11:07 UTC594
    since: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC595
    until: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC596
) -> Result<bool> {
25d14e49ab68atagen2026-09-24 11:07 UTC597
    let pause = Instant::now() + Duration::from_secs(1);
25d14e49ab68atagen2026-09-24 11:07 UTC598
    while let Some(frame) = connection.next(pause).await? {
25d14e49ab68atagen2026-09-24 11:07 UTC599
        handle_live(link, frame).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC600
    }
25d14e49ab68atagen2026-09-24 11:07 UTC601
    filter["since"] = json!(since);
25d14e49ab68atagen2026-09-24 11:07 UTC602
    filter["until"] = json!(until);
25d14e49ab68atagen2026-09-24 11:07 UTC603
    filter["limit"] = json!(link.peer.query_limit);
25d14e49ab68atagen2026-09-24 11:07 UTC604
    let matching: Filter = serde_json::from_value(filter.clone())?;
25d14e49ab68atagen2026-09-24 11:07 UTC605
    connection.request_sequence += 1;
25d14e49ab68atagen2026-09-24 11:07 UTC606
    let subscription = format!("history-{}", connection.request_sequence);
25d14e49ab68atagen2026-09-24 11:07 UTC607
    connection
25d14e49ab68atagen2026-09-24 11:07 UTC608
        .send(json!(["REQ", subscription, filter]))
25d14e49ab68atagen2026-09-24 11:07 UTC609
        .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC610
    let deadline = Instant::now() + RESPONSE_TIMEOUT;
25d14e49ab68atagen2026-09-24 11:07 UTC611
    let mut received = HashSet::new();
25d14e49ab68atagen2026-09-24 11:07 UTC612
    let complete = loop {
25d14e49ab68atagen2026-09-24 11:07 UTC613
        let frame = connection
25d14e49ab68atagen2026-09-24 11:07 UTC614
            .next(deadline)
25d14e49ab68atagen2026-09-24 11:07 UTC615
            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC616
            .context("peer history timed out")?;
25d14e49ab68atagen2026-09-24 11:07 UTC617
        if frame[1] != subscription {
25d14e49ab68atagen2026-09-24 11:07 UTC618
            handle_live(link, frame).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC619
            continue;
25d14e49ab68atagen2026-09-24 11:07 UTC620
        }
25d14e49ab68atagen2026-09-24 11:07 UTC621
        match frame[0].as_str() {
25d14e49ab68atagen2026-09-24 11:07 UTC622
            Some("EVENT") => {
25d14e49ab68atagen2026-09-24 11:07 UTC623
                let event: Event = serde_json::from_value(frame[2].clone())?;
25d14e49ab68atagen2026-09-24 11:07 UTC624
                if !matching.match_event(&event, MatchEventOptions::new()) {
25d14e49ab68atagen2026-09-24 11:07 UTC625
                    bail!("peer returned an event outside the requested history");
25d14e49ab68atagen2026-09-24 11:07 UTC626
                }
25d14e49ab68atagen2026-09-24 11:07 UTC627
                received.insert(event.id);
25d14e49ab68atagen2026-09-24 11:07 UTC628
                import(link, frame[2].clone()).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC629
                if received.len() >= link.peer.query_limit {
25d14e49ab68atagen2026-09-24 11:07 UTC630
                    break false;
25d14e49ab68atagen2026-09-24 11:07 UTC631
                }
25d14e49ab68atagen2026-09-24 11:07 UTC632
            }
25d14e49ab68atagen2026-09-24 11:07 UTC633
            Some("EOSE") => break true,
25d14e49ab68atagen2026-09-24 11:07 UTC634
            Some("CLOSED") => {
25d14e49ab68atagen2026-09-24 11:07 UTC635
                let message = frame[2].as_str().unwrap_or_default();
25d14e49ab68atagen2026-09-24 11:07 UTC636
                if (message.starts_with("rate-limited:") || message.starts_with("error:"))
25d14e49ab68atagen2026-09-24 11:07 UTC637
                    && (message.contains("byte limit")
25d14e49ab68atagen2026-09-24 11:07 UTC638
                        || message.contains("result limit")
25d14e49ab68atagen2026-09-24 11:07 UTC639
                        || message.contains("too many results"))
25d14e49ab68atagen2026-09-24 11:07 UTC640
                {
25d14e49ab68atagen2026-09-24 11:07 UTC641
                    break false;
25d14e49ab68atagen2026-09-24 11:07 UTC642
                }
25d14e49ab68atagen2026-09-24 11:07 UTC643
                bail!("peer closed history with {message}");
25d14e49ab68atagen2026-09-24 11:07 UTC644
            }
25d14e49ab68atagen2026-09-24 11:07 UTC645
            _ => {}
25d14e49ab68atagen2026-09-24 11:07 UTC646
        }
25d14e49ab68atagen2026-09-24 11:07 UTC647
    };
25d14e49ab68atagen2026-09-24 11:07 UTC648
    connection.send(json!(["CLOSE", subscription])).await?;
25d14e49ab68atagen2026-09-24 11:07 UTC649
    Ok(complete)
25d14e49ab68atagen2026-09-24 11:07 UTC650
}