Compare commits

...

38 Commits

Author SHA1 Message Date
Greg Heartsfield
d72af96d5f refactor: rustfmt 2025-02-23 11:23:22 -06:00
Greg Heartsfield
b4234eae25 refactor: clippy suggestions 2025-02-23 11:22:12 -06:00
Greg Heartsfield
d73cde2844 fix: only send events to the nip05 verifier if grpc permits
Prior to this, the NIP05 verifier would receieve metadata events for
GRPC-denied events.  If  the verifier passed, it would try to write
the verification record, but referencing a non-existent event, which
would fail with a SQL constraint error.
2025-02-23 11:04:35 -06:00
Greg Heartsfield
afbd7559e8 improvement: update dependencies
Updating addr2line v0.22.0 -> v0.24.2
Removing adler v1.0.2
Updating allocator-api2 v0.2.18 -> v0.2.21
Updating anstream v0.6.15 -> v0.6.18
Updating anstyle v1.0.8 -> v1.0.10
Updating anstyle-parse v0.2.5 -> v0.2.6
Updating anstyle-query v1.1.1 -> v1.1.2
Updating anstyle-wincon v3.0.4 -> v3.0.7
Updating anyhow v1.0.86 -> v1.0.96
Updating async-executor v1.13.0 -> v1.13.1
Removing async-io v1.13.0
Removing async-io v2.3.4
  Adding async-io v2.4.0
Removing async-lock v2.8.0
Updating async-std v1.12.0 -> v1.13.0
Updating async-stream v0.3.5 -> v0.3.6
Updating async-stream-impl v0.3.5 -> v0.3.6
Updating async-trait v0.1.82 -> v0.1.86
Updating autocfg v1.3.0 -> v1.4.0
Updating backtrace v0.3.73 -> v0.3.74
Updating bitflags v2.6.0 -> v2.8.0
Updating bumpalo v3.16.0 -> v3.17.0
Updating bytes v1.7.1 -> v1.10.0
Updating cc v1.1.16 -> v1.2.15
Updating chrono v0.4.38 -> v0.4.39
Updating clap v4.5.17 -> v4.5.30
Updating clap_builder v4.5.17 -> v4.5.30
Updating clap_derive v4.5.13 -> v4.5.28
Updating clap_lex v0.7.2 -> v0.7.4
Updating colorchoice v1.0.2 -> v1.0.3
Updating console v0.15.8 -> v0.15.10
Updating const_format v0.2.33 -> v0.2.34
Updating const_format_proc_macros v0.2.33 -> v0.2.34
Updating cpufeatures v0.2.14 -> v0.2.17
Updating crossbeam-channel v0.5.13 -> v0.5.14
Updating crossbeam-queue v0.3.11 -> v0.3.12
Updating crossbeam-utils v0.8.20 -> v0.8.21
  Adding displaydoc v0.2.5
Updating encode_unicode v0.3.6 -> v1.0.0
Updating equivalent v1.0.1 -> v1.0.2
Updating errno v0.3.9 -> v0.3.10
Updating event-listener v5.3.1 -> v5.4.0
Updating event-listener-strategy v0.5.2 -> v0.5.3
Removing fastrand v1.9.0
Removing fastrand v2.1.1
  Adding fastrand v2.3.0
Updating flate2 v1.0.33 -> v1.0.35
Updating futures v0.3.30 -> v0.3.31
Updating futures-channel v0.3.30 -> v0.3.31
Updating futures-core v0.3.30 -> v0.3.31
Updating futures-executor v0.3.30 -> v0.3.31
Updating futures-io v0.3.30 -> v0.3.31
Removing futures-lite v1.13.0
Removing futures-lite v2.3.0
  Adding futures-lite v2.6.0
Updating futures-macro v0.3.30 -> v0.3.31
Updating futures-sink v0.3.30 -> v0.3.31
Updating futures-task v0.3.30 -> v0.3.31
Updating futures-util v0.3.30 -> v0.3.31
  Adding getrandom v0.3.1
Updating gimli v0.29.0 -> v0.31.1
Updating gloo-timers v0.2.6 -> v0.3.0
  Adding hashbrown v0.15.2
Removing hermit-abi v0.3.9
Updating home v0.5.9 -> v0.5.11
Updating httparse v1.9.4 -> v1.10.0
Updating hyper v0.14.30 -> v0.14.32 (available: v1.6.0)
Updating iana-time-zone v0.1.60 -> v0.1.61
  Adding icu_collections v1.5.0
  Adding icu_locid v1.5.0
  Adding icu_locid_transform v1.5.0
  Adding icu_locid_transform_data v1.5.0
  Adding icu_normalizer v1.5.0
  Adding icu_normalizer_data v1.5.0
  Adding icu_properties v1.5.1
  Adding icu_properties_data v1.5.0
  Adding icu_provider v1.5.0
  Adding icu_provider_macros v1.5.0
Updating idna v0.5.0 -> v1.0.3
  Adding idna_adapter v1.2.0
Updating indexmap v2.5.0 -> v2.7.1
Updating indicatif v0.17.8 -> v0.17.11
Updating inout v0.1.3 -> v0.1.4
Removing io-lifetimes v1.0.11
Updating itoa v1.0.11 -> v1.0.14
Updating js-sys v0.3.70 -> v0.3.77
Updating libc v0.2.158 -> v0.2.170
Removing linux-raw-sys v0.3.8
Removing linux-raw-sys v0.4.14
  Adding linux-raw-sys v0.4.15
  Adding litemap v0.7.4
Updating log v0.4.22 -> v0.4.26
Removing miniz_oxide v0.7.4
Removing miniz_oxide v0.8.0
  Adding miniz_oxide v0.8.5
Updating mio v1.0.2 -> v1.0.3
Updating object v0.36.4 -> v0.36.7
Updating once_cell v1.19.0 -> v1.20.3
Updating openssl-probe v0.1.5 -> v0.1.6
Updating parking v2.2.0 -> v2.2.1
Updating pathdiff v0.2.1 -> v0.2.3
Updating pest v2.7.12 -> v2.7.15
Updating pest_derive v2.7.12 -> v2.7.15
Updating pest_generator v2.7.12 -> v2.7.15
Updating pest_meta v2.7.12 -> v2.7.15
Updating pin-project v1.1.5 -> v1.1.9
Updating pin-project-internal v1.1.5 -> v1.1.9
Updating pin-project-lite v0.2.14 -> v0.2.16
Updating pkg-config v0.3.30 -> v0.3.31
Removing polling v2.8.0
Removing polling v3.7.3
  Adding polling v3.7.4
Updating portable-atomic v1.7.0 -> v1.10.0
Updating proc-macro2 v1.0.86 -> v1.0.93
Updating quote v1.0.37 -> v1.0.38
Updating redox_syscall v0.5.3 -> v0.5.9
Updating regex v1.10.6 -> v1.11.1
Updating regex-automata v0.4.7 -> v0.4.9
Updating regex-syntax v0.8.4 -> v0.8.5
Updating ring v0.17.8 -> v0.17.11
Removing rustix v0.37.27
Removing rustix v0.38.36
  Adding rustix v0.38.44
Updating rustversion v1.0.17 -> v1.0.19
Updating ryu v1.0.18 -> v1.0.19
Updating schannel v0.1.23 -> v0.1.27
Updating security-framework-sys v2.11.1 -> v2.14.0
Updating serde v1.0.209 -> v1.0.218
Updating serde_derive v1.0.209 -> v1.0.218
Updating serde_json v1.0.128 -> v1.0.139
Updating smallvec v1.13.2 -> v1.14.0
Removing socket2 v0.4.10
Removing socket2 v0.5.7
  Adding socket2 v0.5.8
Removing spin v0.9.8
  Adding stable_deref_trait v1.2.0
Updating syn v2.0.77 -> v2.0.98
  Adding synstructure v0.13.1
Updating tempfile v3.12.0 -> v3.17.1
Removing thiserror v1.0.63
  Adding thiserror v1.0.69 (available: v2.0.11)
  Adding thiserror v2.0.11
Removing thiserror-impl v1.0.63
  Adding thiserror-impl v1.0.69
  Adding thiserror-impl v2.0.11
Updating time v0.3.36 -> v0.3.37
Updating time-macros v0.2.18 -> v0.2.19
  Adding tinystr v0.7.6
Updating tinyvec v1.8.0 -> v1.8.1
Updating tokio v1.40.0 -> v1.43.0
Updating tokio-macros v2.4.0 -> v2.5.0
Updating tokio-stream v0.1.16 -> v0.1.17
Updating tokio-util v0.7.12 -> v0.7.13
Updating tracing v0.1.40 -> v0.1.41
Updating tracing-attributes v0.1.27 -> v0.1.28
Updating tracing-core v0.1.32 -> v0.1.33
Updating tracing-subscriber v0.3.18 -> v0.3.19
Updating typenum v1.17.0 -> v1.18.0
Updating ucd-trie v0.1.6 -> v0.1.7
Updating unicode-bidi v0.3.15 -> v0.3.18
Updating unicode-ident v1.0.12 -> v1.0.17
Updating unicode-normalization v0.1.23 -> v0.1.24
Updating unicode-properties v0.1.2 -> v0.1.3
Updating unicode-segmentation v1.11.0 -> v1.12.0
Updating unicode-width v0.1.13 -> v0.2.0
Updating unicode-xid v0.2.5 -> v0.2.6
Updating url v2.5.2 -> v2.5.4
  Adding utf16_iter v1.0.5
  Adding utf8_iter v1.0.4
Updating uuid v1.10.0 -> v1.14.0
Updating valuable v0.1.0 -> v0.1.1
Updating value-bag v1.9.0 -> v1.10.0
Removing waker-fn v1.2.0
  Adding wasi v0.13.3+wasi-0.2.2
Updating wasm-bindgen v0.2.93 -> v0.2.100
Updating wasm-bindgen-backend v0.2.93 -> v0.2.100
Updating wasm-bindgen-futures v0.4.43 -> v0.4.50
Updating wasm-bindgen-macro v0.2.93 -> v0.2.100
Updating wasm-bindgen-macro-support v0.2.93 -> v0.2.100
Updating wasm-bindgen-shared v0.2.93 -> v0.2.100
Updating web-sys v0.3.70 -> v0.3.77
  Adding web-time v1.1.0
Removing windows-sys v0.48.0
Removing windows-targets v0.48.5
Removing windows_aarch64_gnullvm v0.48.5
Removing windows_aarch64_msvc v0.48.5
Removing windows_i686_gnu v0.48.5
Removing windows_i686_msvc v0.48.5
Removing windows_x86_64_gnu v0.48.5
Removing windows_x86_64_gnullvm v0.48.5
Removing windows_x86_64_msvc v0.48.5
  Adding wit-bindgen-rt v0.33.0
  Adding write16 v1.0.0
  Adding writeable v0.5.5
  Adding yoke v0.7.5
  Adding yoke-derive v0.7.5
  Adding zerofrom v0.1.5
  Adding zerofrom-derive v0.1.5
  Adding zerovec v0.10.4
  Adding zerovec-derive v0.10.3
2025-02-23 11:01:27 -06:00
laanwj
a6b48620fd fix: sqlite schema comment
`event_hash` is the raw SHA256 hash of the event, not 4-byte hash.
2024-10-19 07:12:11 -05:00
Greg Heartsfield
d71f5cb029 docs: remove reference to defunct anigma channel 2024-10-04 16:54:10 -05:00
Greg Heartsfield
1ed8cc08cc docs: point github CI badge to main repo 2024-10-04 08:34:42 -05:00
Greg Heartsfield
ff65ec2acd build: bump version to 0.9.0 2024-09-06 11:10:41 -05:00
Greg Heartsfield
4461648c64 improvement: update dependencies
Updating addr2line v0.21.0 -> v0.22.0
Adding adler2 v2.0.0
Updating allocator-api2 v0.2.16 -> v0.2.18
Updating anstream v0.6.13 -> v0.6.15
Updating anstyle v1.0.6 -> v1.0.8
Updating anstyle-parse v0.2.3 -> v0.2.5
Updating anstyle-query v1.0.2 -> v1.1.1
Updating anstyle-wincon v3.0.2 -> v3.0.4
Updating anyhow v1.0.81 -> v1.0.86
Updating async-channel v2.2.0 -> v2.3.1
Updating async-executor v1.8.0 -> v1.13.0
Updating async-io v2.3.2 -> v2.3.4
Updating async-lock v3.3.0 -> v3.4.0
Updating async-task v4.7.0 -> v4.7.1
Updating async-trait v0.1.79 -> v0.1.82
Updating autocfg v1.2.0 -> v1.3.0
Updating backtrace v0.3.71 -> v0.3.73
Updating bitflags v2.5.0 -> v2.6.0
Updating blocking v1.5.1 -> v1.6.1
Updating bumpalo v3.15.4 -> v3.16.0
Updating bytes v1.6.0 -> v1.7.1
Updating cc v1.0.90 -> v1.1.16
Updating chrono v0.4.37 -> v0.4.38
Updating clap v4.5.4 -> v4.5.17
Updating clap_builder v4.5.2 -> v4.5.17
Updating clap_derive v4.5.4 -> v4.5.13
Updating clap_lex v0.7.0 -> v0.7.2
Updating colorchoice v1.0.0 -> v1.0.2
Updating concurrent-queue v2.4.0 -> v2.5.0
Updating const_format v0.2.32 -> v0.2.33
Updating const_format_proc_macros v0.2.32 -> v0.2.33
Updating core-foundation-sys v0.8.6 -> v0.8.7
Updating cpufeatures v0.2.12 -> v0.2.14
Updating crc v3.0.1 -> v3.2.1
Updating crc32fast v1.4.0 -> v1.4.2
Updating crossbeam-channel v0.5.12 -> v0.5.13
Updating crossbeam-utils v0.8.19 -> v0.8.20
Updating either v1.10.0 -> v1.13.0
Updating errno v0.3.8 -> v0.3.9
Removing event-listener v4.0.3
Removing event-listener v5.2.0
Adding event-listener v5.3.1
Removing event-listener-strategy v0.4.0
Removing event-listener-strategy v0.5.0
Adding event-listener-strategy v0.5.2
Updating fastrand v2.0.2 -> v2.1.1
Removing finl_unicode v1.2.0
Updating flate2 v1.0.28 -> v1.0.33
Updating getrandom v0.2.12 -> v0.2.15
Updating gimli v0.28.1 -> v0.29.0
Updating h2 v0.3.25 -> v0.3.26
Updating hashbrown v0.14.3 -> v0.14.5
Adding hermit-abi v0.4.0
Updating httparse v1.8.0 -> v1.9.4
Updating hyper v0.14.28 -> v0.14.30
Updating indexmap v2.2.6 -> v2.5.0
Updating instant v0.1.12 -> v0.1.13
Adding is_terminal_polyfill v1.70.1
Removing itertools v0.12.1
Updating js-sys v0.3.69 -> v0.3.70
Updating lazy_static v1.4.0 -> v1.5.0
Updating libc v0.2.153 -> v0.2.158
Updating libredox v0.0.1 -> v0.1.3
Updating linux-raw-sys v0.4.13 -> v0.4.14
Updating lock_api v0.4.11 -> v0.4.12
Updating log v0.4.21 -> v0.4.22
Updating memchr v2.7.2 -> v2.7.4
Removing miniz_oxide v0.7.2
Adding miniz_oxide v0.7.4
Adding miniz_oxide v0.8.0
Updating mio v0.8.11 -> v1.0.2
Updating num-iter v0.1.44 -> v0.1.45
Updating num-traits v0.2.18 -> v0.2.19
Removing num_cpus v1.16.0
Updating object v0.32.2 -> v0.36.4
Updating parking_lot v0.12.1 -> v0.12.3
Updating parking_lot_core v0.9.9 -> v0.9.10
Updating paste v1.0.14 -> v1.0.15
Updating pest v2.7.8 -> v2.7.12
Updating pest_derive v2.7.8 -> v2.7.12
Updating pest_generator v2.7.8 -> v2.7.12
Updating pest_meta v2.7.8 -> v2.7.12
Updating petgraph v0.6.4 -> v0.6.5
Updating pin-project-lite v0.2.13 -> v0.2.14
Updating piper v0.2.1 -> v0.2.4
Updating polling v3.6.0 -> v3.7.3
Updating portable-atomic v1.6.0 -> v1.7.0
Updating ppv-lite86 v0.2.17 -> v0.2.20
Updating proc-macro2 v1.0.79 -> v1.0.86
Updating prometheus v0.13.3 -> v0.13.4
Updating quote v1.0.35 -> v1.0.37
Updating redox_syscall v0.4.1 -> v0.5.3
Updating redox_users v0.4.4 -> v0.4.6
Updating regex v1.10.4 -> v1.10.6
Updating regex-automata v0.4.6 -> v0.4.7
Updating regex-syntax v0.8.3 -> v0.8.4
Updating rustc-demangle v0.1.23 -> v0.1.24
Updating rustix v0.38.32 -> v0.38.36
Updating rustls v0.21.10 -> v0.21.12
Updating rustversion v1.0.14 -> v1.0.17
Updating ryu v1.0.17 -> v1.0.18
Updating security-framework v2.9.2 -> v2.11.1
Updating security-framework-sys v2.9.1 -> v2.11.1
Updating serde v1.0.197 -> v1.0.209
Updating serde_derive v1.0.197 -> v1.0.209
Updating serde_json v1.0.115 -> v1.0.128
Adding shlex v1.3.0
Updating signal-hook-registry v1.4.1 -> v1.4.2
Updating socket2 v0.5.6 -> v0.5.7
Updating sqlformat v0.2.3 -> v0.2.6
Updating stringprep v0.1.4 -> v0.1.5
Updating strsim v0.11.0 -> v0.11.1
Updating subtle v2.5.0 -> v2.6.1
Updating syn v2.0.55 -> v2.0.77
Updating tempfile v3.10.1 -> v3.12.0
Updating thiserror v1.0.58 -> v1.0.63
Updating thiserror-impl v1.0.58 -> v1.0.63
Updating tinyvec v1.6.0 -> v1.8.0
Updating tokio v1.36.0 -> v1.40.0
Updating tokio-macros v2.2.0 -> v2.4.0
Updating tokio-stream v0.1.15 -> v0.1.16
Updating tokio-util v0.7.10 -> v0.7.12
Updating tower-layer v0.3.2 -> v0.3.3
Updating tower-service v0.3.2 -> v0.3.3
Adding unicode-properties v0.1.2
Updating unicode-width v0.1.11 -> v0.1.13
Updating unicode-xid v0.2.4 -> v0.2.5
Updating url v2.5.0 -> v2.5.2
Updating utf8parse v0.2.1 -> v0.2.2
Updating uuid v1.8.0 -> v1.10.0
Updating value-bag v1.8.1 -> v1.9.0
Updating version_check v0.9.4 -> v0.9.5
Updating waker-fn v1.1.1 -> v1.2.0
Updating wasm-bindgen v0.2.92 -> v0.2.93
Updating wasm-bindgen-backend v0.2.92 -> v0.2.93
Updating wasm-bindgen-futures v0.4.42 -> v0.4.43
Updating wasm-bindgen-macro v0.2.92 -> v0.2.93
Updating wasm-bindgen-macro-support v0.2.92 -> v0.2.93
Updating wasm-bindgen-shared v0.2.92 -> v0.2.93
Updating web-sys v0.3.69 -> v0.3.70
Updating whoami v1.5.1 -> v1.5.2
Adding windows-sys v0.59.0
Updating windows-targets v0.52.4 -> v0.52.6
Updating windows_aarch64_gnullvm v0.52.4 -> v0.52.6
Updating windows_aarch64_msvc v0.52.4 -> v0.52.6
Updating windows_i686_gnu v0.52.4 -> v0.52.6
Adding windows_i686_gnullvm v0.52.6
Updating windows_i686_msvc v0.52.4 -> v0.52.6
Updating windows_x86_64_gnu v0.52.4 -> v0.52.6
Updating windows_x86_64_gnullvm v0.52.4 -> v0.52.6
Updating windows_x86_64_msvc v0.52.4 -> v0.52.6
Updating zerocopy v0.7.32 -> v0.7.35
Updating zerocopy-derive v0.7.32 -> v0.7.35
2024-09-06 11:05:42 -05:00
kuba
6329acd82b feat: add custom relay page 2024-09-06 10:44:15 -05:00
zappityzap
05411eb9e3 refactor: separate conditional dependencies
- move cln-rpc to regular dependencies
- add whitespace to separate conditional dependencies

fixes build on MSVC and OpenBSD
2024-09-06 10:33:56 -05:00
Joseph Goulden
5a21890625 feat: add cln payment processor 2024-08-14 11:36:07 -05:00
Joseph Goulden
0d04b5eefa add nix flake 2024-08-14 11:34:41 -05:00
Daniel Emery
07198b2cb9 refactor: add explicit logging when NIP-05 verification requests fail 2024-08-14 11:27:58 -05:00
Daniel Cadenas
af6d101c21 fix: id based sqlite query
Queries using the `id` field were failing after the refactor of
removing hexranges.
2024-08-14 11:21:44 -05:00
jesterhodl
5ad318e6e8 fix: time compilation 2024-08-14 10:48:07 -05:00
zappityzap
914ec77617 fix: openbsd build 2024-08-14 10:45:57 -05:00
Greg Heartsfield
4f518fd0e7 fix: prevent thread panic on large tag values using postgres
Exceptionally large tag values (thousands of characters) can result in
an error from postgres: index row size exceeds btree version 4 maximum
2704 for index "tag_value_idx".  This panics the writer thread, and
prevents further writes from succeeding.

This change simply removes the unwrap, allowing the error to propagate
where it is sent as a write error back to the client.  The error
message could be improved.

https://github.com/scsibug/nostr-rs-relay/issues/196
2024-06-06 17:32:13 -05:00
Laszlo Megyer
b04ab76e73 fix: postgresql tag filtering for odd-length hex-looking values
The tag filtering code misses odd-length strings that contains only hex digits [0-9a-f].
This fix makes the condition for `has_plain_values` the inverse of the condition for `has_hex_values`.

Fixes #191
2024-04-03 02:51:12 +00:00
Greg Heartsfield
39a3a258a0 refactor: clippy suggestions 2024-03-28 10:15:00 -05:00
Greg Heartsfield
44c6e3d88b improvement: upgrade dependencies
Updating aes v0.8.3 -> v0.8.4
Removing ahash v0.7.7
Removing ahash v0.8.6
Adding ahash v0.7.8
Adding ahash v0.8.11
Updating aho-corasick v1.1.2 -> v1.1.3
Updating anstream v0.6.4 -> v0.6.13
Updating anstyle v1.0.4 -> v1.0.6
Updating anstyle-parse v0.2.2 -> v0.2.3
Updating anstyle-query v1.0.0 -> v1.0.2
Updating anstyle-wincon v3.0.1 -> v3.0.2
Updating anyhow v1.0.75 -> v1.0.81
Updating async-channel v2.1.1 -> v2.2.0
Updating async-global-executor v2.4.0 -> v2.4.1
Updating async-io v2.2.1 -> v2.3.2
Updating async-lock v3.1.2 -> v3.3.0
Updating async-task v4.5.0 -> v4.7.0
Updating async-trait v0.1.74 -> v0.1.79
Updating autocfg v1.1.0 -> v1.2.0
Updating backtrace v0.3.69 -> v0.3.71
Updating base64 v0.21.5 -> v0.21.7
Updating bitflags v2.4.1 -> v2.5.0
Updating bumpalo v3.14.0 -> v3.15.4
Updating bytes v1.5.0 -> v1.6.0
Updating cc v1.0.83 -> v1.0.90
Updating chrono v0.4.31 -> v0.4.37
Updating clap v4.4.10 -> v4.5.4
Updating clap_builder v4.4.9 -> v4.5.2
Updating clap_derive v4.4.7 -> v4.5.4
Updating clap_lex v0.6.0 -> v0.7.0
Updating concurrent-queue v2.3.0 -> v2.4.0
Updating console v0.15.7 -> v0.15.8
Updating cpufeatures v0.2.11 -> v0.2.12
Updating crc32fast v1.3.2 -> v1.4.0
Updating crossbeam-channel v0.5.8 -> v0.5.12
Updating crossbeam-queue v0.3.8 -> v0.3.11
Updating crossbeam-utils v0.8.16 -> v0.8.19
Updating deranged v0.3.9 -> v0.3.11
Updating either v1.9.0 -> v1.10.0
Removing event-listener v4.0.0
Adding event-listener v4.0.3
Adding event-listener v5.2.0
Adding event-listener-strategy v0.5.0
Updating fastrand v2.0.1 -> v2.0.2
Updating futures v0.3.29 -> v0.3.30
Updating futures-channel v0.3.29 -> v0.3.30
Updating futures-core v0.3.29 -> v0.3.30
Updating futures-executor v0.3.29 -> v0.3.30
Updating futures-io v0.3.29 -> v0.3.30
Updating futures-lite v2.0.1 -> v2.3.0
Updating futures-macro v0.3.29 -> v0.3.30
Updating futures-sink v0.3.29 -> v0.3.30
Updating futures-task v0.3.29 -> v0.3.30
Updating futures-timer v3.0.2 -> v3.0.3
Updating futures-util v0.3.29 -> v0.3.30
Updating getrandom v0.2.11 -> v0.2.12
Updating h2 v0.3.22 -> v0.3.25
Adding heck v0.5.0
Updating hermit-abi v0.3.3 -> v0.3.9
Updating hkdf v0.12.3 -> v0.12.4
Updating home v0.5.5 -> v0.5.9
Updating http v0.2.11 -> v0.2.12
Updating http-body v0.4.5 -> v0.4.6
Updating hyper v0.14.27 -> v0.14.28
Updating iana-time-zone v0.1.58 -> v0.1.60
Updating indexmap v2.1.0 -> v2.2.6
Updating indicatif v0.17.7 -> v0.17.8
Updating itertools v0.11.0 -> v0.12.1
Updating itoa v1.0.9 -> v1.0.11
Updating js-sys v0.3.66 -> v0.3.69
Updating libc v0.2.150 -> v0.2.153
Updating linux-raw-sys v0.4.12 -> v0.4.13
Updating log v0.4.20 -> v0.4.21
Updating memchr v2.6.4 -> v2.7.2
Updating miniz_oxide v0.7.1 -> v0.7.2
Updating mio v0.8.9 -> v0.8.11
Adding num-conv v0.1.0
Updating num-integer v0.1.45 -> v0.1.46
Updating num-iter v0.1.43 -> v0.1.44
Updating num-traits v0.2.17 -> v0.2.18
Updating object v0.32.1 -> v0.32.2
Updating once_cell v1.18.0 -> v1.19.0
Updating pest v2.7.5 -> v2.7.8
Updating pest_derive v2.7.5 -> v2.7.8
Updating pest_generator v2.7.5 -> v2.7.8
Updating pest_meta v2.7.5 -> v2.7.8
Updating pin-project v1.1.3 -> v1.1.5
Updating pin-project-internal v1.1.3 -> v1.1.5
Updating pkg-config v0.3.27 -> v0.3.30
Updating polling v3.3.1 -> v3.6.0
Updating portable-atomic v1.5.1 -> v1.6.0
Updating proc-macro2 v1.0.70 -> v1.0.79
Updating quote v1.0.33 -> v1.0.35
Updating regex v1.10.2 -> v1.10.4
Updating regex-automata v0.4.3 -> v0.4.6
Updating regex-syntax v0.8.2 -> v0.8.3
Updating ring v0.17.6 -> v0.17.8
Updating rustix v0.38.26 -> v0.38.32
Updating rustls v0.21.9 -> v0.21.10
Updating ryu v1.0.15 -> v1.0.17
Updating schannel v0.1.22 -> v0.1.23
Updating serde v1.0.193 -> v1.0.197
Updating serde_derive v1.0.193 -> v1.0.197
Updating serde_json v1.0.108 -> v1.0.115
Updating smallvec v1.11.2 -> v1.13.2
Updating socket2 v0.5.5 -> v0.5.6
Updating sqlformat v0.2.2 -> v0.2.3
Updating strsim v0.10.0 -> v0.11.0
Updating syn v2.0.39 -> v2.0.55
Updating tempfile v3.8.1 -> v3.10.1
Updating thiserror v1.0.50 -> v1.0.58
Updating thiserror-impl v1.0.50 -> v1.0.58
Updating thread_local v1.1.7 -> v1.1.8
Updating time v0.3.30 -> v0.3.34
Updating time-macros v0.2.15 -> v0.2.17
Updating tokio v1.34.0 -> v1.36.0
Updating tokio-stream v0.1.14 -> v0.1.15
Updating try-lock v0.2.4 -> v0.2.5
Updating unicode-bidi v0.3.13 -> v0.3.15
Updating unicode-normalization v0.1.22 -> v0.1.23
Updating unicode-segmentation v1.10.1 -> v1.11.0
Updating uuid v1.6.1 -> v1.8.0
Updating value-bag v1.4.2 -> v1.8.1
Adding wasite v0.1.0
Updating wasm-bindgen v0.2.89 -> v0.2.92
Updating wasm-bindgen-backend v0.2.89 -> v0.2.92
Updating wasm-bindgen-futures v0.4.39 -> v0.4.42
Updating wasm-bindgen-macro v0.2.89 -> v0.2.92
Updating wasm-bindgen-macro-support v0.2.89 -> v0.2.92
Updating wasm-bindgen-shared v0.2.89 -> v0.2.92
Updating web-sys v0.3.66 -> v0.3.69
Updating whoami v1.4.1 -> v1.5.1
Updating windows-core v0.51.1 -> v0.52.0
Removing windows-sys v0.45.0
Removing windows-targets v0.42.2
Removing windows-targets v0.52.0
Adding windows-targets v0.52.4
Removing windows_aarch64_gnullvm v0.42.2
Removing windows_aarch64_gnullvm v0.52.0
Adding windows_aarch64_gnullvm v0.52.4
Removing windows_aarch64_msvc v0.42.2
Removing windows_aarch64_msvc v0.52.0
Adding windows_aarch64_msvc v0.52.4
Removing windows_i686_gnu v0.42.2
Removing windows_i686_gnu v0.52.0
Adding windows_i686_gnu v0.52.4
Removing windows_i686_msvc v0.42.2
Removing windows_i686_msvc v0.52.0
Adding windows_i686_msvc v0.52.4
Removing windows_x86_64_gnu v0.42.2
Removing windows_x86_64_gnu v0.52.0
Adding windows_x86_64_gnu v0.52.4
Removing windows_x86_64_gnullvm v0.42.2
Removing windows_x86_64_gnullvm v0.52.0
Adding windows_x86_64_gnullvm v0.52.4
Removing windows_x86_64_msvc v0.42.2
Removing windows_x86_64_msvc v0.52.0
Adding windows_x86_64_msvc v0.52.4
Updating zerocopy v0.7.28 -> v0.7.32
Updating zerocopy-derive v0.7.28 -> v0.7.32
2024-03-28 09:52:58 -05:00
Laszlo Megyer
767b76b2b3 fix: author filter in SQLite queries use correct blob type
https://todo.sr.ht/~gheartsfield/nostr-rs-relay/79
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2024-03-28 09:40:17 -05:00
Greg Heartsfield
c5fb16cd98 improvement: describe migration step that failed 2023-12-09 09:51:09 -06:00
Kieran
9c86f03902 refactor: drop hexrange
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-12-03 12:52:03 -06:00
Greg Heartsfield
971889f9a6 improvement: disable limit_scrapers by default
This is a good feature, but will limit valid requests from being
served.  Defaulting this to off will be less surprising to relay ops.
2023-12-03 10:51:59 -06:00
Kieran
388eadf880 feat: limit_scrapers
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-12-03 10:51:49 -06:00
Greg Heartsfield
1ce029860c docs: ensure latest Rust base image is pulled 2023-12-02 09:27:27 -06:00
Greg Heartsfield
b7e10e26a2 improvement: upgrade dependencies
Adding async-channel v2.1.1
Updating async-executor v1.6.0 -> v1.8.0
Updating async-global-executor v2.3.1 -> v2.4.0
Adding async-io v2.2.1
Adding async-lock v3.1.2
Updating blocking v1.4.1 -> v1.5.1
Updating clap v4.4.7 -> v4.4.10
Updating clap_builder v4.4.7 -> v4.4.9
Updating core-foundation v0.9.3 -> v0.9.4
Updating core-foundation-sys v0.8.4 -> v0.8.6
Updating errno v0.3.5 -> v0.3.8
Adding event-listener v4.0.0
Adding event-listener-strategy v0.4.0
Updating form_urlencoded v1.2.0 -> v1.2.1
Adding futures-lite v2.0.1
Updating getrandom v0.2.10 -> v0.2.11
Updating gimli v0.28.0 -> v0.28.1
Updating h2 v0.3.21 -> v0.3.22
Updating hashbrown v0.14.2 -> v0.14.3
Updating hdrhistogram v7.5.2 -> v7.5.4
Updating http v0.2.9 -> v0.2.11
Updating idna v0.4.0 -> v0.5.0
Updating js-sys v0.3.65 -> v0.3.66
Updating linux-raw-sys v0.4.10 -> v0.4.12
Updating percent-encoding v2.3.0 -> v2.3.1
Adding polling v3.3.1
Updating proc-macro2 v1.0.69 -> v1.0.70
Updating ring v0.17.5 -> v0.17.6
Updating rustix v0.38.21 -> v0.38.26
Updating rustls v0.21.8 -> v0.21.9
Updating rustls-pemfile v1.0.3 -> v1.0.4
Updating serde v1.0.190 -> v1.0.193
Updating serde_derive v1.0.190 -> v1.0.193
Updating smallvec v1.11.1 -> v1.11.2
Updating syn v2.0.38 -> v2.0.39
Updating tokio v1.33.0 -> v1.34.0
Updating tokio-macros v2.1.0 -> v2.2.0
Updating tracing-appender v0.2.2 -> v0.2.3
Updating tracing-log v0.1.4 -> v0.2.0
Updating tracing-subscriber v0.3.17 -> v0.3.18
Updating url v2.4.1 -> v2.5.0
Updating uuid v1.5.0 -> v1.6.1
Updating wasm-bindgen v0.2.88 -> v0.2.89
Updating wasm-bindgen-backend v0.2.88 -> v0.2.89
Updating wasm-bindgen-futures v0.4.38 -> v0.4.39
Updating wasm-bindgen-macro v0.2.88 -> v0.2.89
Updating wasm-bindgen-macro-support v0.2.88 -> v0.2.89
Updating wasm-bindgen-shared v0.2.88 -> v0.2.89
Updating web-sys v0.3.65 -> v0.3.66
Adding windows-sys v0.52.0
Adding windows-targets v0.52.0
Adding windows_aarch64_gnullvm v0.52.0
Adding windows_aarch64_msvc v0.52.0
Adding windows_i686_gnu v0.52.0
Adding windows_i686_msvc v0.52.0
Adding windows_x86_64_gnu v0.52.0
Adding windows_x86_64_gnullvm v0.52.0
Adding windows_x86_64_msvc v0.52.0
Updating zerocopy v0.7.25 -> v0.7.28
Updating zerocopy-derive v0.7.25 -> v0.7.28
2023-12-01 17:51:17 -06:00
Kieran
ab736f5f98 fix: abort query builder for empty arrays
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-12-01 17:22:14 -06:00
Kieran
b4471a6698 fix: multi-tag query
fixes https://github.com/scsibug/nostr-rs-relay/issues/102

Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-12-01 17:22:01 -06:00
Kieran
7120de4ff8 feat: restricted_writes
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-23 13:27:27 -06:00
thesimplekid
4ff77ab537 improvement: config to disable dm/invoice creation
While sending dms to users who are not signed up
but have attempted to publish events, it is now
disabled by default. This stops the creation of
extra invoices for pubkeys that may have no
intention of signing up for the relay. It also
reduced the number of dms that are created.

Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-23 13:22:41 -06:00
Carsten Otto
84f60f0abc improvement(NIP-11): mention requirements for admin contact pubkey
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-23 13:14:45 -06:00
Greg Heartsfield
8a67770206 improvement: update dependencies
Removing ahash v0.4.7
Removing ahash v0.7.6
Removing ahash v0.8.3
Adding ahash v0.4.8
Adding ahash v0.7.7
Adding ahash v0.8.6
Updating aho-corasick v1.0.5 -> v1.1.2
Updating anstream v0.5.0 -> v0.6.4
Updating anstyle v1.0.2 -> v1.0.4
Updating anstyle-parse v0.2.1 -> v0.2.2
Updating anstyle-wincon v2.1.0 -> v3.0.1
Updating async-executor v1.5.1 -> v1.6.0
Updating async-task v4.4.0 -> v4.5.0
Updating async-trait v0.1.73 -> v0.1.74
Updating atomic-waker v1.1.1 -> v1.1.2
Updating base64 v0.21.3 -> v0.21.5
Updating bitflags v2.4.0 -> v2.4.1
Updating blocking v1.3.1 -> v1.4.1
Updating bumpalo v3.13.0 -> v3.14.0
Updating byteorder v1.4.3 -> v1.5.0
Updating bytes v1.4.0 -> v1.5.0
Updating chrono v0.4.28 -> v0.4.31
Updating clap v4.4.2 -> v4.4.7
Updating clap_builder v4.4.2 -> v4.4.7
Updating clap_derive v4.4.2 -> v4.4.7
Updating clap_lex v0.5.1 -> v0.6.0
Updating concurrent-queue v2.2.0 -> v2.3.0
Updating const_format v0.2.31 -> v0.2.32
Updating const_format_proc_macros v0.2.31 -> v0.2.32
Updating cpufeatures v0.2.9 -> v0.2.11
Updating crc-catalog v2.2.0 -> v2.4.0
Updating deranged v0.3.8 -> v0.3.9
Removing dirs v5.0.1
Removing dirs-sys v0.4.1
Updating errno v0.3.3 -> v0.3.5
Removing errno-dragonfly v0.1.2
Updating fastrand v2.0.0 -> v2.0.1
Adding finl_unicode v1.2.0
Updating flate2 v1.0.27 -> v1.0.28
Updating futures v0.3.28 -> v0.3.29
Updating futures-channel v0.3.28 -> v0.3.29
Updating futures-core v0.3.28 -> v0.3.29
Updating futures-executor v0.3.28 -> v0.3.29
Updating futures-io v0.3.28 -> v0.3.29
Updating futures-macro v0.3.28 -> v0.3.29
Updating futures-sink v0.3.28 -> v0.3.29
Updating futures-task v0.3.28 -> v0.3.29
Updating futures-util v0.3.28 -> v0.3.29
Updating hashbrown v0.14.0 -> v0.14.2
Updating hermit-abi v0.3.2 -> v0.3.3
Adding home v0.5.5
Updating hyper-rustls v0.24.1 -> v0.24.2
Updating iana-time-zone v0.1.57 -> v0.1.58
Updating indexmap v2.0.0 -> v2.1.0
Updating indicatif v0.17.6 -> v0.17.7
Updating js-sys v0.3.64 -> v0.3.65
Updating libc v0.2.147 -> v0.2.150
Adding libredox v0.0.1
Updating linux-raw-sys v0.4.5 -> v0.4.10
Updating lock_api v0.4.10 -> v0.4.11
Updating matchit v0.7.2 -> v0.7.3
Updating md-5 v0.10.5 -> v0.10.6
Updating memchr v2.6.3 -> v2.6.4
Updating mio v0.8.8 -> v0.8.9
Updating num-traits v0.2.16 -> v0.2.17
Removing option-ext v0.2.0
Updating parking v2.1.0 -> v2.2.0
Updating parking_lot_core v0.9.8 -> v0.9.9
Updating pest v2.7.3 -> v2.7.5
Updating pest_derive v2.7.3 -> v2.7.5
Updating pest_generator v2.7.3 -> v2.7.5
Updating pest_meta v2.7.3 -> v2.7.5
Adding piper v0.2.1
Updating portable-atomic v1.4.3 -> v1.5.1
Adding powerfmt v0.2.0
Updating proc-macro2 v1.0.66 -> v1.0.69
Updating redox_syscall v0.3.5 -> v0.4.1
Updating redox_users v0.4.3 -> v0.4.4
Updating regex v1.9.5 -> v1.10.2
Updating regex-automata v0.3.8 -> v0.4.3
Updating regex-syntax v0.7.5 -> v0.8.2
Adding ring v0.17.5
Removing rustix v0.37.23
Removing rustix v0.38.11
Adding rustix v0.37.27
Adding rustix v0.38.21
Updating rustls v0.21.7 -> v0.21.8
Updating rustls-webpki v0.101.4 -> v0.101.7
Updating sct v0.7.0 -> v0.7.1
Updating serde v1.0.188 -> v1.0.190
Updating serde_derive v1.0.188 -> v1.0.190
Updating serde_json v1.0.105 -> v1.0.108
Updating sha1 v0.10.5 -> v0.10.6
Updating sha2 v0.10.7 -> v0.10.8
Updating sharded-slab v0.1.4 -> v0.1.7
Updating smallvec v1.11.0 -> v1.11.1
Removing socket2 v0.4.9
Removing socket2 v0.5.3
Adding socket2 v0.4.10
Adding socket2 v0.5.5
Adding spin v0.9.8
Updating stringprep v0.1.3 -> v0.1.4
Updating syn v2.0.31 -> v2.0.38
Updating tempfile v3.8.0 -> v3.8.1
Updating thiserror v1.0.48 -> v1.0.50
Updating thiserror-impl v1.0.48 -> v1.0.50
Removing time v0.1.43
Removing time v0.3.28
Adding time v0.3.30
Updating time-core v0.1.1 -> v0.1.2
Updating time-macros v0.2.14 -> v0.2.15
Updating tokio v1.32.0 -> v1.33.0
Updating tokio-util v0.7.8 -> v0.7.10
Updating tracing v0.1.37 -> v0.1.40
Updating tracing-attributes v0.1.26 -> v0.1.27
Updating tracing-core v0.1.31 -> v0.1.32
Updating tracing-log v0.1.3 -> v0.1.4
Updating typenum v1.16.0 -> v1.17.0
Updating unicode-ident v1.0.11 -> v1.0.12
Updating unicode-width v0.1.10 -> v0.1.11
Adding untrusted v0.9.0
Updating uuid v1.4.1 -> v1.5.0
Updating value-bag v1.4.1 -> v1.4.2
Updating waker-fn v1.1.0 -> v1.1.1
Updating wasm-bindgen v0.2.87 -> v0.2.88
Updating wasm-bindgen-backend v0.2.87 -> v0.2.88
Updating wasm-bindgen-futures v0.4.37 -> v0.4.38
Updating wasm-bindgen-macro v0.2.87 -> v0.2.88
Updating wasm-bindgen-macro-support v0.2.87 -> v0.2.88
Updating wasm-bindgen-shared v0.2.87 -> v0.2.88
Updating web-sys v0.3.64 -> v0.3.65
Updating webpki v0.22.1 -> v0.22.4
Updating which v4.4.1 -> v4.4.2
Removing windows v0.48.0
Adding windows-core v0.51.1
Adding zerocopy v0.7.25
Adding zerocopy-derive v0.7.25
2023-11-05 09:54:07 -06:00
thesimplekid
7650f5f4a3 fix: relay fee in msats
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-03 18:30:01 -05:00
Yuki Kishimoto
a7b169c0d3 fix: send OK message for ephemeral events
4b9f13d983/01.md (L153)
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-03 18:19:25 -05:00
Kieran
24b1705a08 fix: value_hex tag query
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-03 18:17:05 -05:00
benthecarman
9d0a98f8bf docs: add line for enabling systemd service
Signed-off-by: Greg Heartsfield <scsibug@imap.cc>
2023-11-03 18:15:25 -05:00
30 changed files with 1952 additions and 1276 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
nostr.db
nostr.db-*
justfile
result

1934
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "nostr-rs-relay"
version = "0.8.13"
version = "0.9.0"
edition = "2021"
authors = ["Greg Heartsfield <scsibug@imap.cc>"]
description = "A relay implementation for the Nostr protocol"
@@ -55,9 +55,11 @@ bech32 = "0.9.1"
url = "2.3.1"
qrcode = { version = "0.12.0", default-features = false, features = ["svg"] }
nostr = { version = "0.18.0", default-features = false, features = ["base", "nip04", "nip19"] }
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = "0.5"
log = "0.4"
cln-rpc = "0.1.9"
[target.'cfg(all(not(target_env = "msvc"), not(target_os = "openbsd")))'.dependencies]
tikv-jemallocator = "0.5"
[dev-dependencies]
anyhow = "1"

View File

@@ -11,7 +11,7 @@ mirrored on [GitHub](https://github.com/scsibug/nostr-rs-relay).
[![builds.sr.ht status](https://builds.sr.ht/~gheartsfield/nostr-rs-relay/commits/master.svg)](https://builds.sr.ht/~gheartsfield/nostr-rs-relay/commits/master?)
![Github CI](https://github.com/schlunsen/nostr-rs-relay/actions/workflows/ci.yml/badge.svg)
![Github CI](https://github.com/scsibug/nostr-rs-relay/actions/workflows/ci.yml/badge.svg)
## Features
@@ -49,7 +49,7 @@ The examples below start a rootless podman container, mapping a local
data directory and config file.
```console
$ podman build -t nostr-rs-relay .
$ podman build --pull -t nostr-rs-relay .
$ mkdir data
@@ -98,6 +98,11 @@ The following OS packages will be helpful; on Debian/Ubuntu:
$ sudo apt-get install build-essential cmake protobuf-compiler pkg-config libssl-dev
```
On OpenBSD:
```console
$ doas pkg_add rust protobuf
```
Clone this repository, and then build a release version of the relay:
```console
@@ -136,6 +141,7 @@ be mounted into a docker container like so:
$ docker run -it -p 7000:8080 \
--mount src=$(pwd)/config.toml,target=/usr/src/app/config.toml,type=bind \
--mount src=$(pwd)/data,target=/usr/src/app/db,type=bind \
--mount src=$(pwd)/index.html,target=/usr/src/app/index.html,type=bind \
nostr-rs-relay
```
@@ -152,10 +158,6 @@ Proxy](docs/reverse-proxy.md).
For development discussions, please feel free to use the [sourcehut
mailing list](https://lists.sr.ht/~gheartsfield/nostr-rs-relay-devel).
Or, drop by the [Nostr Telegram Channel](https://t.me/nostr_protocol).
To chat about `nostr-rs-relay` on `nostr` itself; visit our channel on [anigma](https://anigma.io/) or another client that supports [NIP-28](https://github.com/nostr-protocol/nips/blob/master/28.md) chats:
* `2ad246a094fee48c6e455dd13d759d5f41b5a233120f5719d81ebc1935075194`
License
---

View File

@@ -10,7 +10,7 @@ name = "nostr-rs-relay"
# Description
description = "A newly created nostr-rs-relay.\n\nCustomize this with your own info."
# Administrative contact pubkey
# Administrative contact pubkey (32-byte hex, not npub)
#pubkey = "0c2d168a4ae8ca58c9f1ab237b5df682599c6c7ab74307ea8b05684b60405d41"
# Administrative contact URI
@@ -23,6 +23,9 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i
# URL of Relay's icon.
#relay_icon = "https://example.test/img.png"
# Path to custom relay html page
#relay_page = "index.html"
[diagnostics]
# Enable tokio tracing (for use with tokio-console)
#tracing = false
@@ -75,6 +78,11 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i
# `proto/nauthz.proto`.
# event_admission_server = "http://[::1]:50051"
# If the event admission server denies writes
# in any case (excluding spam filtering).
# This is reflected in the relay information document.
# restricts_write = true
[network]
# Bind to this network address
address = "0.0.0.0"
@@ -150,6 +158,11 @@ reject_future_seconds = 1800
# 0, 1, 2, 3, 7, 40, 41, 42, 43, 44, 30023,
#]
# Rejects imprecise requests (kind only and author only etc)
# This is a temperary measure to improve the adoption of outbox model
# Its recommended to have this enabled
limit_scrapers = false
[authorization]
# Pubkey addresses in this array are whitelisted for event publishing.
# Only valid events by these authors will be accepted, if the variable
@@ -193,20 +206,26 @@ reject_future_seconds = 1800
# Enable pay to relay
#enabled = false
# Node interface to use
#processor = "ClnRest/LNBits"
# The cost to be admitted to relay
#admission_cost = 4200
# The cost in sats per post
#cost_per_event = 0
# Url of lnbits api
# Url of node api
#node_url = "<node url>"
# LNBits api secret
#api_secret = "<ln bits api>"
# Path to CLN rune
#rune_path = "<rune path>"
# Nostr direct message on signup
#direct_message=true
#direct_message=false
# Terms of service
#terms_message = """

View File

@@ -30,7 +30,8 @@ To get the service running, we need to reload the systemd daemon and enable the
1. `sudo systemctl daemon-reload`
2. `sudo systemctl start nostr-rs-relay.service`
3. `sudo systemctl status nostr-rs-relay.service`
3. `sudo systemctl enable nostr-rs-relay.service`
4. `sudo systemctl status nostr-rs-relay.service`
### Tips

82
flake.lock generated Normal file
View File

@@ -0,0 +1,82 @@
{
"nodes": {
"crane": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1719249093,
"narHash": "sha256-0q1haa3sw6GbmJ+WhogMnducZGjEaCa/iR6hF2vq80I=",
"owner": "ipetkov",
"repo": "crane",
"rev": "9791c77eb7e98b8d8ac5b0305d47282f994411ca",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1719254875,
"narHash": "sha256-ECni+IkwXjusHsm9Sexdtq8weAq/yUyt1TWIemXt3Ko=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2893f56de08021cffd9b6b6dfc70fd9ccd51eb60",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

44
flake.nix Normal file
View File

@@ -0,0 +1,44 @@
{
description = "Nostr Relay written in Rust";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
crane = {
url = "github:ipetkov/crane";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ self, ... }:
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = inputs.nixpkgs.legacyPackages.${system};
craneLib = inputs.crane.mkLib pkgs;
src = pkgs.lib.cleanSourceWith {
src = ./.;
filter = path: type:
(pkgs.lib.hasSuffix "\.proto" path) ||
# Default filter from crane (allow .rs files)
(craneLib.filterCargoSources path type)
;
};
crate = craneLib.buildPackage {
name = "nostr-rs-relay";
inherit src;
nativeBuildInputs = [ pkgs.pkg-config pkgs.protobuf ];
};
in
{
checks = {
inherit crate;
};
packages.default = crate;
formatter = pkgs.nixpkgs-fmt;
devShells.default = craneLib.devShell {
checks = self.checks.${system};
};
});
}

View File

@@ -15,7 +15,6 @@ use tracing::info;
/// Bulk load JSONL data from STDIN to the database specified in config.toml (or ./nostr.db as a default).
/// The database must already exist, this will not create a new one.
/// Tested against schema v13.
pub fn main() -> Result<()> {
let _trace_sub = tracing_subscriber::fmt::try_init();
println!("Nostr-rs-relay Bulk Loader");
@@ -143,7 +142,7 @@ fn write_event(tx: &Transaction, e: Event) -> Result<usize> {
let event_id = tx.last_insert_rowid();
// look at each event, and each tag, creating new tag entries if appropriate.
for t in e.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;

View File

@@ -14,6 +14,7 @@ pub struct Info {
pub contact: Option<String>,
pub favicon: Option<String>,
pub relay_icon: Option<String>,
pub relay_page: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -32,6 +33,7 @@ pub struct Database {
#[allow(unused)]
pub struct Grpc {
pub event_admission_server: Option<String>,
pub restricts_write: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -73,6 +75,7 @@ pub struct Limits {
pub event_persist_buffer: usize, // events to buffer for database commits (block senders if database writes are too slow)
pub event_kind_blacklist: Option<Vec<u64>>,
pub event_kind_allowlist: Option<Vec<u64>>,
pub limit_scrapers: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -96,6 +99,7 @@ pub struct PayToRelay {
pub direct_message: bool, // Send direct message to user with invoice and terms
pub secret_key: Option<String>,
pub processor: Processor,
pub rune_path: Option<String>, // To access clightning API
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -245,17 +249,25 @@ impl Settings {
// Validate pay to relay settings
if settings.pay_to_relay.enabled {
assert_ne!(settings.pay_to_relay.api_secret, "");
if settings.pay_to_relay.processor == Processor::ClnRest {
assert!(settings
.pay_to_relay
.rune_path
.as_ref()
.is_some_and(|path| path != "<rune path>"));
} else if settings.pay_to_relay.processor == Processor::LNBits {
assert_ne!(settings.pay_to_relay.api_secret, "");
}
// Should check that url is valid
assert_ne!(settings.pay_to_relay.node_url, "");
assert_ne!(settings.pay_to_relay.terms_message, "");
if settings.pay_to_relay.direct_message {
assert_ne!(
settings.pay_to_relay.secret_key,
Some("<nostr nsec>".to_string())
);
assert!(settings.pay_to_relay.secret_key.is_some());
assert!(settings
.pay_to_relay
.secret_key
.as_ref()
.is_some_and(|key| key != "<nostr nsec>"));
}
}
@@ -274,6 +286,7 @@ impl Default for Settings {
contact: None,
favicon: None,
relay_icon: None,
relay_page: None,
},
diagnostics: Diagnostics { tracing: false },
database: Database {
@@ -287,6 +300,7 @@ impl Default for Settings {
},
grpc: Grpc {
event_admission_server: None,
restricts_write: false,
},
network: Network {
port: 8080,
@@ -306,6 +320,7 @@ impl Default for Settings {
event_persist_buffer: 4096,
event_kind_blacklist: None,
event_kind_allowlist: None,
limit_scrapers: false,
},
authorization: Authorization {
pubkey_whitelist: None, // Allow any address to publish
@@ -319,8 +334,9 @@ impl Default for Settings {
terms_message: "".to_string(),
node_url: "".to_string(),
api_secret: "".to_string(),
rune_path: None,
sign_ups: false,
direct_message: true,
direct_message: false,
secret_key: None,
processor: Processor::LNBits,
},

View File

@@ -185,10 +185,10 @@ impl ClientConn {
let mut relay: Option<&str> = None;
for tag in &event.tags {
if tag.len() == 2 && tag.get(0) == Some(&"challenge".into()) {
if tag.len() == 2 && tag.first() == Some(&"challenge".into()) {
challenge = tag.get(1).map(|x| x.as_str());
}
if tag.len() == 2 && tag.get(0) == Some(&"relay".into()) {
if tag.len() == 2 && tag.first() == Some(&"relay".into()) {
relay = tag.get(1).map(|x| x.as_str());
}
}

View File

@@ -261,7 +261,7 @@ pub async fn db_writer(
) => {
// User does not exist
info!("Unregistered user");
if settings.pay_to_relay.sign_ups {
if settings.pay_to_relay.sign_ups && settings.pay_to_relay.direct_message {
payment_tx
.send(PaymentMessage::NewAccount(event.pubkey))
.ok();
@@ -281,15 +281,6 @@ pub async fn db_writer(
}
}
// send any metadata events to the NIP-05 verifier
if nip05_active && event.is_kind_metadata() {
// we are sending this prior to even deciding if we
// persist it. this allows the nip05 module to
// inspect it, update if necessary, or persist a new
// event and broadcast it itself.
metadata_tx.send(event.clone()).ok();
}
// get a validation result for use in verification and GPRC
let validation = if nip05_active {
Some(repo.get_latest_user_verification(&event.pubkey).await)
@@ -390,6 +381,15 @@ pub async fn db_writer(
}
}
// send any metadata events to the NIP-05 verifier
if nip05_active && event.is_kind_metadata() {
// we are sending this prior to even deciding if we
// persist it. this allows the nip05 module to
// inspect it, update if necessary, or persist a new
// event and broadcast it itself.
metadata_tx.send(event.clone()).ok();
}
// TODO: cache recent list of authors to remove a DB call.
let start = Instant::now();
if event.is_ephemeral() {
@@ -401,6 +401,9 @@ pub async fn db_writer(
start.elapsed()
);
event_write = true;
// send OK message
notice_tx.try_send(Notice::saved(event.id)).ok();
} else {
match repo.write_event(&event).await {
Ok(updated) => {

View File

@@ -42,7 +42,7 @@ pub enum Error {
CommandUnknownError,
#[error("SQL error")]
SqlError(rusqlite::Error),
#[error("Config error")]
#[error("Config error : {0}")]
ConfigError(config::ConfigError),
#[error("Data directory does not exist")]
DatabaseDirError,

View File

@@ -160,11 +160,11 @@ impl Event {
.tags
.iter()
.filter(|x| !x.is_empty())
.filter(|x| x.get(0).unwrap() == "expiration")
.filter(|x| x.first().unwrap() == "expiration")
.map(|x| x.get(1).unwrap_or(&default))
.take(1)
.collect();
let val_first = dvals.get(0);
let val_first = dvals.first();
val_first.and_then(|t| t.parse::<u64>().ok())
}
@@ -192,11 +192,11 @@ impl Event {
.tags
.iter()
.filter(|x| !x.is_empty())
.filter(|x| x.get(0).unwrap() == "d")
.filter(|x| x.first().unwrap() == "d")
.map(|x| x.get(1).unwrap_or(&default))
.take(1)
.collect();
let dval_first = dvals.get(0);
let dval_first = dvals.first();
match dval_first {
Some(_) => dval_first.map(|x| x.to_string()),
None => Some(default),
@@ -232,7 +232,7 @@ impl Event {
.tags
.iter()
.filter(|x| x.len() == 4)
.filter(|x| x.get(0).unwrap() == "delegation")
.filter(|x| x.first().unwrap() == "delegation")
.take(1)
.next()?
.clone(); // get first tag
@@ -277,7 +277,7 @@ impl Event {
let mut idx: HashMap<char, HashSet<String>> = HashMap::new();
// iterate over tags that have at least 2 elements
for t in self.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;
@@ -285,7 +285,7 @@ impl Event {
let tagnamechar = tagnamechar_opt.unwrap();
let tagval = t.get(1).unwrap();
// ensure a vector exists for this tag
idx.entry(tagnamechar).or_insert_with(HashSet::new);
idx.entry(tagnamechar).or_default();
// get the tag vec and insert entry
let idx_tag_vec = idx.get_mut(&tagnamechar).expect("could not get tag vector");
idx_tag_vec.insert(tagval.clone());
@@ -310,7 +310,7 @@ impl Event {
self.tags
.iter()
.filter(|x| x.len() > 1)
.filter(|x| x.get(0).unwrap() == tag_name)
.filter(|x| x.first().unwrap() == tag_name)
.map(|x| x.get(1).unwrap().clone())
.collect()
}
@@ -472,12 +472,8 @@ mod tests {
let mut event = Event::simple_event();
event.tags = vec![vec!["e".to_owned(), "foo".to_owned()]];
event.build_index();
assert!(
event.generic_tag_val_intersect(
'e',
&HashSet::from(["foo".to_owned(), "bar".to_owned()])
)
);
assert!(event
.generic_tag_val_intersect('e', &HashSet::from(["foo".to_owned(), "bar".to_owned()])));
}
#[test]

View File

@@ -1,159 +0,0 @@
//! Utilities for searching hexadecimal
use crate::utils::is_hex;
use hex;
/// Types of hexadecimal queries.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
pub enum HexSearch {
// when no range is needed, exact 32-byte
Exact(Vec<u8>),
// lower (inclusive) and upper range (exclusive)
Range(Vec<u8>, Vec<u8>),
// lower bound only, upper bound is MAX inclusive
LowerOnly(Vec<u8>),
}
/// Check if a string contains only f chars
fn is_all_fs(s: &str) -> bool {
s.chars().all(|x| x == 'f' || x == 'F')
}
/// Find the next hex sequence greater than the argument.
#[must_use]
pub fn hex_range(s: &str) -> Option<HexSearch> {
let mut hash_base = s.to_owned();
if !is_hex(&hash_base) || hash_base.len() > 64 {
return None;
}
if hash_base.len() == 64 {
return Some(HexSearch::Exact(hex::decode(&hash_base).ok()?));
}
// if s is odd, add a zero
let mut odd = hash_base.len() % 2 != 0;
if odd {
// extend the string to make it even
hash_base.push('0');
}
let base = hex::decode(hash_base).ok()?;
// check for all ff's
if is_all_fs(s) {
// there is no higher bound, we only want to search for blobs greater than this.
return Some(HexSearch::LowerOnly(base));
}
// return a range
let mut upper = base.clone();
let mut byte_len = upper.len();
// for odd strings, we made them longer, but we want to increment the upper char (+16).
// we know we can do this without overflowing because we explicitly set the bottom half to 0's.
while byte_len > 0 {
byte_len -= 1;
// check if byte can be incremented, or if we need to carry.
let b = upper[byte_len];
if b == u8::MAX {
// reset and carry
upper[byte_len] = 0;
} else if odd {
// check if first char in this byte is NOT 'f'
if b < 240 {
// bump up the first character in this byte
upper[byte_len] = b + 16;
// increment done, stop iterating through the vec
break;
}
// if it is 'f', reset the byte to 0 and do a carry
// reset and carry
upper[byte_len] = 0;
// done with odd logic, so don't repeat this
odd = false;
} else {
// bump up the first character in this byte
upper[byte_len] = b + 1;
// increment done, stop iterating
break;
}
}
Some(HexSearch::Range(base, upper))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Result;
#[test]
fn hex_range_exact() -> Result<()> {
let hex = "abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00";
let r = hex_range(hex);
assert_eq!(
r,
Some(HexSearch::Exact(hex::decode(hex).expect("invalid hex")))
);
Ok(())
}
#[test]
fn hex_full_range() -> Result<()> {
let hex = "aaaa";
let hex_upper = "aaab";
let r = hex_range(hex);
assert_eq!(
r,
Some(HexSearch::Range(
hex::decode(hex).expect("invalid hex"),
hex::decode(hex_upper).expect("invalid hex")
))
);
Ok(())
}
#[test]
fn hex_full_range_odd() -> Result<()> {
let r = hex_range("abc");
assert_eq!(
r,
Some(HexSearch::Range(
hex::decode("abc0").expect("invalid hex"),
hex::decode("abd0").expect("invalid hex")
))
);
Ok(())
}
#[test]
fn hex_full_range_odd_end_f() -> Result<()> {
let r = hex_range("abf");
assert_eq!(
r,
Some(HexSearch::Range(
hex::decode("abf0").expect("invalid hex"),
hex::decode("ac00").expect("invalid hex")
))
);
Ok(())
}
#[test]
fn hex_no_upper() -> Result<()> {
let r = hex_range("ffff");
assert_eq!(
r,
Some(HexSearch::LowerOnly(
hex::decode("ffff").expect("invalid hex")
))
);
Ok(())
}
#[test]
fn hex_no_upper_odd() -> Result<()> {
let r = hex_range("fff");
assert_eq!(
r,
Some(HexSearch::LowerOnly(
hex::decode("fff0").expect("invalid hex")
))
);
Ok(())
}
}

View File

@@ -4,7 +4,7 @@ use crate::config::Settings;
use serde::{Deserialize, Serialize};
pub const CARGO_PKG_VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
pub const UNIT: &str = "sats";
pub const UNIT: &str = "msats";
/// Limitations of the relay as specified in NIP-111
/// (This nip isn't finalized so may change)
@@ -13,6 +13,9 @@ pub const UNIT: &str = "sats";
pub struct Limitation {
#[serde(skip_serializing_if = "Option::is_none")]
payment_required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
restricted_writes: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -45,7 +48,7 @@ pub struct RelayInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<String>,
pub icon: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supported_nips: Option<Vec<i64>>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -75,12 +78,18 @@ impl From<Settings> for RelayInfo {
let limitations = Limitation {
payment_required: Some(p.enabled),
restricted_writes: Some(
p.enabled
|| c.verified_users.is_enabled()
|| c.authorization.pubkey_whitelist.is_some()
|| c.grpc.restricts_write,
),
};
let (payment_url, fees) = if p.enabled {
let admission_fee = if p.admission_cost > 0 {
Some(vec![Fee {
amount: p.admission_cost,
amount: p.admission_cost * 1000,
unit: UNIT.to_string(),
}])
} else {
@@ -89,7 +98,7 @@ impl From<Settings> for RelayInfo {
let post_fee = if p.cost_per_event > 0 {
Some(vec![Fee {
amount: p.cost_per_event,
amount: p.cost_per_event * 1000,
unit: UNIT.to_string(),
}])
} else {

View File

@@ -6,7 +6,6 @@ pub mod db;
pub mod delegation;
pub mod error;
pub mod event;
pub mod hexrange;
pub mod info;
pub mod nauthz;
pub mod nip05;

View File

@@ -10,13 +10,13 @@ use std::process;
use std::sync::mpsc as syncmpsc;
use std::sync::mpsc::{Receiver as MpscReceiver, Sender as MpscSender};
use std::thread;
#[cfg(not(target_env = "msvc"))]
#[cfg(all(not(target_env = "msvc"), not(target_os = "openbsd")))]
use tikv_jemallocator::Jemalloc;
use tracing::info;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
#[cfg(not(target_env = "msvc"))]
#[cfg(all(not(target_env = "msvc"), not(target_os = "openbsd")))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

View File

@@ -121,7 +121,7 @@ fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes)
// get the pubkey for the requested user
let check_name = names_map.get(username).and_then(serde_json::Value::as_str);
// ensure the address is a match
Ok(check_name.map_or(false, |x| x == address))
Ok(check_name == Some(address))
}
impl Verifier {
@@ -208,7 +208,7 @@ impl Verifier {
.ok_or_else(|| Error::CustomError("invalid NIP-05 URL".to_owned()))?;
let req = hyper::Request::builder()
.method(hyper::Method::GET)
.uri(url)
.uri(url.clone())
.header("Accept", "application/json")
.header(
"User-Agent",
@@ -226,38 +226,84 @@ impl Verifier {
// limit size of verification document to 1MB.
const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024 * 1024;
let response = response_res?;
let status = response.status();
// Log non-2XX status codes
if !status.is_success() {
info!(
"unexpected status code {} received for account {:?} at URL: {}",
status,
nip.to_string(),
url
);
return Ok(UserWebVerificationStatus::Unknown);
}
// determine content length from response
let response_content_length = match response.body().size_hint().upper() {
Some(v) => v,
None => MAX_ALLOWED_RESPONSE_SIZE + 1, // reject missing content length
None => {
info!(
"missing content length header for account {:?} at URL: {}",
nip.to_string(),
url
);
return Ok(UserWebVerificationStatus::Unknown);
}
};
// TODO: test how hyper handles the client providing an inaccurate content-length.
if response_content_length <= MAX_ALLOWED_RESPONSE_SIZE {
let (parts, body) = response.into_parts();
// TODO: consider redirects
if parts.status == http::StatusCode::OK {
// parse body, determine if the username / key / address is present
let body_bytes = hyper::body::to_bytes(body).await?;
let body_matches = body_contains_user(&nip.local, pubkey, &body_bytes)?;
if body_matches {
return Ok(UserWebVerificationStatus::Verified);
if response_content_length > MAX_ALLOWED_RESPONSE_SIZE {
info!(
"content length {} exceeded limit of {} bytes for account {:?} at URL: {}",
response_content_length,
MAX_ALLOWED_RESPONSE_SIZE,
nip.to_string(),
url
);
return Ok(UserWebVerificationStatus::Unknown);
}
let (parts, body) = response.into_parts();
// TODO: consider redirects
if parts.status == http::StatusCode::OK {
// parse body, determine if the username / key / address is present
let body_bytes = match hyper::body::to_bytes(body).await {
Ok(bytes) => bytes,
Err(e) => {
info!(
"failed to read response body for account {:?} at URL: {}: {:?}",
nip.to_string(),
url,
e
);
return Ok(UserWebVerificationStatus::Unknown);
}
};
match body_contains_user(&nip.local, pubkey, &body_bytes) {
Ok(true) => Ok(UserWebVerificationStatus::Verified),
Ok(false) => Ok(UserWebVerificationStatus::Unverified),
Err(e) => {
info!(
"error parsing response body for account {:?}: {:?}",
nip.to_string(),
e
);
Ok(UserWebVerificationStatus::Unknown)
}
// successful response, parsed as a nip-05
// document, but this name/pubkey was not
// present.
return Ok(UserWebVerificationStatus::Unverified);
}
} else {
info!(
"content length missing or exceeded limits for account: {:?}",
"unexpected status code {} for account {:?}",
parts.status,
nip.to_string()
);
Ok(UserWebVerificationStatus::Unknown)
}
} else {
info!("timeout verifying account {:?}", nip);
return Ok(UserWebVerificationStatus::Unknown);
Ok(UserWebVerificationStatus::Unknown)
}
Ok(UserWebVerificationStatus::Unknown)
}
/// Perform NIP-05 verifier tasks.

View File

@@ -25,7 +25,9 @@ impl EventResultStatus {
pub fn to_bool(&self) -> bool {
match self {
Self::Duplicate | Self::Saved => true,
Self::Invalid | Self::Blocked | Self::RateLimited | Self::Error | Self::Restricted => false,
Self::Invalid | Self::Blocked | Self::RateLimited | Self::Error | Self::Restricted => {
false
}
}
}

137
src/payment/cln_rest.rs Normal file
View File

@@ -0,0 +1,137 @@
use std::{fs, str::FromStr};
use async_trait::async_trait;
use cln_rpc::{
model::{
requests::InvoiceRequest,
responses::{InvoiceResponse, ListinvoicesInvoicesStatus, ListinvoicesResponse},
},
primitives::{Amount, AmountOrAny},
};
use config::ConfigError;
use http::{header::CONTENT_TYPE, HeaderValue, Uri};
use hyper::{client::HttpConnector, Client};
use hyper_rustls::HttpsConnector;
use nostr::Keys;
use rand::random;
use crate::{
config::Settings,
error::{Error, Result},
};
use super::{InvoiceInfo, InvoiceStatus, PaymentProcessor};
#[derive(Clone)]
pub struct ClnRestPaymentProcessor {
client: hyper::Client<HttpsConnector<HttpConnector>, hyper::Body>,
settings: Settings,
rune_header: HeaderValue,
}
impl ClnRestPaymentProcessor {
pub fn new(settings: &Settings) -> Result<Self> {
let rune_path = settings
.pay_to_relay
.rune_path
.clone()
.ok_or(ConfigError::NotFound("rune_path".to_string()))?;
let rune = String::from_utf8(fs::read(rune_path)?)
.map_err(|_| ConfigError::Message("Rune should be UTF8".to_string()))?;
let mut rune_header = HeaderValue::from_str(rune.trim())
.map_err(|_| ConfigError::Message("Invalid Rune header".to_string()))?;
rune_header.set_sensitive(true);
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();
let client = Client::builder().build::<_, hyper::Body>(https);
Ok(Self {
client,
settings: settings.clone(),
rune_header,
})
}
}
#[async_trait]
impl PaymentProcessor for ClnRestPaymentProcessor {
async fn get_invoice(&self, key: &Keys, amount: u64) -> Result<InvoiceInfo, Error> {
let random_number: u16 = random();
let memo = format!("{}: {}", random_number, key.public_key());
let body = InvoiceRequest {
cltv: None,
deschashonly: None,
expiry: None,
preimage: None,
exposeprivatechannels: None,
fallbacks: None,
amount_msat: AmountOrAny::Amount(Amount::from_sat(amount)),
description: memo.clone(),
label: "Nostr".to_string(),
};
let uri = Uri::from_str(&format!(
"{}/v1/invoice",
&self.settings.pay_to_relay.node_url
))
.map_err(|_| ConfigError::Message("Bad node URL".to_string()))?;
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(uri)
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.header("Rune", self.rune_header.clone())
.body(hyper::Body::from(serde_json::to_string(&body)?))
.expect("request builder");
let res = self.client.request(req).await?;
let body = hyper::body::to_bytes(res.into_body()).await?;
let invoice_response: InvoiceResponse = serde_json::from_slice(&body)?;
Ok(InvoiceInfo {
pubkey: key.public_key().to_string(),
payment_hash: invoice_response.payment_hash.to_string(),
bolt11: invoice_response.bolt11,
amount,
memo,
status: InvoiceStatus::Unpaid,
confirmed_at: None,
})
}
async fn check_invoice(&self, payment_hash: &str) -> Result<InvoiceStatus, Error> {
let uri = Uri::from_str(&format!(
"{}/v1/listinvoices?payment_hash={}",
&self.settings.pay_to_relay.node_url, payment_hash
))
.map_err(|_| ConfigError::Message("Bad node URL".to_string()))?;
let req = hyper::Request::builder()
.method(hyper::Method::POST)
.uri(uri)
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.header("Rune", self.rune_header.clone())
.body(hyper::Body::empty())
.expect("request builder");
let res = self.client.request(req).await?;
let body = hyper::body::to_bytes(res.into_body()).await?;
let invoice_response: ListinvoicesResponse = serde_json::from_slice(&body)?;
let invoice = invoice_response
.invoices
.first()
.ok_or(Error::CustomError("Invoice not found".to_string()))?;
let status = match invoice.status {
ListinvoicesInvoicesStatus::PAID => InvoiceStatus::Paid,
ListinvoicesInvoicesStatus::UNPAID => InvoiceStatus::Unpaid,
ListinvoicesInvoicesStatus::EXPIRED => InvoiceStatus::Expired,
};
Ok(status)
}
}

View File

@@ -1,5 +1,6 @@
use crate::error::{Error, Result};
use crate::event::Event;
use crate::payment::cln_rest::ClnRestPaymentProcessor;
use crate::payment::lnbits::LNBitsPaymentProcessor;
use crate::repo::NostrRepo;
use serde::{Deserialize, Serialize};
@@ -10,6 +11,7 @@ use async_trait::async_trait;
use nostr::key::{FromPkStr, FromSkStr};
use nostr::{key::Keys, Event as NostrEvent, EventBuilder};
pub mod cln_rest;
pub mod lnbits;
/// Payment handler
@@ -41,6 +43,7 @@ pub trait PaymentProcessor: Send + Sync {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum Processor {
LNBits,
ClnRest,
}
/// Possible states of an invoice
@@ -52,12 +55,12 @@ pub enum InvoiceStatus {
Expired,
}
impl ToString for InvoiceStatus {
fn to_string(&self) -> String {
impl std::fmt::Display for InvoiceStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvoiceStatus::Paid => "Paid".to_string(),
InvoiceStatus::Unpaid => "Unpaid".to_string(),
InvoiceStatus::Expired => "Expired".to_string(),
InvoiceStatus::Paid => write!(f, "Paid"),
InvoiceStatus::Unpaid => write!(f, "Unpaid"),
InvoiceStatus::Expired => write!(f, "Expired"),
}
}
}
@@ -109,8 +112,9 @@ impl Payment {
};
// Create processor kind defined in settings
let processor = match &settings.pay_to_relay.processor {
let processor: Arc<dyn PaymentProcessor> = match &settings.pay_to_relay.processor {
Processor::LNBits => Arc::new(LNBitsPaymentProcessor::new(&settings)),
Processor::ClnRest => Arc::new(ClnRestPaymentProcessor::new(&settings)?),
};
Ok(Payment {

View File

@@ -14,7 +14,6 @@ use sqlx::{Error, Execute, FromRow, Postgres, QueryBuilder, Row};
use std::time::{Duration, Instant};
use crate::error;
use crate::hexrange::{hex_range, HexSearch};
use crate::repo::postgres_migration::run_migrations;
use crate::server::NostrMetrics;
use crate::utils::{self, is_hex, is_lower_hex};
@@ -178,30 +177,25 @@ ON CONFLICT (id) DO NOTHING"#,
let tag_val = &tag[1];
// only single-char tags are searchable
let tag_char_opt = single_char_tagname(tag_name);
match &tag_char_opt {
Some(_) => {
// if tag value is lowercase hex;
if is_lower_hex(tag_val) && (tag_val.len() % 2 == 0) {
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, NULL, $3) \
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
if tag_char_opt.is_some() {
// if tag value is lowercase hex;
if is_lower_hex(tag_val) && (tag_val.len() % 2 == 0) {
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, NULL, $3) \
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
.bind(&id_blob)
.bind(tag_name)
.bind(hex::decode(tag_val).ok())
.execute(&mut tx)
.await
.unwrap();
} else {
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, $3, NULL) \
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
.bind(tag_name)
.bind(hex::decode(tag_val).ok())
.execute(&mut tx)
.await?;
} else {
sqlx::query("INSERT INTO tag (event_id, \"name\", value, value_hex) VALUES($1, $2, $3, NULL) \
ON CONFLICT (event_id, \"name\", value, value_hex) DO NOTHING")
.bind(&id_blob)
.bind(tag_name)
.bind(tag_val.as_bytes())
.execute(&mut tx)
.await
.unwrap();
}
.bind(tag_name)
.bind(tag_val.as_bytes())
.execute(&mut tx)
.await?;
}
None => {}
}
}
}
@@ -637,14 +631,14 @@ ON CONFLICT (id) DO NOTHING"#,
sqlx::query(
"INSERT INTO invoice (pubkey, payment_hash, amount, status, description, created_at, invoice) VALUES ($1, $2, $3, $4, $5, now(), $6)",
)
.bind(pub_key)
.bind(invoice_info.payment_hash)
.bind(invoice_info.amount as i64)
.bind(invoice_info.status)
.bind(invoice_info.memo)
.bind(invoice_info.bolt11)
.execute(&mut tx)
.await.unwrap();
.bind(pub_key)
.bind(invoice_info.payment_hash)
.bind(invoice_info.amount as i64)
.bind(invoice_info.status)
.bind(invoice_info.memo)
.bind(invoice_info.bolt11)
.execute(&mut tx)
.await.unwrap();
debug!("Invoice added");
@@ -733,140 +727,62 @@ fn query_from_filter(f: &ReqFilter) -> Option<QueryBuilder<Postgres>> {
// filter out non-hex values
let auth_vec: Vec<&String> = auth_vec.iter().filter(|a| is_hex(a)).collect();
if !auth_vec.is_empty() {
query.push("(");
// shortcut authors into "IN" query
let any_is_range = auth_vec.iter().any(|pk| pk.len() != 64);
if !any_is_range {
query.push("e.pub_key in (");
let mut pk_sep = query.separated(", ");
for pk in auth_vec.iter() {
pk_sep.push_bind(hex::decode(pk).ok());
}
query.push(") OR e.delegated_by in (");
let mut pk_delegated_sep = query.separated(", ");
for pk in auth_vec.iter() {
pk_delegated_sep.push_bind(hex::decode(pk).ok());
}
query.push(")");
push_and = true;
} else {
let mut range_authors = query.separated(" OR ");
for auth in auth_vec {
match hex_range(auth) {
Some(HexSearch::Exact(ex)) => {
range_authors
.push("(e.pub_key = ")
.push_bind_unseparated(ex.clone())
.push_unseparated(" OR e.delegated_by = ")
.push_bind_unseparated(ex)
.push_unseparated(")");
}
Some(HexSearch::Range(lower, upper)) => {
range_authors
.push("((e.pub_key > ")
.push_bind_unseparated(lower.clone())
.push_unseparated(" AND e.pub_key < ")
.push_bind_unseparated(upper.clone())
.push_unseparated(") OR (e.delegated_by > ")
.push_bind_unseparated(lower)
.push_unseparated(" AND e.delegated_by < ")
.push_bind_unseparated(upper)
.push_unseparated("))");
}
Some(HexSearch::LowerOnly(lower)) => {
range_authors
.push("(e.pub_key > ")
.push_bind_unseparated(lower.clone())
.push_unseparated(" OR e.delegated_by > ")
.push_bind_unseparated(lower)
.push_unseparated(")");
}
None => {
info!("Could not parse hex range from author {:?}", auth);
}
}
push_and = true;
}
}
query.push(")");
if auth_vec.is_empty() {
return None;
}
query.push("(e.pub_key in (");
let mut pk_sep = query.separated(", ");
for pk in auth_vec.iter() {
pk_sep.push_bind(hex::decode(pk).ok());
}
query.push(") OR e.delegated_by in (");
let mut pk_delegated_sep = query.separated(", ");
for pk in auth_vec.iter() {
pk_delegated_sep.push_bind(hex::decode(pk).ok());
}
push_and = true;
query.push("))");
}
// Query for Kind
if let Some(ks) = &f.kinds {
if !ks.is_empty() {
if push_and {
query.push(" AND ");
}
push_and = true;
query.push("e.kind in (");
let mut list_query = query.separated(", ");
for k in ks.iter() {
list_query.push_bind(*k as i64);
}
query.push(")");
if ks.is_empty() {
return None;
}
if push_and {
query.push(" AND ");
}
push_and = true;
query.push("e.kind in (");
let mut list_query = query.separated(", ");
for k in ks.iter() {
list_query.push_bind(*k as i64);
}
query.push(")");
}
// Query for event, allowing prefix matches
// Query for event,
if let Some(id_vec) = &f.ids {
// filter out non-hex values
let id_vec: Vec<&String> = id_vec.iter().filter(|a| is_hex(a)).collect();
if !id_vec.is_empty() {
if push_and {
query.push(" AND (");
} else {
query.push("(");
}
push_and = true;
// shortcut ids into "IN" query
let any_is_range = id_vec.iter().any(|pk| pk.len() != 64);
if !any_is_range {
query.push("id in (");
let mut sep = query.separated(", ");
for id in id_vec.iter() {
sep.push_bind(hex::decode(id).ok());
}
query.push(")");
} else {
// take each author and convert to a hex search
let mut id_query = query.separated(" OR ");
for id in id_vec {
match hex_range(id) {
Some(HexSearch::Exact(ex)) => {
id_query
.push("(id = ")
.push_bind_unseparated(ex)
.push_unseparated(")");
}
Some(HexSearch::Range(lower, upper)) => {
id_query
.push("(id > ")
.push_bind_unseparated(lower)
.push_unseparated(" AND id < ")
.push_bind_unseparated(upper)
.push_unseparated(")");
}
Some(HexSearch::LowerOnly(lower)) => {
id_query
.push("(id > ")
.push_bind_unseparated(lower)
.push_unseparated(")");
}
None => {
info!("Could not parse hex range from id {:?}", id);
}
}
}
}
query.push(")");
if id_vec.is_empty() {
return None;
}
if push_and {
query.push(" AND (");
} else {
query.push("(");
}
push_and = true;
query.push("id in (");
let mut sep = query.separated(", ");
for id in id_vec.iter() {
sep.push_bind(hex::decode(id).ok());
}
query.push("))");
}
// Query for tags
@@ -877,22 +793,46 @@ fn query_from_filter(f: &ReqFilter) -> Option<QueryBuilder<Postgres>> {
}
push_and = true;
let mut push_or = false;
query.push("e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and ");
for (key, val) in map.iter() {
query.push("e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and (t.\"name\" = ")
if val.is_empty() {
return None;
}
if push_or {
query.push(" OR ");
}
query
.push("(t.\"name\" = ")
.push_bind(key.to_string())
.push(" AND (value in (");
.push(" AND (");
// plain value match first
let mut tag_query = query.separated(", ");
for v in val.iter() {
if (v.len() % 2 != 0) && !is_lower_hex(v) {
let has_plain_values = val.iter().any(|v| (v.len() % 2 != 0 || !is_lower_hex(v)));
let has_hex_values = val.iter().any(|v| v.len() % 2 == 0 && is_lower_hex(v));
if has_plain_values {
query.push("value in (");
// plain value match first
let mut tag_query = query.separated(", ");
for v in val.iter().filter(|v| v.len() % 2 != 0 || !is_lower_hex(v)) {
tag_query.push_bind(v.as_bytes());
} else {
}
}
if has_plain_values && has_hex_values {
query.push(") OR ");
}
if has_hex_values {
query.push("value_hex in (");
// plain value match first
let mut tag_query = query.separated(", ");
for v in val.iter().filter(|v| v.len() % 2 == 0 && is_lower_hex(v)) {
tag_query.push_bind(hex::decode(v).ok());
}
}
query.push("))))");
query.push(")))");
push_or = true;
}
query.push(")");
}
}
@@ -925,10 +865,7 @@ fn query_from_filter(f: &ReqFilter) -> Option<QueryBuilder<Postgres>> {
query.push("e.hidden != 1::bit(1)");
}
// never display expired events
query
.push(" AND (e.expires_at IS NULL OR e.expires_at > ")
.push_bind(Utc.timestamp_opt(utils::unix_time() as i64, 0).unwrap())
.push(")");
query.push(" AND (e.expires_at IS NULL OR e.expires_at > now())");
// Apply per-filter limit to this query.
// The use of a LIMIT implies a DESC order, to capture only the most recent events.
@@ -963,3 +900,111 @@ impl FromRow<'_, PgRow> for VerificationRecord {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
#[test]
fn test_query_gen_tag_value_hex() {
let filter = ReqFilter {
ids: None,
kinds: Some(vec![1000]),
since: None,
until: None,
authors: Some(vec![
"84de35e2584d2b144aae823c9ed0b0f3deda09648530b93d1a2a146d1dea9864".to_owned(),
]),
limit: None,
tags: Some(HashMap::from([(
'p',
HashSet::from([
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed".to_owned(),
]),
)])),
force_no_match: false,
};
let q = query_from_filter(&filter).unwrap();
assert_eq!(q.sql(), "SELECT e.\"content\", e.created_at FROM \"event\" e WHERE (e.pub_key in ($1) OR e.delegated_by in ($2)) AND e.kind in ($3) AND e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and (t.\"name\" = $4 AND (value_hex in ($5)))) AND e.hidden != 1::bit(1) AND (e.expires_at IS NULL OR e.expires_at > now()) ORDER BY e.created_at ASC LIMIT 1000")
}
#[test]
fn test_query_gen_tag_value() {
let filter = ReqFilter {
ids: None,
kinds: Some(vec![1000]),
since: None,
until: None,
authors: Some(vec![
"84de35e2584d2b144aae823c9ed0b0f3deda09648530b93d1a2a146d1dea9864".to_owned(),
]),
limit: None,
tags: Some(HashMap::from([('d', HashSet::from(["test".to_owned()]))])),
force_no_match: false,
};
let q = query_from_filter(&filter).unwrap();
assert_eq!(q.sql(), "SELECT e.\"content\", e.created_at FROM \"event\" e WHERE (e.pub_key in ($1) OR e.delegated_by in ($2)) AND e.kind in ($3) AND e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and (t.\"name\" = $4 AND (value in ($5)))) AND e.hidden != 1::bit(1) AND (e.expires_at IS NULL OR e.expires_at > now()) ORDER BY e.created_at ASC LIMIT 1000")
}
#[test]
fn test_query_gen_tag_value_and_value_hex() {
let filter = ReqFilter {
ids: None,
kinds: Some(vec![1000]),
since: None,
until: None,
authors: Some(vec![
"84de35e2584d2b144aae823c9ed0b0f3deda09648530b93d1a2a146d1dea9864".to_owned(),
]),
limit: None,
tags: Some(HashMap::from([(
'd',
HashSet::from([
"test".to_owned(),
"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed".to_owned(),
]),
)])),
force_no_match: false,
};
let q = query_from_filter(&filter).unwrap();
assert_eq!(q.sql(), "SELECT e.\"content\", e.created_at FROM \"event\" e WHERE (e.pub_key in ($1) OR e.delegated_by in ($2)) AND e.kind in ($3) AND e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and (t.\"name\" = $4 AND (value in ($5) OR value_hex in ($6)))) AND e.hidden != 1::bit(1) AND (e.expires_at IS NULL OR e.expires_at > now()) ORDER BY e.created_at ASC LIMIT 1000")
}
#[test]
fn test_query_multiple_tags() {
let filter = ReqFilter {
ids: None,
kinds: Some(vec![30_001]),
since: None,
until: None,
authors: None,
limit: None,
tags: Some(HashMap::from([
('d', HashSet::from(["follow".to_owned()])),
('t', HashSet::from(["siamstr".to_owned()])),
])),
force_no_match: false,
};
let q = query_from_filter(&filter).unwrap();
assert_eq!(q.sql(), "SELECT e.\"content\", e.created_at FROM \"event\" e WHERE e.kind in ($1) AND e.id IN (SELECT ee.id FROM \"event\" ee LEFT JOIN tag t on ee.id = t.event_id WHERE ee.hidden != 1::bit(1) and (t.\"name\" = $2 AND (value in ($3))) OR (t.\"name\" = $4 AND (value in ($5)))) AND e.hidden != 1::bit(1) AND (e.expires_at IS NULL OR e.expires_at > now()) ORDER BY e.created_at ASC LIMIT 1000")
}
#[test]
fn test_query_empty_tags() {
let filter = ReqFilter {
ids: None,
kinds: Some(vec![1, 6, 16, 30023, 1063, 6969]),
since: Some(1700697846),
until: None,
authors: None,
limit: None,
tags: Some(HashMap::from([('a', HashSet::new())])),
force_no_match: false,
};
assert!(query_from_filter(&filter).is_none());
}
}

View File

@@ -205,7 +205,7 @@ CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
let event: Event = serde_json::from_str(&String::from_utf8(event_bytes).unwrap())?;
for t in event.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;

View File

@@ -4,8 +4,6 @@ use crate::config::Settings;
use crate::db::QueryResult;
use crate::error::{Error::SqlError, Result};
use crate::event::{single_char_tagname, Event};
use crate::hexrange::hex_range;
use crate::hexrange::HexSearch;
use crate::nip05::{Nip05Name, VerificationRecord};
use crate::payment::{InvoiceInfo, InvoiceStatus};
use crate::repo::sqlite_migration::{upgrade_db, STARTUP_SQL};
@@ -158,14 +156,11 @@ impl SqliteRepo {
let tagval = &tag[1];
// only single-char tags are searchable
let tagchar_opt = single_char_tagname(tagname);
match &tagchar_opt {
Some(_) => {
tx.execute(
"INSERT OR IGNORE INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
params![ev_id, &tagname, &tagval, e.kind, e.created_at],
)?;
}
None => {}
if tagchar_opt.is_some() {
tx.execute(
"INSERT OR IGNORE INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
params![ev_id, &tagname, &tagval, e.kind, e.created_at],
)?;
}
}
}
@@ -994,24 +989,9 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
// take each author and convert to a hexsearch
let mut auth_searches: Vec<String> = vec![];
for auth in authvec {
match hex_range(auth) {
Some(HexSearch::Exact(ex)) => {
auth_searches.push("author=?".to_owned());
params.push(Box::new(ex));
}
Some(HexSearch::Range(lower, upper)) => {
auth_searches.push("(author>? AND author<?)".to_owned());
params.push(Box::new(lower));
params.push(Box::new(upper));
}
Some(HexSearch::LowerOnly(lower)) => {
auth_searches.push("author>?".to_owned());
params.push(Box::new(lower));
}
None => {
trace!("Could not parse hex range from author {:?}", auth);
}
}
auth_searches.push("author=?".to_owned());
let auth_bin = hex::decode(auth).ok();
params.push(Box::new(auth_bin));
}
if !authvec.is_empty() {
let auth_clause = format!("({})", auth_searches.join(" OR "));
@@ -1032,24 +1012,9 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
// take each author and convert to a hexsearch
let mut id_searches: Vec<String> = vec![];
for id in idvec {
match hex_range(id) {
Some(HexSearch::Exact(ex)) => {
id_searches.push("event_hash=?".to_owned());
params.push(Box::new(ex));
}
Some(HexSearch::Range(lower, upper)) => {
id_searches.push("(event_hash>? AND event_hash<?)".to_owned());
params.push(Box::new(lower));
params.push(Box::new(upper));
}
Some(HexSearch::LowerOnly(lower)) => {
id_searches.push("event_hash>?".to_owned());
params.push(Box::new(lower));
}
None => {
info!("Could not parse hex range from id {:?}", id);
}
}
id_searches.push("event_hash=?".to_owned());
let id_bin = hex::decode(id).ok();
params.push(Box::new(id_bin));
}
if idvec.is_empty() {
// if the ids list was empty, we should never return
@@ -1315,6 +1280,7 @@ pub async fn db_checkpoint_task(
}
#[derive(Debug)]
#[allow(dead_code)]
enum SqliteStatus {
Ok,
Busy,

View File

@@ -40,7 +40,7 @@ PRAGMA user_version = {};
-- Event Table
CREATE TABLE IF NOT EXISTS event (
id INTEGER PRIMARY KEY,
event_hash BLOB NOT NULL, -- 4-byte hash
event_hash BLOB NOT NULL, -- 32-byte SHA256 hash
first_seen INTEGER NOT NULL, -- when the event was first seen (not authored!) (seconds since 1970)
created_at INTEGER NOT NULL, -- when the event was authored
expires_at INTEGER, -- when the event expires and may be deleted
@@ -159,7 +159,7 @@ fn mig_init(conn: &mut PooledConnection) -> usize {
);
}
Err(err) => {
error!("update failed: {}", err);
error!("update (init) failed: {}", err);
panic!("database could not be initialized");
}
}
@@ -295,7 +295,7 @@ pub fn rebuild_tags(conn: &mut PooledConnection) -> Result<()> {
let event: Event = serde_json::from_str(&event_json)?;
// look at each event, and each tag, creating new tag entries if appropriate.
for t in event.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;
@@ -325,7 +325,7 @@ pub fn rebuild_tags(conn: &mut PooledConnection) -> Result<()> {
Ok(())
}
//// Migration Scripts
// Migration Scripts
fn mig_1_to_2(conn: &mut PooledConnection) -> Result<usize> {
// only change is adding a hidden column to events.
@@ -339,7 +339,7 @@ PRAGMA user_version = 2;
info!("database schema upgraded v1 -> v2");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v1->v2) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -366,7 +366,7 @@ PRAGMA user_version = 3;
info!("database schema upgraded v2 -> v3");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v2->v3) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -416,7 +416,7 @@ PRAGMA user_version = 4;
info!("database schema upgraded v3 -> v4");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v3->v4) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -435,7 +435,7 @@ PRAGMA user_version=5;
info!("database schema upgraded v4 -> v5");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v4->v5) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -461,7 +461,7 @@ fn mig_5_to_6(conn: &mut PooledConnection) -> Result<usize> {
let event: Event = serde_json::from_str(&event_json)?;
// look at each event, and each tag, creating new tag entries if appropriate.
for t in event.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;
@@ -507,7 +507,7 @@ PRAGMA user_version = 7;
info!("database schema upgraded v6 -> v7");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v6->v7) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -528,7 +528,7 @@ PRAGMA user_version = 8;
info!("database schema upgraded v7 -> v8");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v7->v8) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -548,7 +548,7 @@ PRAGMA user_version = 9;
info!("database schema upgraded v8 -> v9");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v8->v9) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -567,7 +567,7 @@ PRAGMA user_version = 10;
info!("database schema upgraded v9 -> v10");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v9->v10) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -588,7 +588,7 @@ PRAGMA user_version = 11;
info!("database schema upgraded v10 -> v11");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v10->v11) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -643,7 +643,7 @@ PRAGMA user_version = 13;
info!("database schema upgraded v12 -> v13");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v12->v13) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -663,7 +663,7 @@ PRAGMA user_version = 14;
info!("database schema upgraded v13 -> v14");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v13->v14) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -682,7 +682,7 @@ PRAGMA user_version = 15;
info!("database schema upgraded v14 -> v15");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v14->v15) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -749,7 +749,7 @@ CREATE INDEX IF NOT EXISTS tag_covering_index ON tag(name,kind,value,created_at,
let event: Event = serde_json::from_str(&event_json)?;
// look at each event, and each tag, creating new tag entries if appropriate.
for t in event.tags.iter().filter(|x| x.len() > 1) {
let tagname = t.get(0).unwrap();
let tagname = t.first().unwrap();
let tagnamechar_opt = single_char_tagname(tagname);
if tagnamechar_opt.is_none() {
continue;
@@ -786,7 +786,7 @@ PRAGMA user_version = 17;
info!("database schema upgraded v16 -> v17");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v16->v17) failed: {}", err);
panic!("database could not be upgraded");
}
}
@@ -833,7 +833,7 @@ PRAGMA user_version = 18;
info!("database schema upgraded v17 -> v18");
}
Err(err) => {
error!("update failed: {}", err);
error!("update (v17->v18) failed: {}", err);
panic!("database could not be upgraded");
}
}

View File

@@ -194,6 +194,21 @@ async fn handle_web_request(
.unwrap());
}
if let Some(relay_file_path) = settings.info.relay_page {
match file_bytes(&relay_file_path) {
Ok(file_content) => {
return Ok(Response::builder()
.status(200)
.header("Content-Type", "text/html; charset=UTF-8")
.body(Body::from(file_content))
.expect("request builder"));
}
Err(err) => {
error!("Failed to read relay_page file: {}. Will use default", err);
}
}
}
Ok(Response::builder()
.status(200)
.header("Content-Type", "text/plain")
@@ -568,6 +583,11 @@ async fn handle_web_request(
.unwrap());
}
// Account is checked async so user will have to refresh the page a couple times after
// they have paid.
if let Err(e) = payment_tx.send(PaymentMessage::CheckAccount(pubkey.clone())) {
warn!("Could not check account: {}", e);
}
// Checks if user is already admitted
let text =
if let Ok((admission_status, _)) = repo.get_account_balance(&key.unwrap()).await {
@@ -653,6 +673,7 @@ fn get_header_string(header: &str, headers: &HeaderMap) -> Option<String> {
async fn ctrl_c_or_signal(mut shutdown_signal: Receiver<()>) {
let mut term_signal = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("could not define signal");
#[allow(clippy::never_loop)]
loop {
tokio::select! {
_ = shutdown_signal.recv() => {
@@ -893,11 +914,17 @@ pub fn start_server(settings: &Settings, shutdown_rx: MpscReceiver<()>) -> Resul
bcast_tx.clone(),
settings.clone(),
);
if let Ok(mut p) = payment_opt {
tokio::task::spawn(async move {
info!("starting payment process ...");
p.run().await;
});
match payment_opt {
Ok(mut p) => {
tokio::task::spawn(async move {
info!("starting payment process ...");
p.run().await;
});
}
Err(e) => {
error!("Failed to start payment process {e}");
std::process::exit(1);
}
}
}
@@ -1261,7 +1288,6 @@ async fn nostr_server(
// handle each type of message
let evid = ec.event_id().to_owned();
let parsed : Result<EventWrapper> = Result::<EventWrapper>::from(ec);
metrics.cmd_event.inc();
match parsed {
Ok(WrappedEvent(e)) => {
metrics.cmd_event.inc();
@@ -1342,10 +1368,15 @@ async fn nostr_server(
if conn.has_subscription(&s) {
info!("client sent duplicate subscription, ignoring (cid: {}, sub: {:?})", cid, s.id);
} else {
metrics.cmd_req.inc();
metrics.cmd_req.inc();
if let Some(ref lim) = sub_lim_opt {
lim.until_ready_with_jitter(jitter).await;
}
if settings.limits.limit_scrapers && s.is_scraper() {
info!("subscription was scraper, ignoring (cid: {}, sub: {:?})", cid, s.id);
ws_stream.send(Message::Text(format!("[\"EOSE\",\"{}\"]", s.id))).await.ok();
continue
}
let (abandon_query_tx, abandon_query_rx) = oneshot::channel::<()>();
match conn.subscribe(s.clone()) {
Ok(()) => {
@@ -1369,7 +1400,7 @@ async fn nostr_server(
// closing a request simply removes the subscription.
let parsed : Result<Close> = Result::<Close>::from(cc);
if let Ok(c) = parsed {
metrics.cmd_close.inc();
metrics.cmd_close.inc();
// check if a query is currently
// running, and remove it if so.
let stop_tx = running_queries.remove(&c.id);

View File

@@ -258,6 +258,29 @@ impl Subscription {
}
false
}
/// Is this subscription defined as a scraper query
pub fn is_scraper(&self) -> bool {
for f in &self.filters {
let mut precision = 0;
if f.ids.is_some() {
precision += 2;
}
if f.authors.is_some() {
precision += 1;
}
if f.kinds.is_some() {
precision += 1;
}
if f.tags.is_some() {
precision += 1;
}
if precision < 2 {
return true;
}
}
false
}
}
fn prefix_match(prefixes: &[String], target: &str) -> bool {
@@ -338,7 +361,7 @@ mod tests {
let s: Subscription = serde_json::from_str(raw_json)?;
assert_eq!(s.id, "some-id");
assert_eq!(s.filters.len(), 1);
assert_eq!(s.filters.get(0).unwrap().authors, None);
assert_eq!(s.filters.first().unwrap().authors, None);
Ok(())
}
@@ -402,7 +425,7 @@ mod tests {
let s: Subscription = serde_json::from_str(raw_json)?;
assert_eq!(s.id, "some-id");
assert_eq!(s.filters.len(), 1);
let first_filter = s.filters.get(0).unwrap();
let first_filter = s.filters.first().unwrap();
assert_eq!(
first_filter.authors,
Some(vec!("test-author-id".to_owned()))
@@ -633,11 +656,11 @@ mod tests {
let s: Subscription = serde_json::from_str(
r##"["REQ","xyz",{"authors":["abc", "bcd"], "since": 10, "until": 20, "limit":100, "#e": ["foo", "bar"], "#d": ["test"]}]"##,
)?;
let f = s.filters.get(0);
let f = s.filters.first();
let serialized = serde_json::to_string(&f)?;
let serialized_wrapped = format!(r##"["REQ", "xyz",{}]"##, serialized);
let parsed: Subscription = serde_json::from_str(&serialized_wrapped)?;
let parsed_filter = parsed.filters.get(0);
let parsed_filter = parsed.filters.first();
if let Some(pf) = parsed_filter {
assert_eq!(pf.since, Some(10));
assert_eq!(pf.until, Some(20));
@@ -647,4 +670,29 @@ mod tests {
}
Ok(())
}
#[test]
fn is_scraper() -> Result<()> {
assert!(serde_json::from_str::<Subscription>(
r#"["REQ","some-id",{"kinds": [1984],"since": 123,"limit":1}]"#
)?
.is_scraper());
assert!(serde_json::from_str::<Subscription>(
r#"["REQ","some-id",{"kinds": [1984]},{"kinds": [1984],"authors":["aaaa"]}]"#
)?
.is_scraper());
assert!(!serde_json::from_str::<Subscription>(
r#"["REQ","some-id",{"kinds": [1984],"authors":["aaaa"]}]"#
)?
.is_scraper());
assert!(
!serde_json::from_str::<Subscription>(r#"["REQ","some-id",{"ids": ["aaaa"]}]"#)?
.is_scraper()
);
assert!(!serde_json::from_str::<Subscription>(
r##"["REQ","some-id",{"#p": ["aaaa"],"kinds":[1,4]}]"##
)?
.is_scraper());
Ok(())
}
}

View File

@@ -103,8 +103,5 @@ fn get_available_port() -> Option<u16> {
}
pub fn port_is_available(port: u16) -> bool {
info!("checking on port {}", port);
match TcpListener::bind(("127.0.0.1", port)) {
Ok(_) => true,
Err(_) => false,
}
TcpListener::bind(("127.0.0.1", port)).is_ok()
}

View File

@@ -52,7 +52,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event(challenge);
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Ok(())));
assert_eq!(client_conn.auth_challenge(), None);
@@ -67,7 +67,7 @@ mod tests {
assert_eq!(client_conn.auth_pubkey(), None);
let event = auth_event(&"challenge".into());
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -87,14 +87,14 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap().clone();
let event = auth_event(&challenge);
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Ok(())));
assert_eq!(client_conn.auth_challenge(), None);
assert_eq!(client_conn.auth_pubkey(), Some(&event.pubkey));
let event1 = auth_event(&challenge);
let result1 = client_conn.authenticate(&event1, RELAY.into());
let result1 = client_conn.authenticate(&event1, RELAY);
assert!(matches!(result1, Ok(())));
assert_eq!(client_conn.auth_challenge(), None);
@@ -118,7 +118,7 @@ mod tests {
let mut event = auth_event(challenge);
event.sig = event.sig.chars().rev().collect::<String>();
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -138,7 +138,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event_with_kind(challenge, 9999999999999999);
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -158,7 +158,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event_with_created_at(challenge, unix_time() - 1200); // 20 minutes
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -178,7 +178,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event_with_created_at(challenge, unix_time() + 1200); // 20 minutes
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -197,7 +197,7 @@ mod tests {
let event = auth_event_without_tags();
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -216,7 +216,7 @@ mod tests {
let event = auth_event_without_challenge();
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -236,7 +236,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event_without_relay(challenge);
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -255,7 +255,7 @@ mod tests {
let event = auth_event(&"invalid challenge".into());
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}
@@ -275,7 +275,7 @@ mod tests {
let challenge = client_conn.auth_challenge().unwrap();
let event = auth_event_with_relay(challenge, &"xyz".into());
let result = client_conn.authenticate(&event, RELAY.into());
let result = client_conn.authenticate(&event, RELAY);
assert!(matches!(result, Err(Error::AuthFailure)));
}