atagen

vineyard

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

Blame: src/store.rs

25d14e49ab68atagen2026-09-24 11:07 UTC1
use std::collections::{BTreeSet, HashSet};
25d14e49ab68atagen2026-09-24 11:07 UTC2
use std::fs::{File, OpenOptions, create_dir_all};
25d14e49ab68atagen2026-09-24 11:07 UTC3
use std::future::Future;
25d14e49ab68atagen2026-09-24 11:07 UTC4
use std::io::Error as IoError;
25d14e49ab68atagen2026-09-24 11:07 UTC5
use std::iter::once;
25d14e49ab68atagen2026-09-24 11:07 UTC6
use std::path::PathBuf;
25d14e49ab68atagen2026-09-24 11:07 UTC7
use std::pin::Pin;
25d14e49ab68atagen2026-09-24 11:07 UTC8
use std::sync::Arc;
25d14e49ab68atagen2026-09-24 11:07 UTC9
25d14e49ab68atagen2026-09-24 11:07 UTC10
use anyhow::Context as _;
25d14e49ab68atagen2026-09-24 11:07 UTC11
use fs2::FileExt as _;
25d14e49ab68atagen2026-09-24 11:07 UTC12
use nostr::event::{Event, EventId, Kind};
25d14e49ab68atagen2026-09-24 11:07 UTC13
use nostr::filter::Filter;
25d14e49ab68atagen2026-09-24 11:07 UTC14
use nostr::nips::nip65::extract_relay_list;
25d14e49ab68atagen2026-09-24 11:07 UTC15
use nostr_database::error::Error;
25d14e49ab68atagen2026-09-24 11:07 UTC16
use nostr_database::{DatabaseEventStatus, Features, NostrDatabase, SaveEventStatus};
25d14e49ab68atagen2026-09-24 11:07 UTC17
use tokio::sync::Mutex;
25d14e49ab68atagen2026-09-24 11:07 UTC18
25d14e49ab68atagen2026-09-24 11:07 UTC19
use crate::config::{Config, relay_url};
25d14e49ab68atagen2026-09-24 11:07 UTC20
use crate::journal::Journal;
25d14e49ab68atagen2026-09-24 11:07 UTC21
25d14e49ab68atagen2026-09-24 11:07 UTC22
type DatabaseFuture<'database, T> =
25d14e49ab68atagen2026-09-24 11:07 UTC23
    Pin<Box<dyn Future<Output = Result<T, Error>> + Send + 'database>>;
25d14e49ab68atagen2026-09-24 11:07 UTC24
25d14e49ab68atagen2026-09-24 11:07 UTC25
#[derive(Debug)]
25d14e49ab68atagen2026-09-24 11:07 UTC26
pub struct Store {
25d14e49ab68atagen2026-09-24 11:07 UTC27
    pub(crate) journal: Journal,
25d14e49ab68atagen2026-09-24 11:07 UTC28
    path: PathBuf,
25d14e49ab68atagen2026-09-24 11:07 UTC29
    min_free_bytes: u64,
25d14e49ab68atagen2026-09-24 11:07 UTC30
    max_query_bytes: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC31
    max_repositories: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC32
    max_discovered_peers: usize,
25d14e49ab68atagen2026-09-24 11:07 UTC33
    excluded_peers: HashSet<String>,
25d14e49ab68atagen2026-09-24 11:07 UTC34
    writer: Arc<Mutex<()>>,
25d14e49ab68atagen2026-09-24 11:07 UTC35
    _lock: File,
25d14e49ab68atagen2026-09-24 11:07 UTC36
}
25d14e49ab68atagen2026-09-24 11:07 UTC37
25d14e49ab68atagen2026-09-24 11:07 UTC38
impl Store {
25d14e49ab68atagen2026-09-24 11:07 UTC39
    pub async fn open(config: &Config) -> anyhow::Result<Arc<Self>> {
25d14e49ab68atagen2026-09-24 11:07 UTC40
        if let Some(parent) = config
25d14e49ab68atagen2026-09-24 11:07 UTC41
            .database
25d14e49ab68atagen2026-09-24 11:07 UTC42
            .parent()
25d14e49ab68atagen2026-09-24 11:07 UTC43
            .filter(|parent| !parent.as_os_str().is_empty())
25d14e49ab68atagen2026-09-24 11:07 UTC44
        {
25d14e49ab68atagen2026-09-24 11:07 UTC45
            create_dir_all(parent).context("creating database directory")?;
25d14e49ab68atagen2026-09-24 11:07 UTC46
        }
25d14e49ab68atagen2026-09-24 11:07 UTC47
        let mut lock_name = config.database.as_os_str().to_os_string();
25d14e49ab68atagen2026-09-24 11:07 UTC48
        lock_name.push(".lock");
25d14e49ab68atagen2026-09-24 11:07 UTC49
        let mut lock_options = OpenOptions::new();
25d14e49ab68atagen2026-09-24 11:07 UTC50
        lock_options
25d14e49ab68atagen2026-09-24 11:07 UTC51
            .read(true)
25d14e49ab68atagen2026-09-24 11:07 UTC52
            .write(true)
25d14e49ab68atagen2026-09-24 11:07 UTC53
            .create(true)
25d14e49ab68atagen2026-09-24 11:07 UTC54
            .truncate(false);
25d14e49ab68atagen2026-09-24 11:07 UTC55
        #[cfg(unix)]
25d14e49ab68atagen2026-09-24 11:07 UTC56
        {
25d14e49ab68atagen2026-09-24 11:07 UTC57
            use std::os::unix::fs::OpenOptionsExt as _;
25d14e49ab68atagen2026-09-24 11:07 UTC58
            lock_options.mode(0o600)
25d14e49ab68atagen2026-09-24 11:07 UTC59
        };
25d14e49ab68atagen2026-09-24 11:07 UTC60
        let lock = lock_options
25d14e49ab68atagen2026-09-24 11:07 UTC61
            .open(PathBuf::from(lock_name))
25d14e49ab68atagen2026-09-24 11:07 UTC62
            .context("opening database lock")?;
25d14e49ab68atagen2026-09-24 11:07 UTC63
        lock.try_lock_exclusive()
25d14e49ab68atagen2026-09-24 11:07 UTC64
            .context("database is already in use")?;
25d14e49ab68atagen2026-09-24 11:07 UTC65
25d14e49ab68atagen2026-09-24 11:07 UTC66
        let excluded_peers = once(&config.public_url)
25d14e49ab68atagen2026-09-24 11:07 UTC67
            .chain(config.peers.iter().map(|peer| &peer.url))
25d14e49ab68atagen2026-09-24 11:07 UTC68
            .map(|url| relay_url(url).map(|parsed| parsed.to_string()))
25d14e49ab68atagen2026-09-24 11:07 UTC69
            .collect::<Option<HashSet<_>>>()
25d14e49ab68atagen2026-09-24 11:07 UTC70
            .context("validated relay url became invalid")?;
25d14e49ab68atagen2026-09-24 11:07 UTC71
        let writer = Arc::new(Mutex::new(()));
25d14e49ab68atagen2026-09-24 11:07 UTC72
        let journal = Journal::open(
25d14e49ab68atagen2026-09-24 11:07 UTC73
            config.database.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC74
            config.limits.max_database_bytes,
25d14e49ab68atagen2026-09-24 11:07 UTC75
            config
25d14e49ab68atagen2026-09-24 11:07 UTC76
                .limits
25d14e49ab68atagen2026-09-24 11:07 UTC77
                .max_discovered_peers
25d14e49ab68atagen2026-09-24 11:07 UTC78
                .saturating_add(excluded_peers.len()),
25d14e49ab68atagen2026-09-24 11:07 UTC79
            excluded_peers.iter().cloned().collect(),
25d14e49ab68atagen2026-09-24 11:07 UTC80
            Arc::clone(&writer),
25d14e49ab68atagen2026-09-24 11:07 UTC81
        )
25d14e49ab68atagen2026-09-24 11:07 UTC82
        .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC83
        journal
25d14e49ab68atagen2026-09-24 11:07 UTC84
            .forget_discovered(excluded_peers.iter().cloned().collect())
25d14e49ab68atagen2026-09-24 11:07 UTC85
            .await?;
25d14e49ab68atagen2026-09-24 11:07 UTC86
        tracing::info!(path = %config.database.display(), "opened event database");
25d14e49ab68atagen2026-09-24 11:07 UTC87
        Ok(Arc::new(Self {
25d14e49ab68atagen2026-09-24 11:07 UTC88
            journal,
25d14e49ab68atagen2026-09-24 11:07 UTC89
            path: config.database.clone(),
25d14e49ab68atagen2026-09-24 11:07 UTC90
            min_free_bytes: config.limits.min_free_bytes,
25d14e49ab68atagen2026-09-24 11:07 UTC91
            max_query_bytes: config.limits.max_query_bytes,
25d14e49ab68atagen2026-09-24 11:07 UTC92
            max_repositories: config.limits.max_repositories,
25d14e49ab68atagen2026-09-24 11:07 UTC93
            max_discovered_peers: config.limits.max_discovered_peers,
25d14e49ab68atagen2026-09-24 11:07 UTC94
            excluded_peers,
25d14e49ab68atagen2026-09-24 11:07 UTC95
            writer,
25d14e49ab68atagen2026-09-24 11:07 UTC96
            _lock: lock,
25d14e49ab68atagen2026-09-24 11:07 UTC97
        }))
25d14e49ab68atagen2026-09-24 11:07 UTC98
    }
25d14e49ab68atagen2026-09-24 11:07 UTC99
25d14e49ab68atagen2026-09-24 11:07 UTC100
    pub async fn healthy(&self) -> bool {
25d14e49ab68atagen2026-09-24 11:07 UTC101
        self.journal.healthy().await
25d14e49ab68atagen2026-09-24 11:07 UTC102
    }
25d14e49ab68atagen2026-09-24 11:07 UTC103
25d14e49ab68atagen2026-09-24 11:07 UTC104
    async fn query_events(&self, filter: Filter) -> Result<BTreeSet<Event>, Error> {
25d14e49ab68atagen2026-09-24 11:07 UTC105
        self.journal
25d14e49ab68atagen2026-09-24 11:07 UTC106
            .query(filter, self.max_query_bytes)
25d14e49ab68atagen2026-09-24 11:07 UTC107
            .await
25d14e49ab68atagen2026-09-24 11:07 UTC108
            .map_err(storage_error)
25d14e49ab68atagen2026-09-24 11:07 UTC109
    }
25d14e49ab68atagen2026-09-24 11:07 UTC110
}
25d14e49ab68atagen2026-09-24 11:07 UTC111
25d14e49ab68atagen2026-09-24 11:07 UTC112
fn storage_error(error: anyhow::Error) -> Error {
25d14e49ab68atagen2026-09-24 11:07 UTC113
    Error::storage(IoError::other(error))
25d14e49ab68atagen2026-09-24 11:07 UTC114
}
25d14e49ab68atagen2026-09-24 11:07 UTC115
25d14e49ab68atagen2026-09-24 11:07 UTC116
impl NostrDatabase for Store {
25d14e49ab68atagen2026-09-24 11:07 UTC117
    fn backend(&self) -> &'static str {
25d14e49ab68atagen2026-09-24 11:07 UTC118
        "vineyard-sqlite"
25d14e49ab68atagen2026-09-24 11:07 UTC119
    }
25d14e49ab68atagen2026-09-24 11:07 UTC120
25d14e49ab68atagen2026-09-24 11:07 UTC121
    fn features(&self) -> Features {
25d14e49ab68atagen2026-09-24 11:07 UTC122
        Features {
25d14e49ab68atagen2026-09-24 11:07 UTC123
            persistent: true,
25d14e49ab68atagen2026-09-24 11:07 UTC124
            event_expiration: false,
25d14e49ab68atagen2026-09-24 11:07 UTC125
            full_text_search: false,
25d14e49ab68atagen2026-09-24 11:07 UTC126
            request_to_vanish: false,
25d14e49ab68atagen2026-09-24 11:07 UTC127
        }
25d14e49ab68atagen2026-09-24 11:07 UTC128
    }
25d14e49ab68atagen2026-09-24 11:07 UTC129
25d14e49ab68atagen2026-09-24 11:07 UTC130
    fn save_event<'database>(
25d14e49ab68atagen2026-09-24 11:07 UTC131
        &'database self,
25d14e49ab68atagen2026-09-24 11:07 UTC132
        event: &'database Event,
25d14e49ab68atagen2026-09-24 11:07 UTC133
    ) -> DatabaseFuture<'database, SaveEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC134
        Box::pin(async move {
25d14e49ab68atagen2026-09-24 11:07 UTC135
            let writer = self.writer.lock().await;
25d14e49ab68atagen2026-09-24 11:07 UTC136
            if event.kind.as_u16() == 30617 {
25d14e49ab68atagen2026-09-24 11:07 UTC137
                if let Some(identifier) = event.tags.identifier() {
25d14e49ab68atagen2026-09-24 11:07 UTC138
                    let registered = self
25d14e49ab68atagen2026-09-24 11:07 UTC139
                        .query_events(
25d14e49ab68atagen2026-09-24 11:07 UTC140
                            Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC141
                                .kind(event.kind)
25d14e49ab68atagen2026-09-24 11:07 UTC142
                                .author(event.pubkey)
25d14e49ab68atagen2026-09-24 11:07 UTC143
                                .identifier(identifier)
25d14e49ab68atagen2026-09-24 11:07 UTC144
                                .limit(1),
25d14e49ab68atagen2026-09-24 11:07 UTC145
                        )
25d14e49ab68atagen2026-09-24 11:07 UTC146
                        .await?
25d14e49ab68atagen2026-09-24 11:07 UTC147
                        .len();
25d14e49ab68atagen2026-09-24 11:07 UTC148
                    if registered == 0
25d14e49ab68atagen2026-09-24 11:07 UTC149
                        && self
25d14e49ab68atagen2026-09-24 11:07 UTC150
                            .query_events(
25d14e49ab68atagen2026-09-24 11:07 UTC151
                                Filter::new()
25d14e49ab68atagen2026-09-24 11:07 UTC152
                                    .kind(event.kind)
25d14e49ab68atagen2026-09-24 11:07 UTC153
                                    .limit(self.max_repositories + 1),
25d14e49ab68atagen2026-09-24 11:07 UTC154
                            )
25d14e49ab68atagen2026-09-24 11:07 UTC155
                            .await?
25d14e49ab68atagen2026-09-24 11:07 UTC156
                            .len()
25d14e49ab68atagen2026-09-24 11:07 UTC157
                            >= self.max_repositories
25d14e49ab68atagen2026-09-24 11:07 UTC158
                    {
25d14e49ab68atagen2026-09-24 11:07 UTC159
                        return Err(Error::storage(IoError::other(
25d14e49ab68atagen2026-09-24 11:07 UTC160
                            "repository directory is full",
25d14e49ab68atagen2026-09-24 11:07 UTC161
                        )));
25d14e49ab68atagen2026-09-24 11:07 UTC162
                    }
25d14e49ab68atagen2026-09-24 11:07 UTC163
                }
25d14e49ab68atagen2026-09-24 11:07 UTC164
            }
25d14e49ab68atagen2026-09-24 11:07 UTC165
            if self.journal.status(event.id).await.map_err(storage_error)?
25d14e49ab68atagen2026-09-24 11:07 UTC166
                != DatabaseEventStatus::Saved
25d14e49ab68atagen2026-09-24 11:07 UTC167
            {
25d14e49ab68atagen2026-09-24 11:07 UTC168
                let free = fs2::available_space(&self.path).map_err(Error::io)?;
25d14e49ab68atagen2026-09-24 11:07 UTC169
                let event_bytes = u64::try_from(event.as_json().len())
25d14e49ab68atagen2026-09-24 11:07 UTC170
                    .map_err(|error| Error::storage(IoError::other(error.to_string())))?;
25d14e49ab68atagen2026-09-24 11:07 UTC171
                if free < self.min_free_bytes.saturating_add(event_bytes) {
25d14e49ab68atagen2026-09-24 11:07 UTC172
                    return Err(Error::storage(IoError::other("storage reserve reached")));
25d14e49ab68atagen2026-09-24 11:07 UTC173
                }
25d14e49ab68atagen2026-09-24 11:07 UTC174
            }
25d14e49ab68atagen2026-09-24 11:07 UTC175
            let status = self
25d14e49ab68atagen2026-09-24 11:07 UTC176
                .journal
25d14e49ab68atagen2026-09-24 11:07 UTC177
                .save_locked(event.clone(), &writer)
25d14e49ab68atagen2026-09-24 11:07 UTC178
                .await
25d14e49ab68atagen2026-09-24 11:07 UTC179
                .map_err(storage_error)?;
25d14e49ab68atagen2026-09-24 11:07 UTC180
            if status.is_success() && event.kind == Kind::RelayList {
25d14e49ab68atagen2026-09-24 11:07 UTC181
                let urls = extract_relay_list(event)
25d14e49ab68atagen2026-09-24 11:07 UTC182
                    .map(|(url, _)| url.to_string())
25d14e49ab68atagen2026-09-24 11:07 UTC183
                    .filter(|url| !self.excluded_peers.contains(url))
25d14e49ab68atagen2026-09-24 11:07 UTC184
                    .collect();
25d14e49ab68atagen2026-09-24 11:07 UTC185
                if let Err(error) = self
25d14e49ab68atagen2026-09-24 11:07 UTC186
                    .journal
25d14e49ab68atagen2026-09-24 11:07 UTC187
                    .replace_discovered_locked(
25d14e49ab68atagen2026-09-24 11:07 UTC188
                        event.pubkey,
25d14e49ab68atagen2026-09-24 11:07 UTC189
                        urls,
25d14e49ab68atagen2026-09-24 11:07 UTC190
                        self.max_discovered_peers,
25d14e49ab68atagen2026-09-24 11:07 UTC191
                        &writer,
25d14e49ab68atagen2026-09-24 11:07 UTC192
                    )
25d14e49ab68atagen2026-09-24 11:07 UTC193
                    .await
25d14e49ab68atagen2026-09-24 11:07 UTC194
                {
25d14e49ab68atagen2026-09-24 11:07 UTC195
                    tracing::error!(event = %event.id, %error, "saving discovered peers failed");
25d14e49ab68atagen2026-09-24 11:07 UTC196
                }
25d14e49ab68atagen2026-09-24 11:07 UTC197
            }
25d14e49ab68atagen2026-09-24 11:07 UTC198
            drop(writer);
25d14e49ab68atagen2026-09-24 11:07 UTC199
            Ok(status)
25d14e49ab68atagen2026-09-24 11:07 UTC200
        })
25d14e49ab68atagen2026-09-24 11:07 UTC201
    }
25d14e49ab68atagen2026-09-24 11:07 UTC202
25d14e49ab68atagen2026-09-24 11:07 UTC203
    fn check_id<'database>(
25d14e49ab68atagen2026-09-24 11:07 UTC204
        &'database self,
25d14e49ab68atagen2026-09-24 11:07 UTC205
        event_id: &'database EventId,
25d14e49ab68atagen2026-09-24 11:07 UTC206
    ) -> DatabaseFuture<'database, DatabaseEventStatus> {
25d14e49ab68atagen2026-09-24 11:07 UTC207
        let requested_id = *event_id;
25d14e49ab68atagen2026-09-24 11:07 UTC208
        Box::pin(async move {
25d14e49ab68atagen2026-09-24 11:07 UTC209
            self.journal
25d14e49ab68atagen2026-09-24 11:07 UTC210
                .status(requested_id)
25d14e49ab68atagen2026-09-24 11:07 UTC211
                .await
25d14e49ab68atagen2026-09-24 11:07 UTC212
                .map_err(storage_error)
25d14e49ab68atagen2026-09-24 11:07 UTC213
        })
25d14e49ab68atagen2026-09-24 11:07 UTC214
    }
25d14e49ab68atagen2026-09-24 11:07 UTC215
25d14e49ab68atagen2026-09-24 11:07 UTC216
    fn event_by_id<'database>(
25d14e49ab68atagen2026-09-24 11:07 UTC217
        &'database self,
25d14e49ab68atagen2026-09-24 11:07 UTC218
        event_id: &'database EventId,
25d14e49ab68atagen2026-09-24 11:07 UTC219
    ) -> DatabaseFuture<'database, Option<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC220
        let requested_id = *event_id;
25d14e49ab68atagen2026-09-24 11:07 UTC221
        Box::pin(async move {
25d14e49ab68atagen2026-09-24 11:07 UTC222
            self.journal
25d14e49ab68atagen2026-09-24 11:07 UTC223
                .event(requested_id)
25d14e49ab68atagen2026-09-24 11:07 UTC224
                .await
25d14e49ab68atagen2026-09-24 11:07 UTC225
                .map_err(storage_error)
25d14e49ab68atagen2026-09-24 11:07 UTC226
        })
25d14e49ab68atagen2026-09-24 11:07 UTC227
    }
25d14e49ab68atagen2026-09-24 11:07 UTC228
25d14e49ab68atagen2026-09-24 11:07 UTC229
    fn count(&self, filter: Filter) -> DatabaseFuture<'_, usize> {
25d14e49ab68atagen2026-09-24 11:07 UTC230
        Box::pin(async move { self.journal.count(filter).await.map_err(storage_error) })
25d14e49ab68atagen2026-09-24 11:07 UTC231
    }
25d14e49ab68atagen2026-09-24 11:07 UTC232
25d14e49ab68atagen2026-09-24 11:07 UTC233
    fn query(&self, filter: Filter) -> DatabaseFuture<'_, BTreeSet<Event>> {
25d14e49ab68atagen2026-09-24 11:07 UTC234
        Box::pin(self.query_events(filter))
25d14e49ab68atagen2026-09-24 11:07 UTC235
    }
25d14e49ab68atagen2026-09-24 11:07 UTC236
25d14e49ab68atagen2026-09-24 11:07 UTC237
    fn delete(&self, _filter: Filter) -> DatabaseFuture<'_, ()> {
25d14e49ab68atagen2026-09-24 11:07 UTC238
        Box::pin(async { Err(Error::unsupported("use signed deletion requests")) })
25d14e49ab68atagen2026-09-24 11:07 UTC239
    }
25d14e49ab68atagen2026-09-24 11:07 UTC240
25d14e49ab68atagen2026-09-24 11:07 UTC241
    fn wipe(&self) -> DatabaseFuture<'_, ()> {
25d14e49ab68atagen2026-09-24 11:07 UTC242
        Box::pin(async { Err(Error::unsupported("database wipe is disabled")) })
25d14e49ab68atagen2026-09-24 11:07 UTC243
    }
25d14e49ab68atagen2026-09-24 11:07 UTC244
}