25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1 | use std::cmp::Ordering; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 2 | use std::collections::{BTreeSet, HashSet}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 3 | use std::error::Error; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 4 | use std::path::{Path, PathBuf}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 5 | use std::sync::Arc; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 6 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 7 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 8 | use anyhow::{Context as _, Result}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 9 | use nostr::event::{Event, EventId, Kind, Signature, Tags}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 10 | use nostr::filter::Filter; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 11 | use nostr::key::PublicKey; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 12 | use nostr::types::Timestamp; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 13 | use nostr_database::{DatabaseEventStatus, RejectedReason, SaveEventStatus}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 14 | use rusqlite::{ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 15 | Connection, OptionalExtension as _, Params, Row, Transaction, params, params_from_iter, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 16 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 17 | use tokio::sync::{Mutex, MutexGuard}; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 18 | use tokio::task::spawn_blocking; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 19 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 20 | use crate::config::relay_url; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 21 | use crate::query::EventQuery; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 22 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 23 | const SCHEMA: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 24 | CREATE TABLE IF NOT EXISTS events( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 25 | id BLOB PRIMARY KEY NOT NULL CHECK(length(id) = 32), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 26 | pubkey BLOB NOT NULL CHECK(length(pubkey) = 32), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 27 | created_at INTEGER NOT NULL CHECK(created_at > 0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 28 | kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 65535), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 29 | content TEXT NOT NULL, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 30 | tags JSONB NOT NULL, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 31 | sig BLOB NOT NULL CHECK(length(sig) = 64) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 32 | ) WITHOUT ROWID; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 33 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 34 | CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at DESC); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 35 | CREATE INDEX IF NOT EXISTS idx_events_pubkey_created_at ON events(pubkey, created_at DESC); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 36 | CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind_created_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 37 | ON events(pubkey, kind, created_at DESC); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 38 | CREATE INDEX IF NOT EXISTS idx_events_kind_created_at ON events(kind, created_at DESC); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 39 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 40 | DROP INDEX IF EXISTS idx_events_pubkey; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 41 | DROP INDEX IF EXISTS idx_events_kind; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 42 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 43 | CREATE TABLE IF NOT EXISTS event_tags( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 44 | event_id BLOB NOT NULL CHECK(length(event_id) = 32), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 45 | tag_name TEXT NOT NULL CHECK(length(tag_name) = 1), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 46 | tag_value TEXT NOT NULL, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 47 | FOREIGN KEY(event_id) REFERENCES events(id) ON DELETE CASCADE, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 48 | PRIMARY KEY(tag_name, tag_value, event_id) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 49 | ); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 50 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 51 | CREATE INDEX IF NOT EXISTS idx_tags_event_id ON event_tags(event_id); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 52 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 53 | CREATE TABLE IF NOT EXISTS deleted_ids( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 54 | event_id BLOB PRIMARY KEY NOT NULL CHECK(length(event_id) = 32) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 55 | ) WITHOUT ROWID; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 56 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 57 | CREATE TABLE IF NOT EXISTS deleted_coordinates( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 58 | pubkey BLOB NOT NULL CHECK(length(pubkey) = 32), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 59 | kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 65535), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 60 | identifier TEXT NOT NULL, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 61 | deleted_at INTEGER NOT NULL CHECK(deleted_at > 0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 62 | PRIMARY KEY(pubkey, kind, identifier) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 63 | ) WITHOUT ROWID; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 64 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 65 | DROP TRIGGER IF EXISTS sanctum_deleted_after_insert; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 66 | DROP TABLE IF EXISTS sanctum_deleted_events; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 67 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 68 | CREATE TABLE IF NOT EXISTS vineyard_delivery( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 69 | sequence INTEGER PRIMARY KEY AUTOINCREMENT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 70 | event_id BLOB NOT NULL UNIQUE REFERENCES events(id) ON DELETE CASCADE |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 71 | ); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 72 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 73 | CREATE TABLE IF NOT EXISTS vineyard_peers( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 74 | peer TEXT PRIMARY KEY, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 75 | delivered INTEGER NOT NULL DEFAULT 0 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 76 | ); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 77 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 78 | CREATE TABLE IF NOT EXISTS vineyard_checkpoints( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 79 | peer TEXT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 80 | scope TEXT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 81 | until INTEGER NOT NULL, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 82 | PRIMARY KEY(peer, scope) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 83 | ); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 84 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 85 | CREATE TABLE IF NOT EXISTS vineyard_retries( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 86 | peer TEXT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 87 | sequence INTEGER REFERENCES vineyard_delivery(sequence) ON DELETE CASCADE, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 88 | retry_at INTEGER, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 89 | error TEXT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 90 | PRIMARY KEY(peer, sequence) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 91 | ); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 92 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 93 | CREATE TABLE IF NOT EXISTS vineyard_discovered_peers( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 94 | signer BLOB, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 95 | url TEXT, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 96 | discovered_at INTEGER, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 97 | PRIMARY KEY(signer, url) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 98 | ) WITHOUT ROWID; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 99 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 100 | CREATE INDEX IF NOT EXISTS vineyard_retries_due |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 101 | ON vineyard_retries(peer, retry_at, sequence); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 102 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 103 | CREATE INDEX IF NOT EXISTS vineyard_discovered_urls |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 104 | ON vineyard_discovered_peers(url); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 105 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 106 | CREATE TRIGGER IF NOT EXISTS vineyard_delivery_after_insert |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 107 | AFTER INSERT ON events |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 108 | BEGIN |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 109 | INSERT OR IGNORE INTO vineyard_delivery(event_id) VALUES (NEW.id); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 110 | END; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 111 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 112 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 113 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 114 | const BACKFILL_DELIVERY: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 115 | INSERT INTO vineyard_delivery(event_id) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 116 | SELECT event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 117 | FROM events AS event |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 118 | LEFT JOIN vineyard_delivery AS delivery ON delivery.event_id = event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 119 | WHERE delivery.event_id IS NULL |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 120 | ORDER BY |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 121 | CASE WHEN event.kind = 30617 THEN 0 ELSE 1 END, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 122 | event.created_at, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 123 | event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 124 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 125 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 126 | const DISCOVERED_RELAY_TAGS: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 127 | SELECT event.pubkey, json_extract(tag.value, '$[1]'), event.created_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 128 | FROM events AS event, json_each(event.tags) AS tag |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 129 | WHERE event.kind = 10002 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 130 | AND json_extract(tag.value, '$[0]') = 'r' |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 131 | AND typeof(json_extract(tag.value, '$[1]')) = 'text' |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 132 | ORDER BY event.created_at DESC, event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 133 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 134 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 135 | const DUE_DELIVERIES: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 136 | SELECT delivery.sequence, delivery.event_id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 137 | FROM vineyard_delivery AS delivery |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 138 | JOIN vineyard_retries AS retry ON retry.sequence = delivery.sequence |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 139 | WHERE retry.peer = ?1 AND retry.retry_at <= ?2 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 140 | ORDER BY delivery.sequence |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 141 | LIMIT ?3 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 142 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 143 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 144 | const NEW_DELIVERIES: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 145 | SELECT sequence, event_id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 146 | FROM vineyard_delivery |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 147 | WHERE sequence > ?1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 148 | ORDER BY sequence |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 149 | LIMIT ?2 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 150 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 151 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 152 | const REPOSITORIES: &str = " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 153 | SELECT lower(hex(event.pubkey)), tag.tag_value, event.tags |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 154 | FROM events AS event |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 155 | JOIN event_tags AS tag ON event.id = tag.event_id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 156 | WHERE event.kind = 30617 AND tag.tag_name = 'd' |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 157 | ORDER BY 1, 2, event.created_at DESC, event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 158 | "; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 159 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 160 | type Delivery = (u64, EventId); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 161 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 162 | #[derive(Debug, Clone)] |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 163 | pub struct Journal { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 164 | connection: Arc<Mutex<Connection>>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 165 | writes: Arc<Mutex<()>>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 166 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 167 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 168 | struct StoredEvent { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 169 | id: Vec<u8>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 170 | pubkey: Vec<u8>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 171 | created_at: i64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 172 | kind: i64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 173 | content: String, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 174 | tags: String, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 175 | signature: Vec<u8>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 176 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 177 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 178 | impl Journal { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 179 | pub async fn open( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 180 | path: PathBuf, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 181 | max_database_bytes: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 182 | max_discovered_peers: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 183 | excluded_peers: Vec<String>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 184 | writes: Arc<Mutex<()>>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 185 | ) -> Result<Self> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 186 | let connection = spawn_blocking(move || { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 187 | open_connection( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 188 | &path, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 189 | max_database_bytes, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 190 | max_discovered_peers, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 191 | &excluded_peers, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 192 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 193 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 194 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 195 | .context("journal task panicked")??; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 196 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 197 | Ok(Self { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 198 | connection: Arc::new(Mutex::new(connection)), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 199 | writes, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 200 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 201 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 202 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 203 | pub async fn pending(&self, peer: &str, limit: usize) -> Result<Vec<Delivery>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 204 | let peer_url = peer.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 205 | self.run(move |connection| pending_deliveries(connection, &peer_url, limit)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 206 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 207 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 208 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 209 | pub async fn acknowledge(&self, peer: &str, sequence: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 210 | let peer_url = peer.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 211 | self.write(move |connection| acknowledge_delivery(connection, &peer_url, sequence)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 212 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 213 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 214 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 215 | pub async fn defer(&self, peer: &str, sequence: u64, error: &str, retry_at: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 216 | let peer_url = peer.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 217 | let message = error.chars().take(512).collect::<String>(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 218 | self.write(move |connection| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 219 | defer_delivery(connection, &peer_url, sequence, &message, retry_at) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 220 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 221 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 222 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 223 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 224 | pub async fn checkpoint(&self, peer: &str, scope: &str) -> Result<Option<u64>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 225 | let peer_url = peer.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 226 | let scope_name = scope.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 227 | self.run(move |connection| read_checkpoint(connection, &peer_url, &scope_name)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 228 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 229 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 230 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 231 | pub async fn advance(&self, peer: &str, scope: &str, until: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 232 | let peer_url = peer.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 233 | let scope_name = scope.to_owned(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 234 | self.write(move |connection| write_checkpoint(connection, &peer_url, &scope_name, until)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 235 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 236 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 237 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 238 | pub async fn repositories(&self, limit: usize) -> Result<Vec<(String, String)>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 239 | self.run(move |connection| list_repositories(connection, limit)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 240 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 241 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 242 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 243 | pub async fn healthy(&self) -> bool { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 244 | self.run(|connection| Ok(connection.query_row("SELECT 1", [], |_| Ok(()))?)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 245 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 246 | .is_ok() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 247 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 248 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 249 | pub async fn save_locked( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 250 | &self, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 251 | event: Event, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 252 | _write: &MutexGuard<'_, ()>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 253 | ) -> Result<SaveEventStatus> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 254 | self.run(move |connection| save_event(connection, &event)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 255 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 256 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 257 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 258 | pub async fn status(&self, event_id: EventId) -> Result<DatabaseEventStatus> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 259 | self.run(move |connection| event_status(connection, &event_id)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 260 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 261 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 262 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 263 | pub async fn event(&self, event_id: EventId) -> Result<Option<Event>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 264 | self.run(move |connection| event_by_id(connection, &event_id)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 265 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 266 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 267 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 268 | pub async fn count(&self, filter: Filter) -> Result<usize> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 269 | self.run(move |connection| Ok(query_events(connection, &filter, usize::MAX)?.len())) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 270 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 271 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 272 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 273 | pub async fn query(&self, filter: Filter, max_bytes: usize) -> Result<BTreeSet<Event>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 274 | self.run(move |connection| query_events(connection, &filter, max_bytes)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 275 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 276 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 277 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 278 | pub async fn replace_discovered_locked( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 279 | &self, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 280 | signer: PublicKey, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 281 | urls: Vec<String>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 282 | max: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 283 | _write: &MutexGuard<'_, ()>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 284 | ) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 285 | let signer_bytes = signer.as_bytes().to_vec(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 286 | self.run(move |connection| replace_discovered(connection, &signer_bytes, urls, max)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 287 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 288 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 289 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 290 | pub async fn discovered(&self, limit: usize) -> Result<Vec<String>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 291 | self.run(move |connection| list_discovered(connection, limit)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 292 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 293 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 294 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 295 | pub async fn forget_discovered(&self, urls: Vec<String>) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 296 | self.write(move |connection| forget_discovered(connection, urls)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 297 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 298 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 299 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 300 | async fn write<T, F>(&self, operation: F) -> Result<T> |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 301 | where |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 302 | T: Send + 'static, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 303 | F: FnOnce(&mut Connection) -> Result<T> + Send + 'static, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 304 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 305 | let _write = self.writes.lock().await; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 306 | self.run(operation).await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 307 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 308 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 309 | async fn run<T, F>(&self, operation: F) -> Result<T> |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 310 | where |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 311 | T: Send + 'static, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 312 | F: FnOnce(&mut Connection) -> Result<T> + Send + 'static, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 313 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 314 | let shared_connection = Arc::clone(&self.connection); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 315 | let mut connection = shared_connection.lock_owned().await; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 316 | spawn_blocking(move || operation(&mut connection)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 317 | .await |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 318 | .context("journal task panicked")? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 319 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 320 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 321 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 322 | fn open_connection( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 323 | path: &Path, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 324 | max_database_bytes: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 325 | max_discovered_peers: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 326 | excluded_peers: &[String], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 327 | ) -> Result<Connection> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 328 | let mut connection = |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 329 | Connection::open(path).with_context(|| format!("opening journal {}", path.display()))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 330 | configure(&connection, max_database_bytes)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 331 | migrate(&mut connection, max_discovered_peers, excluded_peers)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 332 | Ok(connection) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 333 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 334 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 335 | fn pending_deliveries(connection: &Connection, peer: &str, limit: usize) -> Result<Vec<Delivery>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 336 | if limit == 0 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 337 | return Ok(Vec::new()); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 338 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 339 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 340 | let retry_limit = limit / 2; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 341 | let mut pending = if retry_limit == 0 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 342 | Vec::new() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 343 | } else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 344 | read_deliveries( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 345 | connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 346 | DUE_DELIVERIES, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 347 | params![peer, unix_seconds(), integer(retry_limit)?], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 348 | )? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 349 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 350 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 351 | let remaining = limit - pending.len(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 352 | if remaining == 0 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 353 | return Ok(pending); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 354 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 355 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 356 | let delivered = read_delivered(connection, peer)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 357 | pending.extend(read_deliveries( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 358 | connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 359 | NEW_DELIVERIES, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 360 | params![delivered, integer(remaining)?], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 361 | )?); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 362 | Ok(pending) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 363 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 364 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 365 | fn read_deliveries<P>(connection: &Connection, sql: &str, parameters: P) -> Result<Vec<Delivery>> |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 366 | where |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 367 | P: Params, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 368 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 369 | let mut statement = connection.prepare(sql)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 370 | let mut rows = statement.query(parameters)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 371 | let mut deliveries = Vec::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 372 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 373 | while let Some(row) = rows.next()? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 374 | let sequence = row.get::<_, i64>(0)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 375 | let event_id = row.get::<_, Vec<u8>>(1)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 376 | deliveries.push(( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 377 | u64::try_from(sequence).context("negative delivery sequence")?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 378 | EventId::from_slice(&event_id) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 379 | .map_err(|error| anyhow::anyhow!("invalid event id {error}"))?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 380 | )); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 381 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 382 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 383 | Ok(deliveries) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 384 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 385 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 386 | fn read_delivered(connection: &Connection, peer: &str) -> Result<i64> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 387 | Ok(connection |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 388 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 389 | "SELECT delivered FROM vineyard_peers WHERE peer = ?1", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 390 | [peer], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 391 | |row| row.get(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 392 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 393 | .optional()? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 394 | .unwrap_or(0)) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 395 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 396 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 397 | fn acknowledge_delivery(connection: &mut Connection, peer: &str, sequence: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 398 | let stored_sequence = integer(sequence)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 399 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 400 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 401 | "DELETE FROM vineyard_retries WHERE peer = ?1 AND sequence = ?2", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 402 | params![peer, stored_sequence], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 403 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 404 | advance_delivery(&transaction, peer, stored_sequence)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 405 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 406 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 407 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 408 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 409 | fn defer_delivery( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 410 | connection: &mut Connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 411 | peer: &str, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 412 | sequence: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 413 | error: &str, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 414 | retry_at: u64, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 415 | ) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 416 | let stored_sequence = integer(sequence)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 417 | let stored_retry_at = integer(retry_at)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 418 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 419 | advance_delivery(&transaction, peer, stored_sequence)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 420 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 421 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 422 | INSERT INTO vineyard_retries(peer, sequence, retry_at, error) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 423 | VALUES (?1, ?2, ?3, ?4) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 424 | ON CONFLICT(peer, sequence) DO UPDATE SET |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 425 | retry_at = excluded.retry_at, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 426 | error = excluded.error |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 427 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 428 | params![peer, stored_sequence, stored_retry_at, error], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 429 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 430 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 431 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 432 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 433 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 434 | fn read_checkpoint(connection: &Connection, peer: &str, scope: &str) -> Result<Option<u64>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 435 | let checkpoint = connection |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 436 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 437 | "SELECT until FROM vineyard_checkpoints WHERE peer = ?1 AND scope = ?2", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 438 | params![peer, scope], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 439 | |row| row.get::<_, i64>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 440 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 441 | .optional()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 442 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 443 | checkpoint |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 444 | .map(|value| u64::try_from(value).context("negative checkpoint")) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 445 | .transpose() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 446 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 447 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 448 | fn write_checkpoint(connection: &Connection, peer: &str, scope: &str, until: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 449 | connection.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 450 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 451 | INSERT INTO vineyard_checkpoints(peer, scope, until) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 452 | VALUES (?1, ?2, ?3) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 453 | ON CONFLICT(peer, scope) DO UPDATE SET |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 454 | until = max(vineyard_checkpoints.until, excluded.until) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 455 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 456 | params![peer, scope, integer(until)?], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 457 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 458 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 459 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 460 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 461 | fn list_repositories(connection: &Connection, limit: usize) -> Result<Vec<(String, String)>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 462 | let mut statement = connection.prepare(REPOSITORIES)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 463 | let mut rows = statement.query([])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 464 | let mut repositories = Vec::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 465 | let mut seen = BTreeSet::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 466 | while let Some(row) = rows.next()? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 467 | let tags = serde_json::from_str::<Tags>(&row.get::<_, String>(2)?)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 468 | if tags |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 469 | .expiration() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 470 | .is_some_and(|expiration| expiration < Timestamp::now()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 471 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 472 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 473 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 474 | let repository = (row.get(0)?, row.get(1)?); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 475 | if !seen.insert(repository.clone()) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 476 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 477 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 478 | repositories.push(repository); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 479 | if repositories.len() == limit { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 480 | break; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 481 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 482 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 483 | Ok(repositories) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 484 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 485 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 486 | fn event_status(connection: &Connection, event_id: &EventId) -> Result<DatabaseEventStatus> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 487 | let deleted = connection.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 488 | "SELECT EXISTS(SELECT 1 FROM deleted_ids WHERE event_id = ?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 489 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 490 | |row| row.get::<_, bool>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 491 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 492 | if deleted { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 493 | return Ok(DatabaseEventStatus::Deleted); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 494 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 495 | let saved = connection.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 496 | "SELECT EXISTS(SELECT 1 FROM events WHERE id = ?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 497 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 498 | |row| row.get::<_, bool>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 499 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 500 | Ok(if saved { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 501 | DatabaseEventStatus::Saved |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 502 | } else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 503 | DatabaseEventStatus::NotExistent |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 504 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 505 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 506 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 507 | fn event_by_id(connection: &Connection, event_id: &EventId) -> Result<Option<Event>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 508 | connection |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 509 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 510 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 511 | SELECT id, pubkey, created_at, kind, content, tags, sig |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 512 | FROM events |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 513 | WHERE id = ?1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 514 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 515 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 516 | StoredEvent::read, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 517 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 518 | .optional()? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 519 | .map(StoredEvent::into_event) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 520 | .transpose() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 521 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 522 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 523 | fn save_event(connection: &mut Connection, event: &Event) -> Result<SaveEventStatus> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 524 | if event.kind.is_ephemeral() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 525 | return Ok(SaveEventStatus::Rejected(RejectedReason::Ephemeral)); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 526 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 527 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 528 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 529 | if event_exists(&transaction, &event.id)? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 530 | return Ok(SaveEventStatus::Rejected(RejectedReason::Duplicate)); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 531 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 532 | if event_deleted(&transaction, &event.id)? || coordinate_deleted(&transaction, event)? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 533 | return Ok(SaveEventStatus::Rejected(RejectedReason::Deleted)); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 534 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 535 | if replacement_rejected(&transaction, event)? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 536 | return Ok(SaveEventStatus::Rejected(RejectedReason::Replaced)); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 537 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 538 | if event.kind == Kind::EventDeletion && !apply_deletion(&transaction, event)? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 539 | return Ok(SaveEventStatus::Rejected(RejectedReason::InvalidDelete)); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 540 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 541 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 542 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 543 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 544 | INSERT INTO events(id, pubkey, created_at, kind, content, tags, sig) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 545 | VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 546 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 547 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 548 | event.id.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 549 | event.pubkey.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 550 | integer(event.created_at.as_secs())?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 551 | i64::from(event.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 552 | &event.content, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 553 | serde_json::to_string(&event.tags)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 554 | event.sig.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 555 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 556 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 557 | for tag in event.tags.iter() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 558 | let (Some(name), Some(value)) = (tag.single_letter_tag(), tag.content()) else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 559 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 560 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 561 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 562 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 563 | INSERT OR IGNORE INTO event_tags(event_id, tag_name, tag_value) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 564 | VALUES (?1, ?2, ?3) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 565 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 566 | params![event.id.as_bytes().as_slice(), name.to_string(), value], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 567 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 568 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 569 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 570 | Ok(SaveEventStatus::Success) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 571 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 572 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 573 | fn event_exists(transaction: &Transaction<'_>, event_id: &EventId) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 574 | Ok(transaction.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 575 | "SELECT EXISTS(SELECT 1 FROM events WHERE id = ?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 576 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 577 | |row| row.get(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 578 | )?) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 579 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 580 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 581 | fn event_deleted(transaction: &Transaction<'_>, event_id: &EventId) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 582 | Ok(transaction.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 583 | "SELECT EXISTS(SELECT 1 FROM deleted_ids WHERE event_id = ?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 584 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 585 | |row| row.get(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 586 | )?) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 587 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 588 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 589 | fn coordinate_deleted(transaction: &Transaction<'_>, event: &Event) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 590 | let Some(coordinate) = event.coordinate() else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 591 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 592 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 593 | let deleted_at = transaction |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 594 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 595 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 596 | SELECT deleted_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 597 | FROM deleted_coordinates |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 598 | WHERE pubkey = ?1 AND kind = ?2 AND identifier = ?3 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 599 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 600 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 601 | coordinate.public_key.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 602 | i64::from(coordinate.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 603 | coordinate.identifier, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 604 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 605 | |row| row.get::<_, i64>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 606 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 607 | .optional()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 608 | Ok(deleted_at.is_some_and(|stored_deleted_at| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 609 | u64::try_from(stored_deleted_at).map_or(true, |value| event.created_at.as_secs() <= value) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 610 | })) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 611 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 612 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 613 | fn replacement_rejected(transaction: &Transaction<'_>, event: &Event) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 614 | let existing = if event.kind.is_replaceable() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 615 | transaction |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 616 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 617 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 618 | SELECT id, created_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 619 | FROM events |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 620 | WHERE pubkey = ?1 AND kind = ?2 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 621 | LIMIT 1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 622 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 623 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 624 | event.pubkey.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 625 | i64::from(event.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 626 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 627 | |row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, i64>(1)?)), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 628 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 629 | .optional()? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 630 | } else if event.kind.is_addressable() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 631 | let Some(identifier) = event.tags.identifier() else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 632 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 633 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 634 | transaction |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 635 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 636 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 637 | SELECT event.id, event.created_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 638 | FROM events AS event |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 639 | JOIN event_tags AS tag ON tag.event_id = event.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 640 | WHERE event.pubkey = ?1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 641 | AND event.kind = ?2 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 642 | AND tag.tag_name = 'd' |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 643 | AND tag.tag_value = ?3 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 644 | LIMIT 1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 645 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 646 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 647 | event.pubkey.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 648 | i64::from(event.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 649 | identifier, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 650 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 651 | |row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, i64>(1)?)), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 652 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 653 | .optional()? |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 654 | } else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 655 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 656 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 657 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 658 | let Some((stored_id, stored_at)) = existing else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 659 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 660 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 661 | let incoming_at = integer(event.created_at.as_secs())?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 662 | match stored_at.cmp(&incoming_at) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 663 | Ordering::Greater => Ok(true), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 664 | Ordering::Equal if stored_id.as_slice() < event.id.as_bytes().as_slice() => Ok(true), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 665 | Ordering::Equal | Ordering::Less => { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 666 | transaction.execute("DELETE FROM events WHERE id = ?1", [stored_id])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 667 | Ok(false) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 668 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 669 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 670 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 671 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 672 | fn apply_deletion(transaction: &Transaction<'_>, event: &Event) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 673 | for event_id in event.tags.event_ids() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 674 | let owner = transaction |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 675 | .query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 676 | "SELECT pubkey FROM events WHERE id = ?1", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 677 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 678 | |row| row.get::<_, Vec<u8>>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 679 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 680 | .optional()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 681 | if owner.is_some_and(|saved_owner| saved_owner.as_slice() != event.pubkey.as_bytes()) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 682 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 683 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 684 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 685 | "INSERT OR IGNORE INTO deleted_ids(event_id) VALUES (?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 686 | [event_id.as_bytes().as_slice()], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 687 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 688 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 689 | "DELETE FROM events WHERE id = ?1 AND pubkey = ?2", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 690 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 691 | event_id.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 692 | event.pubkey.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 693 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 694 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 695 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 696 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 697 | for coordinate in event.tags.coordinates() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 698 | if coordinate.public_key != event.pubkey { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 699 | return Ok(false); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 700 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 701 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 702 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 703 | INSERT INTO deleted_coordinates(pubkey, kind, identifier, deleted_at) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 704 | VALUES (?1, ?2, ?3, ?4) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 705 | ON CONFLICT(pubkey, kind, identifier) DO UPDATE SET |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 706 | deleted_at = max(deleted_coordinates.deleted_at, excluded.deleted_at) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 707 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 708 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 709 | coordinate.public_key.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 710 | i64::from(coordinate.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 711 | &coordinate.identifier, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 712 | integer(event.created_at.as_secs())?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 713 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 714 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 715 | if coordinate.kind.is_replaceable() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 716 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 717 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 718 | DELETE FROM events |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 719 | WHERE pubkey = ?1 AND kind = ?2 AND created_at <= ?3 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 720 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 721 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 722 | coordinate.public_key.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 723 | i64::from(coordinate.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 724 | integer(event.created_at.as_secs())?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 725 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 726 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 727 | } else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 728 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 729 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 730 | DELETE FROM events |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 731 | WHERE id IN ( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 732 | SELECT stored.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 733 | FROM events AS stored |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 734 | JOIN event_tags AS tag ON tag.event_id = stored.id |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 735 | WHERE stored.pubkey = ?1 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 736 | AND stored.kind = ?2 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 737 | AND stored.created_at <= ?3 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 738 | AND tag.tag_name = 'd' |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 739 | AND tag.tag_value = ?4 |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 740 | ) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 741 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 742 | params![ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 743 | coordinate.public_key.as_bytes().as_slice(), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 744 | i64::from(coordinate.kind.as_u16()), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 745 | integer(event.created_at.as_secs())?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 746 | &coordinate.identifier, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 747 | ], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 748 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 749 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 750 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 751 | Ok(true) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 752 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 753 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 754 | fn query_events( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 755 | connection: &Connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 756 | filter: &Filter, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 757 | max_bytes: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 758 | ) -> Result<BTreeSet<Event>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 759 | let query = EventQuery::new(filter)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 760 | if query.limit == 0 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 761 | return Ok(BTreeSet::new()); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 762 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 763 | let mut statement = connection.prepare(&query.sql)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 764 | let mut rows = statement.query(params_from_iter(query.parameters))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 765 | let mut events = BTreeSet::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 766 | let mut bytes = 0_usize; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 767 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 768 | while let Some(row) = rows.next()? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 769 | let event = StoredEvent::read(row)?.into_event()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 770 | if event.is_expired() { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 771 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 772 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 773 | bytes = bytes |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 774 | .checked_add(event.as_json().len()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 775 | .context("history byte count overflow")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 776 | if bytes > max_bytes { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 777 | anyhow::bail!("history byte limit exceeded"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 778 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 779 | events.insert(event); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 780 | if events.len() == query.limit { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 781 | break; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 782 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 783 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 784 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 785 | Ok(events) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 786 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 787 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 788 | fn replace_discovered( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 789 | connection: &mut Connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 790 | signer: &[u8], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 791 | urls: Vec<String>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 792 | max: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 793 | ) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 794 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 795 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 796 | "DELETE FROM vineyard_discovered_peers WHERE signer = ?1", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 797 | [signer], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 798 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 799 | let stored_count = transaction.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 800 | "SELECT count(DISTINCT url) FROM vineyard_discovered_peers", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 801 | [], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 802 | |row| row.get::<_, i64>(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 803 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 804 | let count = usize::try_from(stored_count).context("negative discovered peer count")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 805 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 806 | for url in urls.into_iter().take(max.saturating_sub(count)) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 807 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 808 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 809 | INSERT OR IGNORE INTO vineyard_discovered_peers(signer, url, discovered_at) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 810 | VALUES (?1, ?2, unixepoch()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 811 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 812 | params![signer, url], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 813 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 814 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 815 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 816 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 817 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 818 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 819 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 820 | fn list_discovered(connection: &Connection, limit: usize) -> Result<Vec<String>> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 821 | let mut statement = connection |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 822 | .prepare("SELECT DISTINCT url FROM vineyard_discovered_peers ORDER BY url LIMIT ?1")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 823 | let mut rows = statement.query([integer(limit)?])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 824 | let mut urls = Vec::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 825 | while let Some(row) = rows.next()? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 826 | urls.push(row.get(0)?); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 827 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 828 | Ok(urls) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 829 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 830 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 831 | fn forget_discovered(connection: &mut Connection, urls: Vec<String>) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 832 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 833 | for url in urls { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 834 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 835 | "DELETE FROM vineyard_discovered_peers WHERE url = ?1", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 836 | [url], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 837 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 838 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 839 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 840 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 841 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 842 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 843 | impl StoredEvent { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 844 | fn read(row: &Row<'_>) -> rusqlite::Result<Self> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 845 | Ok(Self { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 846 | id: row.get(0)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 847 | pubkey: row.get(1)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 848 | created_at: row.get(2)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 849 | kind: row.get(3)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 850 | content: row.get(4)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 851 | tags: row.get(5)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 852 | signature: row.get(6)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 853 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 854 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 855 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 856 | fn into_event(self) -> Result<Event> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 857 | let id = EventId::from_slice(&self.id).context("invalid stored event id")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 858 | let pubkey = PublicKey::from_slice(&self.pubkey).context("invalid stored public key")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 859 | let created_at: Timestamp = self |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 860 | .created_at |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 861 | .try_into() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 862 | .context("invalid event timestamp")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 863 | let kind = Kind::from_u16(self.kind.try_into().context("invalid stored event kind")?); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 864 | let tags: Tags = serde_json::from_str(&self.tags).context("invalid stored event tags")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 865 | let signature = |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 866 | Signature::from_slice(&self.signature).context("invalid stored event signature")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 867 | Ok(Event::new( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 868 | id, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 869 | pubkey, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 870 | created_at, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 871 | kind, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 872 | tags, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 873 | self.content, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 874 | signature, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 875 | )) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 876 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 877 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 878 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 879 | fn configure(connection: &Connection, max_database_bytes: u64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 880 | connection.busy_timeout(Duration::from_secs(5))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 881 | connection.pragma_update(None, "journal_mode", "WAL")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 882 | let journal_mode: String = |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 883 | connection.pragma_query_value(None, "journal_mode", |row| row.get(0))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 884 | if !journal_mode.eq_ignore_ascii_case("wal") { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 885 | anyhow::bail!("journal mode is not wal"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 886 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 887 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 888 | connection.pragma_update(None, "synchronous", "FULL")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 889 | connection.pragma_update(None, "foreign_keys", "ON")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 890 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 891 | let stored_page_size = |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 892 | connection.query_row("PRAGMA page_size", [], |row| row.get::<_, i64>(0))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 893 | let page_size = u64::try_from(stored_page_size).context("invalid database page size")?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 894 | let page_budget = max_database_bytes / page_size; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 895 | if page_budget == 0 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 896 | anyhow::bail!("database budget is smaller than one page"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 897 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 898 | let max_pages = integer(page_budget)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 899 | let page_count = connection.query_row("PRAGMA page_count", [], |row| row.get::<_, i64>(0))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 900 | if page_count > max_pages { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 901 | anyhow::bail!("database exceeds size budget"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 902 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 903 | connection.pragma_update(None, "max_page_count", max_pages)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 904 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 905 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 906 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 907 | fn migrate( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 908 | connection: &mut Connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 909 | max_discovered_peers: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 910 | excluded_peers: &[String], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 911 | ) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 912 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 913 | migrate_legacy_tables(&transaction)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 914 | transaction.execute_batch(SCHEMA)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 915 | transaction.execute(BACKFILL_DELIVERY, [])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 916 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 917 | backfill_discovered(connection, max_discovered_peers, excluded_peers)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 918 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 919 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 920 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 921 | fn migrate_legacy_tables(transaction: &Transaction<'_>) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 922 | for (old, new) in [ |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 923 | ("sanctum_delivery", "vineyard_delivery"), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 924 | ("sanctum_peers", "vineyard_peers"), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 925 | ("sanctum_checkpoints", "vineyard_checkpoints"), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 926 | ("sanctum_retries", "vineyard_retries"), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 927 | ("sanctum_discovered_peers", "vineyard_discovered_peers"), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 928 | ] { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 929 | let old_exists = table_exists(transaction, old)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 930 | let new_exists = table_exists(transaction, new)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 931 | if old_exists && new_exists { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 932 | anyhow::bail!("database contains both {old} and {new}"); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 933 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 934 | if old_exists { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 935 | transaction.execute_batch(&format!("ALTER TABLE {old} RENAME TO {new}"))?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 936 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 937 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 938 | transaction.execute_batch( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 939 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 940 | DROP TRIGGER IF EXISTS sanctum_delivery_after_insert; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 941 | DROP INDEX IF EXISTS sanctum_retries_due; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 942 | DROP INDEX IF EXISTS sanctum_discovered_urls; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 943 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 944 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 945 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 946 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 947 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 948 | fn table_exists(transaction: &Transaction<'_>, name: &str) -> Result<bool> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 949 | Ok(transaction.query_row( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 950 | "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?1)", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 951 | [name], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 952 | |row| row.get(0), |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 953 | )?) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 954 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 955 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 956 | fn backfill_discovered( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 957 | connection: &mut Connection, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 958 | max_discovered_peers: usize, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 959 | excluded_peers: &[String], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 960 | ) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 961 | let excluded = excluded_peers.iter().cloned().collect::<HashSet<_>>(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 962 | let relay_tags = { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 963 | let mut statement = connection.prepare(DISCOVERED_RELAY_TAGS)?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 964 | let mut rows = statement.query([])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 965 | let mut relay_tags = Vec::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 966 | while let Some(row) = rows.next()? { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 967 | relay_tags.push(( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 968 | row.get::<_, Vec<u8>>(0)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 969 | row.get::<_, String>(1)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 970 | row.get::<_, i64>(2)?, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 971 | )); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 972 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 973 | relay_tags |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 974 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 975 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 976 | let transaction = connection.transaction()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 977 | transaction.execute("DELETE FROM vineyard_discovered_peers", [])?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 978 | let mut urls = HashSet::new(); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 979 | for (signer, raw_url, discovered_at) in relay_tags { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 980 | let Some(url) = relay_url(&raw_url).map(|url| url.to_string()) else { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 981 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 982 | }; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 983 | if excluded.contains(&url) || (!urls.contains(&url) && urls.len() >= max_discovered_peers) { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 984 | continue; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 985 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 986 | urls.insert(url.clone()); |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 987 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 988 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 989 | INSERT OR IGNORE INTO vineyard_discovered_peers(signer, url, discovered_at) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 990 | VALUES (?1, ?2, ?3) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 991 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 992 | params![signer, url, discovered_at], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 993 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 994 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 995 | transaction.commit()?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 996 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 997 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 998 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 999 | fn advance_delivery(transaction: &Transaction<'_>, peer: &str, sequence: i64) -> Result<()> { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1000 | transaction.execute( |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1001 | " |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1002 | INSERT INTO vineyard_peers(peer, delivered) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1003 | VALUES (?1, ?2) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1004 | ON CONFLICT(peer) DO UPDATE SET |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1005 | delivered = max(vineyard_peers.delivered, excluded.delivered) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1006 | ", |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1007 | params![peer, sequence], |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1008 | )?; |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1009 | Ok(()) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1010 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1011 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1012 | fn integer<T>(value: T) -> Result<i64> |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1013 | where |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1014 | T: TryInto<i64>, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1015 | T::Error: Error + Send + Sync + 'static, |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1016 | { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1017 | value |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1018 | .try_into() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1019 | .context("value exceeds sqlite integer range") |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1020 | } |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1021 | |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1022 | fn unix_seconds() -> i64 { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1023 | SystemTime::now() |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1024 | .duration_since(UNIX_EPOCH) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1025 | .map_or(0, |duration| { |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1026 | i64::try_from(duration.as_secs()).unwrap_or(i64::MAX) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1027 | }) |
25d14e49ab68 | atagen | 2026-09-24 11:07 UTC | 1028 | } |