nostr relay

Storage
Login

nostr-relay stores events using SQLAlchemy, with support for SQLite and PostgreSQL.
The default configuration uses SQLite. To change the location of the database:

storage:
    sqlalchemy.url: sqlite+aiosqlite:////full/path/to/nostr.sqlite3

To use PostgreSQL:

storage:
    sqlalchemy.url: postgresql+asyncpg://username:password@dbhost/nostr

See the SQLAlchemy docs for more on connection URLs.

To use LMDB:

storage:
    class: nostr_relay.storage.kv.LMDBStorage
    path: /path/to/db-environment
    map_size: 209715200

See LMDB documentation for more options

SQLAlchemy Options

Other SQLAlchemy options will be passed into the create_engine call:

storage:
    sqlalchemy.url: postgresql+asyncpg://username:password@dbhost/nostr
    sqlalchemy.max_overflow: 80
    sqlalchemy.pool_size: 4
    sqlalchemy.pool_timeout: 60.0

This will create a pool of 4 DB connections, with a maximum temporary overflow of 84 connections.

Concurrency

nostr-relay manages concurrent access to the databse, outside of the SQLAlchemy pool configuration.

To allow 10 concurrent read requests and 2 concurrent event adds:

storage:
    sqlalchemy.url: sqlite+aiosqlite:///nostr.sqlite3
    num_concurrent_reqs: 10
    num_concurrent_adds: 2

These are the default settings, appropriate for small relays using SQLite, which cannot handle many concurrent adds.

Using SQLite, you can easily increase num_concurrent_reqs. Using PostgreSQL, the concurrency scales much better.
A complete configuration might look like this:

storage:
    sqlalchemy.url: postgresql+asyncpg://username:password@dbhost/nostr
    sqlalchemy.max_overflow: 80
    sqlalchemy.pool_size: 4
    num_concurrent_reqs: 60
    num_concurrent_adds: 20

Validators

nostr-relay has a configurable event validator pipeline, to check events before saving to the database.
The default configuration looks like this:

storage:
  sqlalchemy.url: sqlite+aiosqlite:///nostr.sqlite3
  validators:
    - nostr_relay.validators.is_not_too_large
    - nostr_relay.validators.is_signed
    - nostr_relay.validators.is_recent

is_not_too_large checks configuration option max_event_size
is_recent checks configuration option oldest_event

The defined functions will execute in order. You can add custom validators, as long as they are importable functions that have this interface:

def my_validator(event, config)

my_validator will be called with the event and the configuration object. If the event is invalid, raise nostr_relay.errors.StorageError.
It's best to keep your validator functions small, without side-effects.

To require proof of 20 bits of work:

storage:
  sqlalchemy.url: sqlite+aiosqlite:///nostr.sqlite3
  validators:
    - nostr_relay.validators.is_not_too_large
    - nostr_relay.validators.is_signed
    - nostr_relay.validators.is_recent
    - nostr_relay.validators.is_pow

require_pow: 20

Other validators include is_author_blacklisted, is_author_whitelisted, and is_certain_kind

See the code for the complete list of installed validators.

Customization

To use a different class for subscriptions:

storage:
    sqlalchemy.url: sqlite+aiosqlite:///nostr.sqlite3
    subscription_class: my_module.MySubscription

TODO: describe Subscription interface

To use a different storage class entirely:

storage:
    sqlalchemy.url: sqlite+aiosqlite:///nostr.sqlite3
    class: my_module.MyStorage