mirror of
https://github.com/scsibug/nostr-rs-relay.git
synced 2025-09-01 11:40:48 -04:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
692925942a | ||
|
84afd4b64e | ||
|
46160bb1f9 | ||
|
2fc9168a38 | ||
|
01d0d44868 | ||
|
93f6337fda | ||
|
f3a42712a6 | ||
|
27361d064a | ||
|
3bafb611e5 | ||
|
b960ab70de | ||
|
15e2f097aa | ||
|
185f9e7abb | ||
|
f44dae6ac9 | ||
|
abc356c17d | ||
|
81f8256c37 | ||
|
b3db2bd081 | ||
|
d31e974d56 | ||
|
36eaf9fea5 | ||
|
a16c4e698a | ||
|
e63d179424 | ||
|
28b7b83a6e | ||
|
2e42b1b86e | ||
|
bd07a11f50 | ||
|
bc4b45d4b8 | ||
|
1ca5d652de | ||
|
d7cceab8fc | ||
|
2805a96e5b | ||
|
ac14a0759f | ||
|
cdd4e5949f | ||
|
5999009779 | ||
|
e36c791c53 | ||
|
d95adbcb3d | ||
|
509736c56d | ||
|
8004ea9b44 | ||
|
866c239cc9 | ||
|
6012b57e95 |
@@ -7,6 +7,7 @@ environment:
|
|||||||
packages:
|
packages:
|
||||||
- cargo
|
- cargo
|
||||||
- sqlite-devel
|
- sqlite-devel
|
||||||
|
- protobuf-compiler
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~gheartsfield/nostr-rs-relay/
|
- https://git.sr.ht/~gheartsfield/nostr-rs-relay/
|
||||||
shell: false
|
shell: false
|
||||||
|
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_nostr-rs-relay:
|
test_nostr-rs-relay:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -13,26 +13,27 @@ jobs:
|
|||||||
|
|
||||||
- name: Update local toolchain
|
- name: Update local toolchain
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt-get install -y protobuf-compiler
|
||||||
rustup update
|
rustup update
|
||||||
rustup component add clippy
|
rustup component add clippy
|
||||||
rustup install nightly
|
rustup install nightly
|
||||||
|
|
||||||
- name: Toolchain info
|
- name: Toolchain info
|
||||||
run: |
|
run: |
|
||||||
cargo --version --verbose
|
cargo --version --verbose
|
||||||
rustc --version
|
rustc --version
|
||||||
cargo clippy --version
|
cargo clippy --version
|
||||||
|
|
||||||
# - name: Lint
|
# - name: Lint
|
||||||
# run: |
|
# run: |
|
||||||
# cargo fmt -- --check
|
# cargo fmt -- --check
|
||||||
# cargo clippy -- -D warnings
|
# cargo clippy -- -D warnings
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cargo check
|
cargo check
|
||||||
cargo test --all
|
cargo test --all
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cargo build --release
|
cargo build --release --locked
|
||||||
|
274
Cargo.lock
generated
274
Cargo.lock
generated
@@ -54,9 +54,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.68"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
@@ -115,7 +115,7 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -224,9 +224,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.6.4"
|
version = "0.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc"
|
checksum = "4e246206a63c9830e118d12c894f56a82033da1a2361f5544deeee3df85c99d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
@@ -463,7 +463,7 @@ dependencies = [
|
|||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -622,9 +622,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxx"
|
name = "cxx"
|
||||||
version = "1.0.88"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "322296e2f2e5af4270b54df9e85a02ff037e271af20ba3e7fe1575515dc840b8"
|
checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cxxbridge-flags",
|
"cxxbridge-flags",
|
||||||
@@ -634,9 +634,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxx-build"
|
name = "cxx-build"
|
||||||
version = "1.0.88"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "017a1385b05d631e7875b1f151c9f012d37b53491e2a87f65bff5c262b2111d8"
|
checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
@@ -649,15 +649,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxxbridge-flags"
|
name = "cxxbridge-flags"
|
||||||
version = "1.0.88"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c26bbb078acf09bc1ecda02d4223f03bdd28bd4874edcb0379138efc499ce971"
|
checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cxxbridge-macro"
|
name = "cxxbridge-macro"
|
||||||
version = "1.0.88"
|
version = "1.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "357f40d1f06a24b60ae1fe122542c1fb05d28d32acb2aed064e84bc2ad1e252e"
|
checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -674,7 +674,7 @@ dependencies = [
|
|||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core 0.9.6",
|
"parking_lot_core 0.9.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -783,6 +783,12 @@ dependencies = [
|
|||||||
"instant",
|
"instant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.25"
|
version = "1.0.25"
|
||||||
@@ -1079,9 +1085,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
@@ -1095,6 +1101,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -1167,9 +1179,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.23"
|
version = "0.14.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -1281,24 +1293,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.3.1",
|
||||||
"io-lifetimes",
|
"io-lifetimes",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1318,9 +1330,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.60"
|
version = "0.3.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
@@ -1487,9 +1499,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -1532,7 +1550,7 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-rs-relay"
|
name = "nostr-rs-relay"
|
||||||
version = "0.8.1"
|
version = "0.8.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -1556,6 +1574,7 @@ dependencies = [
|
|||||||
"nonzero_ext",
|
"nonzero_ext",
|
||||||
"parse_duration",
|
"parse_duration",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
|
"prost",
|
||||||
"r2d2",
|
"r2d2",
|
||||||
"r2d2_sqlite",
|
"r2d2_sqlite",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
@@ -1568,6 +1587,8 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
|
"tonic",
|
||||||
|
"tonic-build",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber 0.2.25",
|
"tracing-subscriber 0.2.25",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
@@ -1657,7 +1678,7 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.2.6",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1758,7 +1779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core 0.9.6",
|
"parking_lot_core 0.9.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1777,15 +1798,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.6"
|
version = "0.9.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
|
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1819,9 +1840,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.5.4"
|
version = "2.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ab62d2fa33726dbe6321cc97ef96d8cde531e3eeaf858a058de53a8a6d40d8f"
|
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
@@ -1829,9 +1850,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_derive"
|
name = "pest_derive"
|
||||||
version = "2.5.4"
|
version = "2.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bf026e2d0581559db66d837fe5242320f525d85c76283c61f4d51a1238d65ea"
|
checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_generator",
|
"pest_generator",
|
||||||
@@ -1839,9 +1860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_generator"
|
name = "pest_generator"
|
||||||
version = "2.5.4"
|
version = "2.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b27bd18aa01d91c8ed2b61ea23406a676b42d82609c6e2581fba42f0c15f17f"
|
checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
@@ -1852,15 +1873,25 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_meta"
|
name = "pest_meta"
|
||||||
version = "2.5.4"
|
version = "2.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f02b677c1859756359fc9983c2e56a0237f18624a3789528804406b7e915e5d"
|
checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pest",
|
"pest",
|
||||||
"sha2",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
@@ -1910,7 +1941,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wepoll-ffi",
|
"wepoll-ffi",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1925,6 +1956,16 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -1951,9 +1992,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.50"
|
version = "1.0.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -1983,6 +2024,28 @@ dependencies = [
|
|||||||
"prost-derive",
|
"prost-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-build"
|
||||||
|
version = "0.11.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3f8ad728fb08fe212df3c05169e940fbb6d9d16a877ddde14644a983ba2012e"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"heck",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"multimap",
|
||||||
|
"petgraph",
|
||||||
|
"prettyplease",
|
||||||
|
"prost",
|
||||||
|
"prost-types",
|
||||||
|
"regex",
|
||||||
|
"syn",
|
||||||
|
"tempfile",
|
||||||
|
"which",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-derive"
|
name = "prost-derive"
|
||||||
version = "0.11.6"
|
version = "0.11.6"
|
||||||
@@ -2196,9 +2259,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raw-cpuid"
|
name = "raw-cpuid"
|
||||||
version = "10.6.0"
|
version = "10.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb"
|
checksum = "c307f7aacdbab3f0adee67d52739a1d71112cc068d6fab169ddeb18e48877fad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
@@ -2320,16 +2383,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.36.7"
|
version = "0.36.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
|
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"io-lifetimes",
|
"io-lifetimes",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2371,7 +2434,7 @@ version = "0.1.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2471,9 +2534,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.91"
|
version = "1.0.93"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -2525,9 +2588,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -2703,9 +2766,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
@@ -2752,10 +2815,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.4"
|
version = "1.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2781,9 +2845,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec_macros"
|
name = "tinyvec_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
@@ -2803,7 +2867,7 @@ dependencies = [
|
|||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2829,9 +2893,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-native-tls"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2873,9 +2937,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.4"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
|
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -2926,6 +2990,19 @@ dependencies = [
|
|||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tonic-build"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4"
|
||||||
|
dependencies = [
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"prost-build",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -3251,9 +3328,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
@@ -3261,9 +3338,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -3276,9 +3353,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.33"
|
version = "0.4.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
|
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -3288,9 +3365,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -3298,9 +3375,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -3311,15 +3388,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.60"
|
version = "0.3.61"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3353,6 +3430,17 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "which"
|
||||||
|
version = "4.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -3409,6 +3497,30 @@ dependencies = [
|
|||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nostr-rs-relay"
|
name = "nostr-rs-relay"
|
||||||
version = "0.8.1"
|
version = "0.8.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Greg Heartsfield <scsibug@imap.cc>"]
|
authors = ["Greg Heartsfield <scsibug@imap.cc>"]
|
||||||
description = "A relay implementation for the Nostr protocol"
|
description = "A relay implementation for the Nostr protocol"
|
||||||
@@ -16,6 +16,8 @@ clap = { version = "4.0.32", features = ["env", "default", "derive"]}
|
|||||||
tracing = "0.1.36"
|
tracing = "0.1.36"
|
||||||
tracing-subscriber = "0.2.0"
|
tracing-subscriber = "0.2.0"
|
||||||
tokio = { version = "1", features = ["full", "tracing", "signal"] }
|
tokio = { version = "1", features = ["full", "tracing", "signal"] }
|
||||||
|
prost = "0.11"
|
||||||
|
tonic = "0.8.3"
|
||||||
console-subscriber = "0.1.8"
|
console-subscriber = "0.1.8"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
@@ -52,3 +54,6 @@ bech32 = "0.9.1"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = { version="0.8.3", features = ["prost"] }
|
||||||
|
10
Dockerfile
10
Dockerfile
@@ -1,5 +1,7 @@
|
|||||||
FROM docker.io/library/rust:1.67.0 as builder
|
FROM docker.io/library/rust:1-bookworm as builder
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y cmake protobuf-compiler \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN USER=root cargo install cargo-auditable
|
RUN USER=root cargo install cargo-auditable
|
||||||
RUN USER=root cargo new --bin nostr-rs-relay
|
RUN USER=root cargo new --bin nostr-rs-relay
|
||||||
WORKDIR ./nostr-rs-relay
|
WORKDIR ./nostr-rs-relay
|
||||||
@@ -12,12 +14,14 @@ RUN rm src/*.rs
|
|||||||
|
|
||||||
# copy project source code
|
# copy project source code
|
||||||
COPY ./src ./src
|
COPY ./src ./src
|
||||||
|
COPY ./proto ./proto
|
||||||
|
COPY ./build.rs ./build.rs
|
||||||
|
|
||||||
# build auditable release using locked deps
|
# build auditable release using locked deps
|
||||||
RUN rm ./target/release/deps/nostr*relay*
|
RUN rm ./target/release/deps/nostr*relay*
|
||||||
RUN cargo auditable build --release --locked
|
RUN cargo auditable build --release --locked
|
||||||
|
|
||||||
FROM docker.io/library/debian:bullseye-slim
|
FROM docker.io/library/debian:bookworm-slim
|
||||||
|
|
||||||
ARG APP=/usr/src/app
|
ARG APP=/usr/src/app
|
||||||
ARG APP_DATA=/usr/src/app/db
|
ARG APP_DATA=/usr/src/app/db
|
||||||
|
4
build.rs
Normal file
4
build.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tonic_build::compile_protos("proto/nauthz.proto")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
18
config.toml
18
config.toml
@@ -48,6 +48,16 @@ description = "A newly created nostr-rs-relay.\n\nCustomize this with your own i
|
|||||||
# sqlite.
|
# sqlite.
|
||||||
#connection = "postgresql://postgres:nostr@localhost:7500/nostr"
|
#connection = "postgresql://postgres:nostr@localhost:7500/nostr"
|
||||||
|
|
||||||
|
[grpc]
|
||||||
|
# gRPC interfaces for externalized decisions and other extensions to
|
||||||
|
# functionality.
|
||||||
|
#
|
||||||
|
# Events can be authorized through an external service, by providing
|
||||||
|
# the URL below. In the event the server is not accessible, events
|
||||||
|
# will be permitted. The protobuf3 schema used is available in
|
||||||
|
# `proto/nauthz.proto`.
|
||||||
|
# event_admission_server = "http://[::1]:50051"
|
||||||
|
|
||||||
[network]
|
[network]
|
||||||
# Bind to this network address
|
# Bind to this network address
|
||||||
address = "0.0.0.0"
|
address = "0.0.0.0"
|
||||||
@@ -79,10 +89,10 @@ reject_future_seconds = 1800
|
|||||||
#
|
#
|
||||||
#messages_per_sec = 5
|
#messages_per_sec = 5
|
||||||
|
|
||||||
# Limit client subscriptions created per second, averaged over one
|
# Limit client subscriptions created, averaged over one minute. Must
|
||||||
# minute. Must be an integer. If not set (or set to 0), defaults to
|
# be an integer. If not set (or set to 0), defaults to unlimited.
|
||||||
# unlimited. Strongly recommended to set this to a low value such as
|
# Strongly recommended to set this to a low value such as 10 to ensure
|
||||||
# 10 to ensure fair service.
|
# fair service.
|
||||||
#subscriptions_per_min = 0
|
#subscriptions_per_min = 0
|
||||||
|
|
||||||
# UNIMPLEMENTED...
|
# UNIMPLEMENTED...
|
||||||
|
@@ -7,7 +7,7 @@ intervention. For heavily trafficked relays, there are a number of
|
|||||||
steps that the operator may need to take to maintain performance and
|
steps that the operator may need to take to maintain performance and
|
||||||
limit disk usage.
|
limit disk usage.
|
||||||
|
|
||||||
This maintenance guide is current as of version `0.7.14`. Future
|
This maintenance guide is current as of version `0.8.2`. Future
|
||||||
versions may incorporate and automate some of these steps.
|
versions may incorporate and automate some of these steps.
|
||||||
|
|
||||||
## Backing Up the Database
|
## Backing Up the Database
|
||||||
@@ -43,18 +43,15 @@ vacuum;
|
|||||||
|
|
||||||
## Clearing Hidden Events
|
## Clearing Hidden Events
|
||||||
|
|
||||||
When events are deleted, either through deletion events, metadata or
|
When events are deleted, the event is not actually removed from the
|
||||||
follower updates, or a replaceable event kind, the event is not
|
database. Instead, a flag `HIDDEN` is set to true for the event,
|
||||||
actually removed from the database. Instead, a flag `HIDDEN` is set
|
which excludes it from search results. High volume replacements from
|
||||||
to true for the event, which excludes it from search results. The
|
profile or other replaceable events are deleted, not hidden, in the
|
||||||
original intent was to ensure that subsequent rebroadcasts of the
|
current version of the relay.
|
||||||
event would be easily detected as having been deleted, and would not
|
|
||||||
need to be stored again. In practice, this decision causes excessive
|
|
||||||
growth of the `tags` table, since all the previous followers are
|
|
||||||
retained for those `HIDDEN` events.
|
|
||||||
|
|
||||||
The `event` and especially the `tag` table can be significantly
|
In the current version, removing hidden events should not result in
|
||||||
reduced in size by running these commands:
|
significant space savings, but it can still be used if there is no
|
||||||
|
desire to hold on to events that can never be re-broadcast.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
PRAGMA foreign_keys = ON;
|
PRAGMA foreign_keys = ON;
|
||||||
|
79
docs/grpc-extensions.md
Normal file
79
docs/grpc-extensions.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# gRPC Extensions Design Document
|
||||||
|
|
||||||
|
The relay will be extensible through gRPC endpoints, definable in the
|
||||||
|
main configuration file. These will allow external programs to host
|
||||||
|
logic for deciding things such as, should this event be persisted,
|
||||||
|
should this connection be allowed, and should this subscription
|
||||||
|
request be registered. The primary goal is allow for relay operator
|
||||||
|
specific functionality that allows them to serve smaller communities
|
||||||
|
and reduce spam and abuse.
|
||||||
|
|
||||||
|
This will likely evolve substantially, the first goal is to get a
|
||||||
|
basic one-way service that lets an externalized program decide on
|
||||||
|
event persistance. This does not represent the final state of gRPC
|
||||||
|
extensibility in `nostr-rs-relay`.
|
||||||
|
|
||||||
|
## Considerations
|
||||||
|
|
||||||
|
Write event latency must not be significantly affected. However, the
|
||||||
|
primary reason we are implementing this is spam/abuse protection, so
|
||||||
|
we are willing to tolerate some increase in latency if that protects
|
||||||
|
us against outages!
|
||||||
|
|
||||||
|
The interface should provide enough information to make simple
|
||||||
|
decisions, without burdening the relay to do extra queries. The
|
||||||
|
decision endpoint will be mostly responsible for maintaining state and
|
||||||
|
gathering additional details.
|
||||||
|
|
||||||
|
## Design Overview
|
||||||
|
|
||||||
|
A gRPC server may be defined in the `config.toml` file. If it exists,
|
||||||
|
the relay will attempt to connect to it and send a message for each
|
||||||
|
`EVENT` command submitted by clients. If a successful response is
|
||||||
|
returned indicating the event is permitted, the relay continues
|
||||||
|
processing the event as normal. All existing whitelist, blacklist,
|
||||||
|
and `NIP-05` validation checks are still performed and MAY still
|
||||||
|
result in the event being rejected. If a successful response is
|
||||||
|
returned indicated the decision is anything other than permit, then
|
||||||
|
the relay MUST reject the event, and return a command result to the
|
||||||
|
user (using `NIP-20`) indicating the event was blocked (optionally
|
||||||
|
providing a message).
|
||||||
|
|
||||||
|
In the event there is an error in the gRPC interface, event processing
|
||||||
|
proceeds as if gRPC was disabled (fail open). This allows gRPC
|
||||||
|
servers to be deployed with minimal chance of causing a full relay
|
||||||
|
outage.
|
||||||
|
|
||||||
|
## Design Details
|
||||||
|
|
||||||
|
Currently one procedure call is supported, `EventAdmit`, in the
|
||||||
|
`Authorization` service. It accepts the following data in order to
|
||||||
|
support authorization decisions:
|
||||||
|
|
||||||
|
- The event itself
|
||||||
|
- The client IP that submitted the event
|
||||||
|
- The client's HTTP origin header, if one exists
|
||||||
|
- The client's HTTP user agent header, if one exists
|
||||||
|
- The public key of the client, if `NIP-42` authentication was
|
||||||
|
performed (not supported in the relay yet!)
|
||||||
|
- The `NIP-05` associated with the event's public key, if it is known
|
||||||
|
to the relay
|
||||||
|
|
||||||
|
A server providing authorization decisions will return the following:
|
||||||
|
|
||||||
|
- A decision to permit or deny the event
|
||||||
|
- An optional message that explains why the event was denied, to be
|
||||||
|
transmitted to the client
|
||||||
|
|
||||||
|
## Security Issues
|
||||||
|
|
||||||
|
There is little attempt to secure this interface, since it is intended
|
||||||
|
for use processes running on the same host. It is recommended to
|
||||||
|
ensure that the gRPC server providing the API is not exposed to the
|
||||||
|
public Internet. Authorization server implementations should have
|
||||||
|
their own security reviews performed.
|
||||||
|
|
||||||
|
A slow gRPC server could cause availability issues for event
|
||||||
|
processing, since this is performed on a single thread. Avoid any
|
||||||
|
expensive or long-running processes that could result from submitted
|
||||||
|
events, since any client can initiate a gRPC call to the service.
|
1010
nauthz_server_example/Cargo.lock
generated
Normal file
1010
nauthz_server_example/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
nauthz_server_example/Cargo.toml
Normal file
13
nauthz_server_example/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "nauthz-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Common dependencies
|
||||||
|
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
|
||||||
|
prost = "0.11"
|
||||||
|
tonic = "0.8.3"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = { version="0.8.3", features = ["prost"] }
|
4
nauthz_server_example/build.rs
Normal file
4
nauthz_server_example/build.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tonic_build::compile_protos("../proto/nauthz.proto")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
61
nauthz_server_example/src/main.rs
Normal file
61
nauthz_server_example/src/main.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use tonic::{transport::Server, Request, Response, Status};
|
||||||
|
|
||||||
|
use nauthz_grpc::authorization_server::{Authorization, AuthorizationServer};
|
||||||
|
use nauthz_grpc::{EventReply, EventRequest, Decision};
|
||||||
|
|
||||||
|
pub mod nauthz_grpc {
|
||||||
|
tonic::include_proto!("nauthz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EventAuthz {
|
||||||
|
allowed_kinds: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl Authorization for EventAuthz {
|
||||||
|
|
||||||
|
async fn event_admit(
|
||||||
|
&self,
|
||||||
|
request: Request<EventRequest>,
|
||||||
|
) -> Result<Response<EventReply>, Status> {
|
||||||
|
let reply;
|
||||||
|
let req = request.into_inner();
|
||||||
|
let event = req.event.unwrap();
|
||||||
|
let content_prefix:String = event.content.chars().take(40).collect();
|
||||||
|
println!("recvd event, [kind={}, origin={:?}, nip05_domain={:?}, tag_count={}, content_sample={:?}]",
|
||||||
|
event.kind, req.origin, req.nip05.map(|x| x.domain), event.tags.len(), content_prefix);
|
||||||
|
// Permit any event with a whitelisted kind
|
||||||
|
if self.allowed_kinds.contains(&event.kind) {
|
||||||
|
println!("This looks fine! (kind={})",event.kind);
|
||||||
|
reply = nauthz_grpc::EventReply {
|
||||||
|
decision: Decision::Permit as i32,
|
||||||
|
message: None
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
println!("Blocked! (kind={})",event.kind);
|
||||||
|
reply = nauthz_grpc::EventReply {
|
||||||
|
decision: Decision::Deny as i32,
|
||||||
|
message: Some(format!("kind {} not permitted", event.kind)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Response::new(reply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let addr = "[::1]:50051".parse().unwrap();
|
||||||
|
|
||||||
|
// A simple authorization engine that allows kinds 0-3
|
||||||
|
let checker = EventAuthz {
|
||||||
|
allowed_kinds: vec![0,1,2,3],
|
||||||
|
};
|
||||||
|
println!("EventAuthz Server listening on {}", addr);
|
||||||
|
// Start serving
|
||||||
|
Server::builder()
|
||||||
|
.add_service(AuthorizationServer::new(checker))
|
||||||
|
.serve(addr)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
60
proto/nauthz.proto
Normal file
60
proto/nauthz.proto
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
// Nostr Authorization Services
|
||||||
|
package nauthz;
|
||||||
|
|
||||||
|
// Authorization for actions against a relay
|
||||||
|
service Authorization {
|
||||||
|
// Determine if an event should be admitted to the relay
|
||||||
|
rpc EventAdmit(EventRequest) returns (EventReply) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Event {
|
||||||
|
bytes id = 1; // 32-byte SHA256 hash of serialized event
|
||||||
|
bytes pubkey = 2; // 32-byte public key of event creator
|
||||||
|
fixed64 created_at = 3; // UNIX timestamp provided by event creator
|
||||||
|
uint64 kind = 4; // event kind
|
||||||
|
string content = 5; // arbitrary event contents
|
||||||
|
repeated TagEntry tags = 6; // event tag array
|
||||||
|
bytes sig = 7; // 32-byte signature of the event id
|
||||||
|
// Individual values for a single tag
|
||||||
|
message TagEntry {
|
||||||
|
repeated string values = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event data and metadata for authorization decisions
|
||||||
|
message EventRequest {
|
||||||
|
Event event =
|
||||||
|
1; // the event to be admitted for further relay processing
|
||||||
|
optional string ip_addr =
|
||||||
|
2; // IP address of the client that submitted the event
|
||||||
|
optional string origin =
|
||||||
|
3; // HTTP origin header from the client, if one exists
|
||||||
|
optional string user_agent =
|
||||||
|
4; // HTTP user-agent header from the client, if one exists
|
||||||
|
optional bytes auth_pubkey =
|
||||||
|
5; // the public key associated with a NIP-42 AUTH'd session, if
|
||||||
|
// authentication occurred
|
||||||
|
optional Nip05Name nip05 =
|
||||||
|
6; // NIP-05 address associated with the event pubkey, if it is
|
||||||
|
// known and has been validated by the relay
|
||||||
|
// A NIP_05 verification record
|
||||||
|
message Nip05Name {
|
||||||
|
string local = 1;
|
||||||
|
string domain = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A permit or deny decision
|
||||||
|
enum Decision {
|
||||||
|
DECISION_UNSPECIFIED = 0;
|
||||||
|
DECISION_PERMIT = 1; // Admit this event for further processing
|
||||||
|
DECISION_DENY = 2; // Deny persisting or propagating this event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response to a event authorization request
|
||||||
|
message EventReply {
|
||||||
|
Decision decision = 1; // decision to enforce
|
||||||
|
optional string message = 2; // informative message for the client
|
||||||
|
}
|
@@ -92,6 +92,8 @@ http {
|
|||||||
location / {
|
location / {
|
||||||
proxy_pass http://localhost:8080;
|
proxy_pass http://localhost:8080;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
proxy_read_timeout 1d;
|
||||||
|
proxy_send_timeout 1d;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "Upgrade";
|
proxy_set_header Connection "Upgrade";
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
@@ -20,7 +20,7 @@ pub fn main() -> Result<()> {
|
|||||||
let _trace_sub = tracing_subscriber::fmt::try_init();
|
let _trace_sub = tracing_subscriber::fmt::try_init();
|
||||||
println!("Nostr-rs-relay Bulk Loader");
|
println!("Nostr-rs-relay Bulk Loader");
|
||||||
// check for a database file, or create one.
|
// check for a database file, or create one.
|
||||||
let settings = config::Settings::new();
|
let settings = config::Settings::new(&None);
|
||||||
if !Path::new(&settings.database.data_directory).is_dir() {
|
if !Path::new(&settings.database.data_directory).is_dir() {
|
||||||
info!("Database directory does not exist");
|
info!("Database directory does not exist");
|
||||||
return Err(Error::DatabaseDirError);
|
return Err(Error::DatabaseDirError);
|
||||||
@@ -35,7 +35,7 @@ pub fn main() -> Result<()> {
|
|||||||
// ensure the schema version is current.
|
// ensure the schema version is current.
|
||||||
if version != DB_VERSION {
|
if version != DB_VERSION {
|
||||||
info!("version is not current, exiting");
|
info!("version is not current, exiting");
|
||||||
panic!("cannot write to schema other than v{}", DB_VERSION);
|
panic!("cannot write to schema other than v{DB_VERSION}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this channel will contain parsed events ready to be inserted
|
// this channel will contain parsed events ready to be inserted
|
||||||
|
@@ -10,4 +10,11 @@ pub struct CLIArgs {
|
|||||||
required = false,
|
required = false,
|
||||||
)]
|
)]
|
||||||
pub db: Option<String>,
|
pub db: Option<String>,
|
||||||
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
help = "Use the <file name> as the location of the config file",
|
||||||
|
required = false,
|
||||||
|
)]
|
||||||
|
pub config: Option<String>,
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,12 @@ pub struct Database {
|
|||||||
pub connection: String,
|
pub connection: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[allow(unused)]
|
||||||
|
pub struct Grpc {
|
||||||
|
pub event_admission_server: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct Network {
|
pub struct Network {
|
||||||
@@ -145,6 +151,7 @@ pub struct Settings {
|
|||||||
pub info: Info,
|
pub info: Info,
|
||||||
pub diagnostics: Diagnostics,
|
pub diagnostics: Diagnostics,
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
|
pub grpc: Grpc,
|
||||||
pub network: Network,
|
pub network: Network,
|
||||||
pub limits: Limits,
|
pub limits: Limits,
|
||||||
pub authorization: Authorization,
|
pub authorization: Authorization,
|
||||||
@@ -155,10 +162,10 @@ pub struct Settings {
|
|||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new(config_file_name: &Option<String>) -> Self {
|
||||||
let default_settings = Self::default();
|
let default_settings = Self::default();
|
||||||
// attempt to construct settings with file
|
// attempt to construct settings with file
|
||||||
let from_file = Self::new_from_default(&default_settings);
|
let from_file = Self::new_from_default(&default_settings, config_file_name);
|
||||||
match from_file {
|
match from_file {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -168,13 +175,19 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_from_default(default: &Settings) -> Result<Self, ConfigError> {
|
|
||||||
|
fn new_from_default(default: &Settings, config_file_name: &Option<String>) -> Result<Self, ConfigError> {
|
||||||
|
let default_config_file_name = "config.toml".to_string();
|
||||||
|
let config: &String = match config_file_name {
|
||||||
|
Some(value) => value,
|
||||||
|
None => &default_config_file_name
|
||||||
|
};
|
||||||
let builder = Config::builder();
|
let builder = Config::builder();
|
||||||
let config: Config = builder
|
let config: Config = builder
|
||||||
// use defaults
|
// use defaults
|
||||||
.add_source(Config::try_from(default)?)
|
.add_source(Config::try_from(default)?)
|
||||||
// override with file contents
|
// override with file contents
|
||||||
.add_source(File::with_name("config.toml"))
|
.add_source(File::with_name(config))
|
||||||
.build()?;
|
.build()?;
|
||||||
let mut settings: Settings = config.try_deserialize()?;
|
let mut settings: Settings = config.try_deserialize()?;
|
||||||
// ensure connection pool size is logical
|
// ensure connection pool size is logical
|
||||||
@@ -214,6 +227,9 @@ impl Default for Settings {
|
|||||||
max_conn: 8,
|
max_conn: 8,
|
||||||
connection: "".to_owned(),
|
connection: "".to_owned(),
|
||||||
},
|
},
|
||||||
|
grpc: Grpc {
|
||||||
|
event_admission_server: None,
|
||||||
|
},
|
||||||
network: Network {
|
network: Network {
|
||||||
port: 8080,
|
port: 8080,
|
||||||
ping_interval_seconds: 300,
|
ping_interval_seconds: 300,
|
||||||
|
62
src/db.rs
62
src/db.rs
@@ -4,6 +4,7 @@ use crate::error::{Error, Result};
|
|||||||
use crate::event::Event;
|
use crate::event::Event;
|
||||||
use crate::notice::Notice;
|
use crate::notice::Notice;
|
||||||
use crate::server::NostrMetrics;
|
use crate::server::NostrMetrics;
|
||||||
|
use crate::nauthz;
|
||||||
use governor::clock::Clock;
|
use governor::clock::Clock;
|
||||||
use governor::{Quota, RateLimiter};
|
use governor::{Quota, RateLimiter};
|
||||||
use r2d2;
|
use r2d2;
|
||||||
@@ -26,6 +27,9 @@ pub type PooledConnection = r2d2::PooledConnection<r2d2_sqlite::SqliteConnection
|
|||||||
pub struct SubmittedEvent {
|
pub struct SubmittedEvent {
|
||||||
pub event: Event,
|
pub event: Event,
|
||||||
pub notice_tx: tokio::sync::mpsc::Sender<Notice>,
|
pub notice_tx: tokio::sync::mpsc::Sender<Notice>,
|
||||||
|
pub source_ip: String,
|
||||||
|
pub origin: Option<String>,
|
||||||
|
pub user_agent: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Database file
|
/// Database file
|
||||||
@@ -100,6 +104,18 @@ pub async fn db_writer(
|
|||||||
lim_opt = Some(RateLimiter::direct(Quota::per_minute(quota)));
|
lim_opt = Some(RateLimiter::direct(Quota::per_minute(quota)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// create a client if GRPC is enabled.
|
||||||
|
// Check with externalized event admitter service, if one is defined.
|
||||||
|
let mut grpc_client = if let Some(svr) = settings.grpc.event_admission_server {
|
||||||
|
Some(nauthz::EventAuthzService::connect(&svr).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
//let gprc_client = settings.grpc.event_admission_server.map(|s| {
|
||||||
|
// event_admitter_connect(&s);
|
||||||
|
// });
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if shutdown.try_recv().is_ok() {
|
if shutdown.try_recv().is_ok() {
|
||||||
info!("shutting down database writer");
|
info!("shutting down database writer");
|
||||||
@@ -164,9 +180,16 @@ pub async fn db_writer(
|
|||||||
metadata_tx.send(event.clone()).ok();
|
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)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// check for NIP-05 verification
|
// check for NIP-05 verification
|
||||||
if nip05_enabled {
|
if nip05_enabled && validation.is_some() {
|
||||||
match repo.get_latest_user_verification(&event.pubkey).await {
|
match validation.as_ref().unwrap() {
|
||||||
Ok(uv) => {
|
Ok(uv) => {
|
||||||
if uv.is_valid(&settings.verified_users) {
|
if uv.is_valid(&settings.verified_users) {
|
||||||
info!(
|
info!(
|
||||||
@@ -174,6 +197,7 @@ pub async fn db_writer(
|
|||||||
uv.name.to_string(),
|
uv.name.to_string(),
|
||||||
event.get_author_prefix()
|
event.get_author_prefix()
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
"rejecting event, author ({:?} / {:?}) verification invalid (expired/wrong domain)",
|
"rejecting event, author ({:?} / {:?}) verification invalid (expired/wrong domain)",
|
||||||
@@ -208,6 +232,35 @@ pub async fn db_writer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nip05 address
|
||||||
|
let nip05_address : Option<crate::nip05::Nip05Name> = validation.and_then(|x| x.ok().map(|y| y.name));
|
||||||
|
|
||||||
|
// GRPC check
|
||||||
|
if let Some(ref mut c) = grpc_client {
|
||||||
|
trace!("checking if grpc permits");
|
||||||
|
let grpc_start = Instant::now();
|
||||||
|
let decision_res = c.admit_event(&event, &subm_event.source_ip, subm_event.origin, subm_event.user_agent, nip05_address).await;
|
||||||
|
match decision_res {
|
||||||
|
Ok(decision) => {
|
||||||
|
if !decision.permitted() {
|
||||||
|
// GPRC returned a decision to reject this event
|
||||||
|
info!("GRPC rejected event: {:?} (kind: {}) from: {:?} in: {:?} (IP: {:?})",
|
||||||
|
event.get_event_id_prefix(),
|
||||||
|
event.kind,
|
||||||
|
event.get_author_prefix(),
|
||||||
|
grpc_start.elapsed(),
|
||||||
|
subm_event.source_ip);
|
||||||
|
notice_tx.try_send(Notice::blocked(event.id, &decision.message().unwrap_or_else(|| "".to_string()))).ok();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!("GRPC server error: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: cache recent list of authors to remove a DB call.
|
// TODO: cache recent list of authors to remove a DB call.
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
if event.is_ephemeral() {
|
if event.is_ephemeral() {
|
||||||
@@ -227,11 +280,12 @@ pub async fn db_writer(
|
|||||||
notice_tx.try_send(Notice::duplicate(event.id)).ok();
|
notice_tx.try_send(Notice::duplicate(event.id)).ok();
|
||||||
} else {
|
} else {
|
||||||
info!(
|
info!(
|
||||||
"persisted event: {:?} (kind: {}) from: {:?} in: {:?}",
|
"persisted event: {:?} (kind: {}) from: {:?} in: {:?} (IP: {:?})",
|
||||||
event.get_event_id_prefix(),
|
event.get_event_id_prefix(),
|
||||||
event.kind,
|
event.kind,
|
||||||
event.get_author_prefix(),
|
event.get_author_prefix(),
|
||||||
start.elapsed()
|
start.elapsed(),
|
||||||
|
subm_event.source_ip,
|
||||||
);
|
);
|
||||||
event_write = true;
|
event_write = true;
|
||||||
// send this out to all clients
|
// send this out to all clients
|
||||||
|
@@ -108,7 +108,7 @@ impl ConditionQuery {
|
|||||||
sigstr: &str,
|
sigstr: &str,
|
||||||
) -> Option<ConditionQuery> {
|
) -> Option<ConditionQuery> {
|
||||||
// form the token
|
// form the token
|
||||||
let tok = format!("nostr:delegation:{}:{}", delegatee, cond_query);
|
let tok = format!("nostr:delegation:{delegatee}:{cond_query}");
|
||||||
// form SHA256 hash
|
// form SHA256 hash
|
||||||
let digest: sha256::Hash = sha256::Hash::hash(tok.as_bytes());
|
let digest: sha256::Hash = sha256::Hash::hash(tok.as_bytes());
|
||||||
let sig = schnorr::Signature::from_str(sigstr).unwrap();
|
let sig = schnorr::Signature::from_str(sigstr).unwrap();
|
||||||
|
13
src/error.rs
13
src/error.rs
@@ -62,6 +62,12 @@ pub enum Error {
|
|||||||
HexError(hex::FromHexError),
|
HexError(hex::FromHexError),
|
||||||
#[error("Delegation parse error")]
|
#[error("Delegation parse error")]
|
||||||
DelegationParseError,
|
DelegationParseError,
|
||||||
|
#[error("Channel closed error")]
|
||||||
|
ChannelClosed,
|
||||||
|
#[error("Authz error")]
|
||||||
|
AuthzError,
|
||||||
|
#[error("Tonic GRPC error")]
|
||||||
|
TonicError(tonic::Status),
|
||||||
#[error("Unknown/Undocumented")]
|
#[error("Unknown/Undocumented")]
|
||||||
UnknownError,
|
UnknownError,
|
||||||
}
|
}
|
||||||
@@ -130,3 +136,10 @@ impl From<config::ConfigError> for Error {
|
|||||||
Error::ConfigError(r)
|
Error::ConfigError(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<tonic::Status> for Error {
|
||||||
|
/// Wrap Config error
|
||||||
|
fn from(r: tonic::Status) -> Self {
|
||||||
|
Error::TonicError(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -143,9 +143,9 @@ impl Event {
|
|||||||
let default = "".to_string();
|
let default = "".to_string();
|
||||||
let dvals:Vec<&String> = self.tags
|
let dvals:Vec<&String> = self.tags
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| x.len() >= 1)
|
.filter(|x| !x.is_empty())
|
||||||
.filter(|x| x.get(0).unwrap() == "d")
|
.filter(|x| x.get(0).unwrap() == "d")
|
||||||
.map(|x| x.get(1).unwrap_or_else(|| &default)).take(1)
|
.map(|x| x.get(1).unwrap_or(&default)).take(1)
|
||||||
.collect();
|
.collect();
|
||||||
let dval_first = dvals.get(0);
|
let dval_first = dvals.get(0);
|
||||||
match dval_first {
|
match dval_first {
|
||||||
@@ -292,7 +292,7 @@ impl Event {
|
|||||||
let c = c_opt.unwrap();
|
let c = c_opt.unwrap();
|
||||||
// * compute the sha256sum.
|
// * compute the sha256sum.
|
||||||
let digest: sha256::Hash = sha256::Hash::hash(c.as_bytes());
|
let digest: sha256::Hash = sha256::Hash::hash(c.as_bytes());
|
||||||
let hex_digest = format!("{:x}", digest);
|
let hex_digest = format!("{digest:x}");
|
||||||
// * ensure the id matches the computed sha256sum.
|
// * ensure the id matches the computed sha256sum.
|
||||||
if self.id != hex_digest {
|
if self.id != hex_digest {
|
||||||
debug!("event id does not match digest");
|
debug!("event id does not match digest");
|
||||||
|
@@ -9,6 +9,7 @@ pub mod event;
|
|||||||
pub mod hexrange;
|
pub mod hexrange;
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod nip05;
|
pub mod nip05;
|
||||||
|
pub mod nauthz;
|
||||||
pub mod notice;
|
pub mod notice;
|
||||||
pub mod repo;
|
pub mod repo;
|
||||||
pub mod subscription;
|
pub mod subscription;
|
||||||
|
13
src/main.rs
13
src/main.rs
@@ -11,9 +11,14 @@ use console_subscriber::ConsoleLayer;
|
|||||||
|
|
||||||
/// Start running a Nostr relay server.
|
/// Start running a Nostr relay server.
|
||||||
fn main() {
|
fn main() {
|
||||||
// configure settings from config.toml
|
let args = CLIArgs::parse();
|
||||||
// replace default settings with those read from config.toml
|
|
||||||
let mut settings = config::Settings::new();
|
// get config file name from args
|
||||||
|
let config_file_arg = args.config;
|
||||||
|
|
||||||
|
// configure settings from the config file (defaults to config.toml)
|
||||||
|
// replace default settings with those read from the config file
|
||||||
|
let mut settings = config::Settings::new(&config_file_arg);
|
||||||
|
|
||||||
// setup tracing
|
// setup tracing
|
||||||
if settings.diagnostics.tracing {
|
if settings.diagnostics.tracing {
|
||||||
@@ -25,8 +30,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
info!("Starting up from main");
|
info!("Starting up from main");
|
||||||
|
|
||||||
let args = CLIArgs::parse();
|
|
||||||
|
|
||||||
// get database directory from args
|
// get database directory from args
|
||||||
let db_dir_arg = args.db;
|
let db_dir_arg = args.db;
|
||||||
|
|
||||||
|
110
src/nauthz.rs
Normal file
110
src/nauthz.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
|
use crate::{event::Event, nip05::Nip05Name};
|
||||||
|
use nauthz_grpc::authorization_client::AuthorizationClient;
|
||||||
|
use nauthz_grpc::event::TagEntry;
|
||||||
|
use nauthz_grpc::{Decision, Event as GrpcEvent, EventReply, EventRequest};
|
||||||
|
use tracing::{info, warn};
|
||||||
|
|
||||||
|
pub mod nauthz_grpc {
|
||||||
|
tonic::include_proto!("nauthz");
|
||||||
|
}
|
||||||
|
|
||||||
|
// A decision for the DB to act upon
|
||||||
|
pub trait AuthzDecision: Send + Sync {
|
||||||
|
fn permitted(&self) -> bool;
|
||||||
|
fn message(&self) -> Option<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthzDecision for EventReply {
|
||||||
|
fn permitted(&self) -> bool {
|
||||||
|
self.decision == Decision::Permit as i32
|
||||||
|
}
|
||||||
|
fn message(&self) -> Option<String> {
|
||||||
|
self.message.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A connection to an event admission GRPC server
|
||||||
|
pub struct EventAuthzService {
|
||||||
|
server_addr: String,
|
||||||
|
conn: Option<AuthorizationClient<tonic::transport::Channel>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// conversion of Nip05Names into GRPC type
|
||||||
|
impl std::convert::From<Nip05Name> for nauthz_grpc::event_request::Nip05Name {
|
||||||
|
fn from(value: Nip05Name) -> Self {
|
||||||
|
nauthz_grpc::event_request::Nip05Name {
|
||||||
|
local: value.local.clone(),
|
||||||
|
domain: value.domain.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conversion of event tags into gprc struct
|
||||||
|
fn tags_to_protobuf(tags: &Vec<Vec<String>>) -> Vec<TagEntry> {
|
||||||
|
tags.iter()
|
||||||
|
.map(|x| TagEntry { values: x.clone() })
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventAuthzService {
|
||||||
|
pub async fn connect(server_addr: &str) -> EventAuthzService {
|
||||||
|
let mut eas = EventAuthzService {
|
||||||
|
server_addr: server_addr.to_string(),
|
||||||
|
conn: None,
|
||||||
|
};
|
||||||
|
eas.ready_connection().await;
|
||||||
|
eas
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ready_connection(self: &mut Self) {
|
||||||
|
if self.conn.is_none() {
|
||||||
|
let client = AuthorizationClient::connect(self.server_addr.to_string()).await;
|
||||||
|
if let Err(ref msg) = client {
|
||||||
|
warn!("could not connect to nostr authz GRPC server: {:?}", msg);
|
||||||
|
} else {
|
||||||
|
info!("connected to nostr authorization GRPC server");
|
||||||
|
}
|
||||||
|
self.conn = client.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn admit_event(
|
||||||
|
self: &mut Self,
|
||||||
|
event: &Event,
|
||||||
|
ip: &str,
|
||||||
|
origin: Option<String>,
|
||||||
|
user_agent: Option<String>,
|
||||||
|
nip05: Option<Nip05Name>,
|
||||||
|
) -> Result<Box<dyn AuthzDecision>> {
|
||||||
|
self.ready_connection().await;
|
||||||
|
let id_blob = hex::decode(&event.id)?;
|
||||||
|
let pubkey_blob = hex::decode(&event.pubkey)?;
|
||||||
|
let sig_blob = hex::decode(&event.sig)?;
|
||||||
|
if let Some(ref mut c) = self.conn {
|
||||||
|
let gevent = GrpcEvent {
|
||||||
|
id: id_blob,
|
||||||
|
pubkey: pubkey_blob,
|
||||||
|
sig: sig_blob,
|
||||||
|
created_at: event.created_at,
|
||||||
|
kind: event.kind,
|
||||||
|
content: event.content.clone(),
|
||||||
|
tags: tags_to_protobuf(&event.tags),
|
||||||
|
};
|
||||||
|
let svr_res = c
|
||||||
|
.event_admit(EventRequest {
|
||||||
|
event: Some(gevent),
|
||||||
|
ip_addr: Some(ip.to_string()),
|
||||||
|
origin,
|
||||||
|
user_agent,
|
||||||
|
auth_pubkey: None,
|
||||||
|
nip05: nip05.map(|x| nauthz_grpc::event_request::Nip05Name::from(x)),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let reply = svr_res.into_inner();
|
||||||
|
return Ok(Box::new(reply));
|
||||||
|
} else {
|
||||||
|
return Err(Error::AuthzError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/nip05.rs
16
src/nip05.rs
@@ -42,8 +42,8 @@ pub struct Verifier {
|
|||||||
/// A NIP-05 identifier is a local part and domain.
|
/// A NIP-05 identifier is a local part and domain.
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
pub struct Nip05Name {
|
pub struct Nip05Name {
|
||||||
local: String,
|
pub local: String,
|
||||||
domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nip05Name {
|
impl Nip05Name {
|
||||||
@@ -107,7 +107,7 @@ impl std::fmt::Display for Nip05Name {
|
|||||||
/// Check if the specified username and address are present and match in this response body
|
/// Check if the specified username and address are present and match in this response body
|
||||||
fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes) -> Result<bool> {
|
fn body_contains_user(username: &str, address: &str, bytes: &hyper::body::Bytes) -> Result<bool> {
|
||||||
// convert the body into json
|
// convert the body into json
|
||||||
let body: serde_json::Value = serde_json::from_slice(&bytes)?;
|
let body: serde_json::Value = serde_json::from_slice(bytes)?;
|
||||||
// ensure we have a names object.
|
// ensure we have a names object.
|
||||||
let names_map = body
|
let names_map = body
|
||||||
.as_object()
|
.as_object()
|
||||||
@@ -257,8 +257,15 @@ impl Verifier {
|
|||||||
// run a loop, restarting on failure
|
// run a loop, restarting on failure
|
||||||
loop {
|
loop {
|
||||||
let res = self.run_internal().await;
|
let res = self.run_internal().await;
|
||||||
if let Err(e) = res {
|
match res {
|
||||||
|
Err(Error::ChannelClosed) => {
|
||||||
|
// channel was closed, we are shutting down
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
info!("error in verifier: {:?}", e);
|
info!("error in verifier: {:?}", e);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,6 +312,7 @@ impl Verifier {
|
|||||||
}
|
}
|
||||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
||||||
info!("metadata broadcast channel closed");
|
info!("metadata broadcast channel closed");
|
||||||
|
return Err(Error::ChannelClosed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -77,26 +77,25 @@ impl NostrRepo for PostgresRepo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(d_tag) = e.distinct_param() {
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
let repl_count:i64;
|
let repl_count:i64 = if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
||||||
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
sqlx::query_scalar(
|
||||||
repl_count = sqlx::query_scalar(
|
|
||||||
"SELECT count(*) AS count FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.pub_key=$1 AND e.kind=$2 AND t.name='d' AND t.value_hex=$3 AND e.created_at >= $4 LIMIT 1;")
|
"SELECT count(*) AS count FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.pub_key=$1 AND e.kind=$2 AND t.name='d' AND t.value_hex=$3 AND e.created_at >= $4 LIMIT 1;")
|
||||||
.bind(hex::decode(&e.pubkey).ok())
|
.bind(hex::decode(&e.pubkey).ok())
|
||||||
.bind(e.kind as i64)
|
.bind(e.kind as i64)
|
||||||
.bind(hex::decode(d_tag).ok())
|
.bind(hex::decode(d_tag).ok())
|
||||||
.bind(Utc.timestamp_opt(e.created_at as i64, 0).unwrap())
|
.bind(Utc.timestamp_opt(e.created_at as i64, 0).unwrap())
|
||||||
.fetch_one(&mut tx)
|
.fetch_one(&mut tx)
|
||||||
.await?;
|
.await?
|
||||||
} else {
|
} else {
|
||||||
repl_count = sqlx::query_scalar(
|
sqlx::query_scalar(
|
||||||
"SELECT count(*) AS count FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.pub_key=$1 AND e.kind=$2 AND t.name='d' AND t.value=$3 AND e.created_at >= $4 LIMIT 1;")
|
"SELECT count(*) AS count FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.pub_key=$1 AND e.kind=$2 AND t.name='d' AND t.value=$3 AND e.created_at >= $4 LIMIT 1;")
|
||||||
.bind(hex::decode(&e.pubkey).ok())
|
.bind(hex::decode(&e.pubkey).ok())
|
||||||
.bind(e.kind as i64)
|
.bind(e.kind as i64)
|
||||||
.bind(d_tag.as_bytes())
|
.bind(d_tag.as_bytes())
|
||||||
.bind(Utc.timestamp_opt(e.created_at as i64, 0).unwrap())
|
.bind(Utc.timestamp_opt(e.created_at as i64, 0).unwrap())
|
||||||
.fetch_one(&mut tx)
|
.fetch_one(&mut tx)
|
||||||
.await?;
|
.await?
|
||||||
}
|
};
|
||||||
// if any rows were returned, then some newer event with
|
// if any rows were returned, then some newer event with
|
||||||
// the same author/kind/tag value exist, and we can ignore
|
// the same author/kind/tag value exist, and we can ignore
|
||||||
// this event.
|
// this event.
|
||||||
@@ -178,22 +177,21 @@ ON CONFLICT (id) DO NOTHING"#,
|
|||||||
// parameterized replaceable events
|
// parameterized replaceable events
|
||||||
// check for parameterized replaceable events that would be hidden; don't insert these either.
|
// check for parameterized replaceable events that would be hidden; don't insert these either.
|
||||||
if let Some(d_tag) = e.distinct_param() {
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
let update_count;
|
let update_count = if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
||||||
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
sqlx::query("DELETE FROM event WHERE kind=$1 AND pub_key=$2 AND id IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=$1 AND e.pub_key=$2 AND t.name='d' AND t.value_hex=$3 ORDER BY created_at DESC OFFSET 1);")
|
||||||
update_count = sqlx::query("DELETE FROM event WHERE kind=$1 AND pub_key=$2 AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=$1 AND e.pub_key=$2 AND t.name='d' AND t.value_hex=$3 ORDER BY created_at DESC LIMIT 1);")
|
|
||||||
.bind(e.kind as i64)
|
.bind(e.kind as i64)
|
||||||
.bind(hex::decode(&e.pubkey).ok())
|
.bind(hex::decode(&e.pubkey).ok())
|
||||||
.bind(hex::decode(d_tag).ok())
|
.bind(hex::decode(d_tag).ok())
|
||||||
.execute(&mut tx)
|
.execute(&mut tx)
|
||||||
.await?.rows_affected();
|
.await?.rows_affected()
|
||||||
} else {
|
} else {
|
||||||
update_count = sqlx::query("DELETE FROM event WHERE kind=$1 AND pub_key=$2 AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=$1 AND e.pub_key=$2 AND t.name='d' AND t.value=$3 ORDER BY created_at DESC LIMIT 1);")
|
sqlx::query("DELETE FROM event WHERE kind=$1 AND pub_key=$2 AND id IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=$1 AND e.pub_key=$2 AND t.name='d' AND t.value=$3 ORDER BY created_at DESC OFFSET 1);")
|
||||||
.bind(e.kind as i64)
|
.bind(e.kind as i64)
|
||||||
.bind(hex::decode(&e.pubkey).ok())
|
.bind(hex::decode(&e.pubkey).ok())
|
||||||
.bind(d_tag.as_bytes())
|
.bind(d_tag.as_bytes())
|
||||||
.execute(&mut tx)
|
.execute(&mut tx)
|
||||||
.await?.rows_affected();
|
.await?.rows_affected()
|
||||||
}
|
};
|
||||||
if update_count > 0 {
|
if update_count > 0 {
|
||||||
info!(
|
info!(
|
||||||
"removed {} older parameterized replaceable kind {} events for author: {:?}",
|
"removed {} older parameterized replaceable kind {} events for author: {:?}",
|
||||||
|
@@ -34,11 +34,15 @@ pub async fn run_migrations(db: &PostgresPool) -> crate::error::Result<usize> {
|
|||||||
if m002_result == MigrationResult::Upgraded {
|
if m002_result == MigrationResult::Upgraded {
|
||||||
m002::rebuild_tags(db).await?;
|
m002::rebuild_tags(db).await?;
|
||||||
}
|
}
|
||||||
|
run_migration(m003::migration(), db).await;
|
||||||
Ok(current_version(db).await as usize)
|
Ok(current_version(db).await as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn current_version(db: &PostgresPool) -> i64 {
|
async fn current_version(db: &PostgresPool) -> i64 {
|
||||||
sqlx::query_scalar("SELECT max(serial_number) FROM migrations;").fetch_one(db).await.unwrap()
|
sqlx::query_scalar("SELECT max(serial_number) FROM migrations;")
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_migrations_table(db: &PostgresPool) {
|
async fn prepare_migrations_table(db: &PostgresPool) {
|
||||||
@@ -77,7 +81,7 @@ async fn run_migration(migration: impl Migration, db: &PostgresPool) -> Migratio
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
transaction.commit().await.unwrap();
|
transaction.commit().await.unwrap();
|
||||||
return MigrationResult::Upgraded;
|
MigrationResult::Upgraded
|
||||||
}
|
}
|
||||||
|
|
||||||
mod m001 {
|
mod m001 {
|
||||||
@@ -137,15 +141,15 @@ CREATE INDEX user_verification_name_idx ON user_verification USING btree (name);
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod m002 {
|
mod m002 {
|
||||||
|
use async_std::stream::StreamExt;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use sqlx::Row;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use async_std::stream::StreamExt;
|
|
||||||
use sqlx::Row;
|
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
|
|
||||||
use crate::repo::postgres_migration::{Migration, SimpleSqlMigration};
|
use crate::event::{single_char_tagname, Event};
|
||||||
use crate::repo::postgres::PostgresPool;
|
use crate::repo::postgres::PostgresPool;
|
||||||
use crate::event::{Event, single_char_tagname};
|
use crate::repo::postgres_migration::{Migration, SimpleSqlMigration};
|
||||||
use crate::utils::is_lower_hex;
|
use crate::utils::is_lower_hex;
|
||||||
|
|
||||||
pub const VERSION: i64 = 2;
|
pub const VERSION: i64 = 2;
|
||||||
@@ -172,23 +176,31 @@ CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
|
|||||||
let mut tx = db.begin().await.unwrap();
|
let mut tx = db.begin().await.unwrap();
|
||||||
let mut update_tx = db.begin().await.unwrap();
|
let mut update_tx = db.begin().await.unwrap();
|
||||||
// Clear out table
|
// Clear out table
|
||||||
sqlx::query("DELETE FROM tag;").execute(&mut update_tx).await?;
|
sqlx::query("DELETE FROM tag;")
|
||||||
|
.execute(&mut update_tx)
|
||||||
|
.await?;
|
||||||
{
|
{
|
||||||
let event_count: i64 =
|
let event_count: i64 = sqlx::query_scalar("SELECT COUNT(*) from event;")
|
||||||
sqlx::query_scalar("SELECT COUNT(*) from event;")
|
|
||||||
.fetch_one(&mut tx)
|
.fetch_one(&mut tx)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let bar = ProgressBar::new(event_count.try_into().unwrap()).with_message("rebuilding tags table");
|
let bar = ProgressBar::new(event_count.try_into().unwrap())
|
||||||
bar.set_style(ProgressStyle::with_template("[{elapsed_precise}] {bar:40.white/blue} {pos:>7}/{len:7} [{percent}%] {msg}").unwrap());
|
.with_message("rebuilding tags table");
|
||||||
let mut events = sqlx::query("SELECT id, content FROM event ORDER BY id;").fetch(&mut tx);
|
bar.set_style(
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
"[{elapsed_precise}] {bar:40.white/blue} {pos:>7}/{len:7} [{percent}%] {msg}",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let mut events =
|
||||||
|
sqlx::query("SELECT id, content FROM event ORDER BY id;").fetch(&mut tx);
|
||||||
while let Some(row) = events.next().await {
|
while let Some(row) = events.next().await {
|
||||||
bar.inc(1);
|
bar.inc(1);
|
||||||
// get the row id and content
|
// get the row id and content
|
||||||
let row = row.unwrap();
|
let row = row.unwrap();
|
||||||
let event_id: Vec<u8> = row.get(0);
|
let event_id: Vec<u8> = row.get(0);
|
||||||
let event_bytes: Vec<u8> = row.get(1);
|
let event_bytes: Vec<u8> = row.get(1);
|
||||||
let event:Event = serde_json::from_str(&String::from_utf8(event_bytes).unwrap())?;
|
let event: Event = serde_json::from_str(&String::from_utf8(event_bytes).unwrap())?;
|
||||||
|
|
||||||
for t in event.tags.iter().filter(|x| x.len() > 1) {
|
for t in event.tags.iter().filter(|x| x.len() > 1) {
|
||||||
let tagname = t.get(0).unwrap();
|
let tagname = t.get(0).unwrap();
|
||||||
@@ -202,12 +214,21 @@ CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
|
|||||||
// this means it needs to be even length and lowercase.
|
// this means it needs to be even length and lowercase.
|
||||||
if (tagval.len() % 2 == 0) && is_lower_hex(tagval) {
|
if (tagval.len() % 2 == 0) && is_lower_hex(tagval) {
|
||||||
let q = "INSERT INTO tag (event_id, \"name\", value_hex) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;";
|
let q = "INSERT INTO tag (event_id, \"name\", value_hex) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;";
|
||||||
sqlx::query(q).bind(&event_id).bind(&tagname).bind(hex::decode(tagval).ok()).execute(&mut update_tx).await?;
|
sqlx::query(q)
|
||||||
|
.bind(&event_id)
|
||||||
|
.bind(tagname)
|
||||||
|
.bind(hex::decode(tagval).ok())
|
||||||
|
.execute(&mut update_tx)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
let q = "INSERT INTO tag (event_id, \"name\", value) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;";
|
let q = "INSERT INTO tag (event_id, \"name\", value) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;";
|
||||||
sqlx::query(q).bind(&event_id).bind(&tagname).bind(tagval.as_bytes()).execute(&mut update_tx).await?;
|
sqlx::query(q)
|
||||||
|
.bind(&event_id)
|
||||||
|
.bind(tagname)
|
||||||
|
.bind(tagval.as_bytes())
|
||||||
|
.execute(&mut update_tx)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
update_tx.commit().await?;
|
update_tx.commit().await?;
|
||||||
@@ -217,3 +238,21 @@ CREATE INDEX tag_value_hex_idx ON tag USING btree (value_hex);
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod m003 {
|
||||||
|
use crate::repo::postgres_migration::{Migration, SimpleSqlMigration};
|
||||||
|
|
||||||
|
pub const VERSION: i64 = 3;
|
||||||
|
|
||||||
|
pub fn migration() -> impl Migration {
|
||||||
|
SimpleSqlMigration {
|
||||||
|
serial_number: VERSION,
|
||||||
|
sql: vec![
|
||||||
|
r#"
|
||||||
|
-- Add unique constraint on tag
|
||||||
|
ALTER TABLE tag ADD CONSTRAINT unique_constraint_name UNIQUE (event_id, "name", value);
|
||||||
|
"#,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -6,7 +6,7 @@ use crate::event::{single_char_tagname, Event};
|
|||||||
use crate::hexrange::hex_range;
|
use crate::hexrange::hex_range;
|
||||||
use crate::hexrange::HexSearch;
|
use crate::hexrange::HexSearch;
|
||||||
use crate::repo::sqlite_migration::{STARTUP_SQL,upgrade_db};
|
use crate::repo::sqlite_migration::{STARTUP_SQL,upgrade_db};
|
||||||
use crate::utils::{is_hex, is_lower_hex};
|
use crate::utils::{is_hex};
|
||||||
use crate::nip05::{Nip05Name, VerificationRecord};
|
use crate::nip05::{Nip05Name, VerificationRecord};
|
||||||
use crate::subscription::{ReqFilter, Subscription};
|
use crate::subscription::{ReqFilter, Subscription};
|
||||||
use crate::server::NostrMetrics;
|
use crate::server::NostrMetrics;
|
||||||
@@ -123,16 +123,9 @@ impl SqliteRepo {
|
|||||||
}
|
}
|
||||||
// check for parameterized replaceable events that would be hidden; don't insert these either.
|
// check for parameterized replaceable events that would be hidden; don't insert these either.
|
||||||
if let Some(d_tag) = e.distinct_param() {
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
let repl_count;
|
let repl_count = tx.query_row(
|
||||||
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND e.kind=? AND t.name='d' AND t.value=? AND e.created_at >= ? LIMIT 1;",
|
||||||
repl_count = tx.query_row(
|
params![pubkey_blob, e.kind, d_tag, e.created_at],|row| row.get::<usize, usize>(0));
|
||||||
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND e.kind=? AND t.name='d' AND t.value_hex=? AND e.created_at >= ? LIMIT 1;",
|
|
||||||
params![pubkey_blob, e.kind, hex::decode(d_tag).ok(), e.created_at],|row| row.get::<usize, usize>(0));
|
|
||||||
} else {
|
|
||||||
repl_count = tx.query_row(
|
|
||||||
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND e.kind=? AND t.name='d' AND t.value=? AND e.created_at >= ? LIMIT 1;",
|
|
||||||
params![pubkey_blob, e.kind, d_tag, e.created_at],|row| row.get::<usize, usize>(0));
|
|
||||||
}
|
|
||||||
// if any rows were returned, then some newer event with
|
// if any rows were returned, then some newer event with
|
||||||
// the same author/kind/tag value exist, and we can ignore
|
// the same author/kind/tag value exist, and we can ignore
|
||||||
// this event.
|
// this event.
|
||||||
@@ -163,18 +156,10 @@ impl SqliteRepo {
|
|||||||
let tagchar_opt = single_char_tagname(tagname);
|
let tagchar_opt = single_char_tagname(tagname);
|
||||||
match &tagchar_opt {
|
match &tagchar_opt {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
// if tagvalue is lowercase hex;
|
tx.execute(
|
||||||
if is_lower_hex(tagval) && (tagval.len() % 2 == 0) {
|
"INSERT OR IGNORE INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
tx.execute(
|
params![ev_id, &tagname, &tagval, e.kind, e.created_at],
|
||||||
"INSERT OR IGNORE INTO tag (event_id, name, value_hex) VALUES (?1, ?2, ?3)",
|
)?;
|
||||||
params![ev_id, &tagname, hex::decode(tagval).ok()],
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
tx.execute(
|
|
||||||
"INSERT OR IGNORE INTO tag (event_id, name, value) VALUES (?1, ?2, ?3)",
|
|
||||||
params![ev_id, &tagname, &tagval],
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
@@ -201,16 +186,9 @@ impl SqliteRepo {
|
|||||||
}
|
}
|
||||||
// if this event is parameterized replaceable, remove other events.
|
// if this event is parameterized replaceable, remove other events.
|
||||||
if let Some(d_tag) = e.distinct_param() {
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
let update_count;
|
let update_count = tx.execute(
|
||||||
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
"DELETE FROM event WHERE kind=? AND author=? AND id IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=? AND e.author=? AND t.name='d' AND t.value=? ORDER BY t.created_at DESC LIMIT -1 OFFSET 1);",
|
||||||
update_count = tx.execute(
|
params![e.kind, pubkey_blob, e.kind, pubkey_blob, d_tag])?;
|
||||||
"DELETE FROM event WHERE kind=? AND author=? AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=? AND e.author=? AND t.name='d' AND t.value_hex=? ORDER BY created_at DESC LIMIT 1);",
|
|
||||||
params![e.kind, pubkey_blob, e.kind, pubkey_blob, hex::decode(d_tag).ok()])?;
|
|
||||||
} else {
|
|
||||||
update_count = tx.execute(
|
|
||||||
"DELETE FROM event WHERE kind=? AND author=? AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=? AND e.author=? AND t.name='d' AND t.value=? ORDER BY created_at DESC LIMIT 1);",
|
|
||||||
params![e.kind, pubkey_blob, e.kind, pubkey_blob, d_tag])?;
|
|
||||||
}
|
|
||||||
if update_count > 0 {
|
if update_count > 0 {
|
||||||
info!(
|
info!(
|
||||||
"removed {} older parameterized replaceable kind {} events for author: {:?}",
|
"removed {} older parameterized replaceable kind {} events for author: {:?}",
|
||||||
@@ -245,8 +223,8 @@ impl SqliteRepo {
|
|||||||
// check if a deletion has already been recorded for this event.
|
// check if a deletion has already been recorded for this event.
|
||||||
// Only relevant for non-deletion events
|
// Only relevant for non-deletion events
|
||||||
let del_count = tx.query_row(
|
let del_count = tx.query_row(
|
||||||
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND t.name='e' AND e.kind=5 AND t.value_hex=? LIMIT 1;",
|
"SELECT e.id FROM event e WHERE e.author=? AND e.id IN (SELECT t.event_id FROM tag t WHERE t.name='e' AND t.kind=5 AND t.value=?) LIMIT 1;",
|
||||||
params![pubkey_blob, id_blob], |row| row.get::<usize, usize>(0));
|
params![pubkey_blob, e.id], |row| row.get::<usize, usize>(0));
|
||||||
// check if a the query returned a result, meaning we should
|
// check if a the query returned a result, meaning we should
|
||||||
// hid the current event
|
// hid the current event
|
||||||
if del_count.ok().is_some() {
|
if del_count.ok().is_some() {
|
||||||
@@ -364,9 +342,8 @@ impl NostrRepo for SqliteRepo {
|
|||||||
for filter in sub.filters.iter() {
|
for filter in sub.filters.iter() {
|
||||||
let filter_start = Instant::now();
|
let filter_start = Instant::now();
|
||||||
filter_count += 1;
|
filter_count += 1;
|
||||||
let (q, p, idx) = query_from_filter(&filter);
|
let sql_gen_elapsed = filter_start.elapsed();
|
||||||
let sql_gen_elapsed = start.elapsed();
|
let (q, p, idx) = query_from_filter(filter);
|
||||||
|
|
||||||
if sql_gen_elapsed > Duration::from_millis(10) {
|
if sql_gen_elapsed > Duration::from_millis(10) {
|
||||||
debug!("SQL (slow) generated in {:?}", filter_start.elapsed());
|
debug!("SQL (slow) generated in {:?}", filter_start.elapsed());
|
||||||
}
|
}
|
||||||
@@ -720,8 +697,8 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
|
|||||||
|
|
||||||
// check if the index needs to be overriden
|
// check if the index needs to be overriden
|
||||||
let idx_name = override_index(f);
|
let idx_name = override_index(f);
|
||||||
let idx_stmt = idx_name.as_ref().map_or_else(|| "".to_owned(), |i| format!("INDEXED BY {}",i));
|
let idx_stmt = idx_name.as_ref().map_or_else(|| "".to_owned(), |i| format!("INDEXED BY {i}"));
|
||||||
let mut query = format!("SELECT e.content FROM event e {}", idx_stmt);
|
let mut query = format!("SELECT e.content FROM event e {idx_stmt}");
|
||||||
// query parameters for SQLite
|
// query parameters for SQLite
|
||||||
let mut params: Vec<Box<dyn ToSql>> = vec![];
|
let mut params: Vec<Box<dyn ToSql>> = vec![];
|
||||||
|
|
||||||
@@ -804,61 +781,44 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
|
|||||||
if let Some(map) = &f.tags {
|
if let Some(map) = &f.tags {
|
||||||
for (key, val) in map.iter() {
|
for (key, val) in map.iter() {
|
||||||
let mut str_vals: Vec<Box<dyn ToSql>> = vec![];
|
let mut str_vals: Vec<Box<dyn ToSql>> = vec![];
|
||||||
let mut blob_vals: Vec<Box<dyn ToSql>> = vec![];
|
|
||||||
for v in val {
|
for v in val {
|
||||||
if (v.len() % 2 == 0) && is_lower_hex(v) {
|
str_vals.push(Box::new(v.clone()));
|
||||||
if let Ok(h) = hex::decode(v) {
|
|
||||||
blob_vals.push(Box::new(h));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
str_vals.push(Box::new(v.clone()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// do not mix value and value_hex; this is a temporary special case.
|
// create clauses with "?" params for each tag value being searched
|
||||||
if str_vals.len() == 0 {
|
let str_clause = format!("AND value IN ({})", repeat_vars(str_vals.len()));
|
||||||
// create clauses with "?" params for each tag value being searched
|
// find evidence of the target tag name/value existing for this event.
|
||||||
let blob_clause = format!("value_hex IN ({})", repeat_vars(blob_vals.len()));
|
// Query for Kind/Since/Until additionally, to reduce the number of tags that come back.
|
||||||
// find evidence of the target tag name/value existing for this event.
|
let kind_clause;
|
||||||
let tag_clause = format!(
|
let since_clause;
|
||||||
"e.id IN (SELECT e.id FROM event e LEFT JOIN tag t on e.id=t.event_id WHERE hidden!=TRUE and (name=? AND {}))",
|
let until_clause;
|
||||||
blob_clause
|
if let Some(ks) = &f.kinds {
|
||||||
);
|
// kind is number, no escaping needed
|
||||||
// add the tag name as the first parameter
|
let str_kinds: Vec<String> = ks.iter().map(std::string::ToString::to_string).collect();
|
||||||
params.push(Box::new(key.to_string()));
|
kind_clause = format!("AND kind IN ({})", str_kinds.join(", "));
|
||||||
// add all tag values that are blobs as params
|
|
||||||
params.append(&mut blob_vals);
|
|
||||||
filter_components.push(tag_clause);
|
|
||||||
} else if blob_vals.len() == 0 {
|
|
||||||
// create clauses with "?" params for each tag value being searched
|
|
||||||
let str_clause = format!("value IN ({})", repeat_vars(str_vals.len()));
|
|
||||||
// find evidence of the target tag name/value existing for this event.
|
|
||||||
let tag_clause = format!(
|
|
||||||
"e.id IN (SELECT e.id FROM event e LEFT JOIN tag t on e.id=t.event_id WHERE hidden!=TRUE and (name=? AND {}))",
|
|
||||||
str_clause
|
|
||||||
);
|
|
||||||
// add the tag name as the first parameter
|
|
||||||
params.push(Box::new(key.to_string()));
|
|
||||||
// add all tag values that are blobs as params
|
|
||||||
params.append(&mut str_vals);
|
|
||||||
filter_components.push(tag_clause);
|
|
||||||
} else {
|
} else {
|
||||||
debug!("mixed string/blob query");
|
kind_clause = format!("");
|
||||||
// create clauses with "?" params for each tag value being searched
|
};
|
||||||
let str_clause = format!("value IN ({})", repeat_vars(str_vals.len()));
|
if f.since.is_some() {
|
||||||
let blob_clause = format!("value_hex IN ({})", repeat_vars(blob_vals.len()));
|
since_clause = format!("AND created_at > {}", f.since.unwrap());
|
||||||
// find evidence of the target tag name/value existing for this event.
|
} else {
|
||||||
let tag_clause = format!(
|
since_clause = format!("");
|
||||||
"e.id IN (SELECT e.id FROM event e LEFT JOIN tag t on e.id=t.event_id WHERE hidden!=TRUE and (name=? AND ({} OR {})))",
|
};
|
||||||
str_clause, blob_clause
|
// Query for timestamp
|
||||||
);
|
if f.until.is_some() {
|
||||||
// add the tag name as the first parameter
|
until_clause = format!("AND created_at < {}", f.until.unwrap());
|
||||||
params.push(Box::new(key.to_string()));
|
} else {
|
||||||
// add all tag values that are plain strings as params
|
until_clause = format!("");
|
||||||
params.append(&mut str_vals);
|
};
|
||||||
// add all tag values that are blobs as params
|
|
||||||
params.append(&mut blob_vals);
|
let tag_clause = format!(
|
||||||
filter_components.push(tag_clause);
|
"e.id IN (SELECT t.event_id FROM tag t WHERE (name=? {str_clause} {kind_clause} {since_clause} {until_clause}))"
|
||||||
}
|
);
|
||||||
|
|
||||||
|
// add the tag name as the first parameter
|
||||||
|
params.push(Box::new(key.to_string()));
|
||||||
|
// add all tag values that are blobs as params
|
||||||
|
params.append(&mut str_vals);
|
||||||
|
filter_components.push(tag_clause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Query for timestamp
|
// Query for timestamp
|
||||||
@@ -881,7 +841,7 @@ fn query_from_filter(f: &ReqFilter) -> (String, Vec<Box<dyn ToSql>>, Option<Stri
|
|||||||
// Apply per-filter limit to this subquery.
|
// Apply per-filter limit to this subquery.
|
||||||
// The use of a LIMIT implies a DESC order, to capture only the most recent events.
|
// The use of a LIMIT implies a DESC order, to capture only the most recent events.
|
||||||
if let Some(lim) = f.limit {
|
if let Some(lim) = f.limit {
|
||||||
let _ = write!(query, " ORDER BY e.created_at DESC LIMIT {}", lim);
|
let _ = write!(query, " ORDER BY e.created_at DESC LIMIT {lim}");
|
||||||
} else {
|
} else {
|
||||||
query.push_str(" ORDER BY e.created_at ASC");
|
query.push_str(" ORDER BY e.created_at ASC");
|
||||||
}
|
}
|
||||||
@@ -908,7 +868,7 @@ fn _query_from_sub(sub: &Subscription) -> (String, Vec<Box<dyn ToSql>>, Vec<Stri
|
|||||||
// encapsulate subqueries into select statements
|
// encapsulate subqueries into select statements
|
||||||
let subqueries_selects: Vec<String> = subqueries
|
let subqueries_selects: Vec<String> = subqueries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!("SELECT distinct content, created_at FROM ({})", s))
|
.map(|s| format!("SELECT distinct content, created_at FROM ({s})"))
|
||||||
.collect();
|
.collect();
|
||||||
let query: String = subqueries_selects.join(" UNION ");
|
let query: String = subqueries_selects.join(" UNION ");
|
||||||
(query, params,indexes)
|
(query, params,indexes)
|
||||||
|
@@ -10,17 +10,20 @@ use rusqlite::Connection;
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
/// Startup DB Pragmas
|
/// Startup DB Pragmas
|
||||||
pub const STARTUP_SQL: &str = r##"
|
pub const STARTUP_SQL: &str = r##"
|
||||||
PRAGMA main.synchronous = NORMAL;
|
PRAGMA main.synchronous = NORMAL;
|
||||||
PRAGMA foreign_keys = ON;
|
PRAGMA foreign_keys = ON;
|
||||||
PRAGMA journal_size_limit = 32768;
|
PRAGMA journal_size_limit = 32768;
|
||||||
|
PRAGMA temp_store = 2; -- use memory, not temp files
|
||||||
|
PRAGMA main.cache_size = 20000; -- 80MB max cache size per conn
|
||||||
pragma mmap_size = 17179869184; -- cap mmap at 16GB
|
pragma mmap_size = 17179869184; -- cap mmap at 16GB
|
||||||
"##;
|
"##;
|
||||||
|
|
||||||
/// Latest database version
|
/// Latest database version
|
||||||
pub const DB_VERSION: usize = 15;
|
pub const DB_VERSION: usize = 16;
|
||||||
|
|
||||||
/// Schema definition
|
/// Schema definition
|
||||||
const INIT_SQL: &str = formatcp!(
|
const INIT_SQL: &str = formatcp!(
|
||||||
@@ -63,18 +66,21 @@ CREATE INDEX IF NOT EXISTS author_kind_index ON event(author,kind);
|
|||||||
-- Tag values are stored as either a BLOB (if they come in as a
|
-- Tag values are stored as either a BLOB (if they come in as a
|
||||||
-- hex-string), or TEXT otherwise.
|
-- hex-string), or TEXT otherwise.
|
||||||
-- This means that searches need to select the appropriate column.
|
-- This means that searches need to select the appropriate column.
|
||||||
|
-- We duplicate the kind/created_at to make indexes much more efficient.
|
||||||
CREATE TABLE IF NOT EXISTS tag (
|
CREATE TABLE IF NOT EXISTS tag (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
event_id INTEGER NOT NULL, -- an event ID that contains a tag.
|
event_id INTEGER NOT NULL, -- an event ID that contains a tag.
|
||||||
name TEXT, -- the tag name ("p", "e", whatever)
|
name TEXT, -- the tag name ("p", "e", whatever)
|
||||||
value TEXT, -- the tag value, if not hex.
|
value TEXT, -- the tag value, if not hex.
|
||||||
value_hex BLOB, -- the tag value, if it can be interpreted as a lowercase hex string.
|
value_hex BLOB, -- the tag value, if it can be interpreted as a lowercase hex string.
|
||||||
|
created_at INTEGER NOT NULL, -- when the event was authored
|
||||||
|
kind INTEGER NOT NULL, -- event kind
|
||||||
FOREIGN KEY(event_id) REFERENCES event(id) ON UPDATE CASCADE ON DELETE CASCADE
|
FOREIGN KEY(event_id) REFERENCES event(id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS tag_val_index ON tag(value);
|
CREATE INDEX IF NOT EXISTS tag_val_index ON tag(value);
|
||||||
CREATE INDEX IF NOT EXISTS tag_val_hex_index ON tag(value_hex);
|
CREATE INDEX IF NOT EXISTS tag_composite_index ON tag(event_id,name,value);
|
||||||
CREATE INDEX IF NOT EXISTS tag_composite_index ON tag(event_id,name,value_hex,value);
|
CREATE INDEX IF NOT EXISTS tag_name_eid_index ON tag(name,event_id,value);
|
||||||
CREATE INDEX IF NOT EXISTS tag_name_eid_index ON tag(name,event_id,value_hex);
|
CREATE INDEX IF NOT EXISTS tag_covering_index ON tag(name,kind,value,created_at,event_id);
|
||||||
|
|
||||||
-- NIP-05 User Validation
|
-- NIP-05 User Validation
|
||||||
CREATE TABLE IF NOT EXISTS user_verification (
|
CREATE TABLE IF NOT EXISTS user_verification (
|
||||||
@@ -199,6 +205,9 @@ pub fn upgrade_db(conn: &mut PooledConnection) -> Result<usize> {
|
|||||||
if curr_version == 14 {
|
if curr_version == 14 {
|
||||||
curr_version = mig_14_to_15(conn)?;
|
curr_version = mig_14_to_15(conn)?;
|
||||||
}
|
}
|
||||||
|
if curr_version == 15 {
|
||||||
|
curr_version = mig_15_to_16(conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
if curr_version == DB_VERSION {
|
if curr_version == DB_VERSION {
|
||||||
info!(
|
info!(
|
||||||
@@ -209,13 +218,12 @@ pub fn upgrade_db(conn: &mut PooledConnection) -> Result<usize> {
|
|||||||
}
|
}
|
||||||
// Database is current, all is good
|
// Database is current, all is good
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
debug!("Database version was already current (v{})", DB_VERSION);
|
debug!("Database version was already current (v{DB_VERSION})");
|
||||||
}
|
}
|
||||||
// Database is newer than what this code understands, abort
|
// Database is newer than what this code understands, abort
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
panic!(
|
panic!(
|
||||||
"Database version is newer than supported by this executable (v{} > v{})",
|
"Database version is newer than supported by this executable (v{curr_version} > v{DB_VERSION})",
|
||||||
curr_version, DB_VERSION
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,3 +659,73 @@ PRAGMA user_version = 15;
|
|||||||
}
|
}
|
||||||
Ok(15)
|
Ok(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mig_15_to_16(conn: &mut PooledConnection) -> Result<usize> {
|
||||||
|
let count = db_event_count(conn)?;
|
||||||
|
info!("database schema needs update from 15->16 (this make take a few minutes)");
|
||||||
|
let upgrade_sql = r##"
|
||||||
|
DROP TABLE tag;
|
||||||
|
CREATE TABLE tag (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
event_id INTEGER NOT NULL, -- an event ID that contains a tag.
|
||||||
|
name TEXT, -- the tag name ("p", "e", whatever)
|
||||||
|
value TEXT, -- the tag value, if not hex.
|
||||||
|
created_at INTEGER NOT NULL, -- when the event was authored
|
||||||
|
kind INTEGER NOT NULL, -- event kind
|
||||||
|
FOREIGN KEY(event_id) REFERENCES event(id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS tag_val_index ON tag(value);
|
||||||
|
CREATE INDEX IF NOT EXISTS tag_composite_index ON tag(event_id,name,value);
|
||||||
|
CREATE INDEX IF NOT EXISTS tag_name_eid_index ON tag(name,event_id,value);
|
||||||
|
CREATE INDEX IF NOT EXISTS tag_covering_index ON tag(name,kind,value,created_at,event_id);
|
||||||
|
"##;
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
let tx = conn.transaction()?;
|
||||||
|
|
||||||
|
let bar = ProgressBar::new(count.try_into().unwrap())
|
||||||
|
.with_message("rebuilding tags table");
|
||||||
|
bar.set_style(
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
"[{elapsed_precise}] {bar:40.white/blue} {pos:>7}/{len:7} [{percent}%] {msg}",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
{
|
||||||
|
tx.execute_batch(upgrade_sql)?;
|
||||||
|
let mut stmt = tx.prepare("select id, kind, created_at, content from event order by id;")?;
|
||||||
|
let mut tag_rows = stmt.query([])?;
|
||||||
|
let mut count = 0;
|
||||||
|
while let Some(row) = tag_rows.next()? {
|
||||||
|
count += 1;
|
||||||
|
if count%10==0 {
|
||||||
|
bar.inc(10);
|
||||||
|
}
|
||||||
|
let event_id: u64 = row.get(0)?;
|
||||||
|
let kind: u64 = row.get(1)?;
|
||||||
|
let created_at: u64 = row.get(2)?;
|
||||||
|
let event_json: String = row.get(3)?;
|
||||||
|
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 tagnamechar_opt = single_char_tagname(tagname);
|
||||||
|
if tagnamechar_opt.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// safe because len was > 1
|
||||||
|
let tagval = t.get(1).unwrap();
|
||||||
|
// otherwise, insert as text
|
||||||
|
tx.execute(
|
||||||
|
"INSERT INTO tag (event_id, name, value, kind, created_at) VALUES (?1, ?2, ?3, ?4, ?5);",
|
||||||
|
params![event_id, tagname, &tagval, kind, created_at],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx.execute("PRAGMA user_version = 16;", [])?;
|
||||||
|
}
|
||||||
|
bar.finish();
|
||||||
|
tx.commit()?;
|
||||||
|
info!("database schema upgraded v15 -> v16 in {:?}", start.elapsed());
|
||||||
|
Ok(16)
|
||||||
|
}
|
||||||
|
@@ -50,6 +50,7 @@ use tungstenite::protocol::Message;
|
|||||||
use tungstenite::protocol::WebSocketConfig;
|
use tungstenite::protocol::WebSocketConfig;
|
||||||
|
|
||||||
/// Handle arbitrary HTTP requests, including for `WebSocket` upgrades.
|
/// Handle arbitrary HTTP requests, including for `WebSocket` upgrades.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_web_request(
|
async fn handle_web_request(
|
||||||
mut request: Request<Body>,
|
mut request: Request<Body>,
|
||||||
repo: Arc<dyn NostrRepo>,
|
repo: Arc<dyn NostrRepo>,
|
||||||
@@ -127,9 +128,8 @@ async fn handle_web_request(
|
|||||||
// todo: trace, don't print...
|
// todo: trace, don't print...
|
||||||
Err(e) => println!(
|
Err(e) => println!(
|
||||||
"error when trying to upgrade connection \
|
"error when trying to upgrade connection \
|
||||||
from address {} to websocket connection. \
|
from address {remote_addr} to websocket connection. \
|
||||||
Error is: {}",
|
Error is: {e}",
|
||||||
remote_addr, e
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -139,7 +139,7 @@ async fn handle_web_request(
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
warn!("websocket response failed");
|
warn!("websocket response failed");
|
||||||
let mut res =
|
let mut res =
|
||||||
Response::new(Body::from(format!("Failed to create websocket: {}", error)));
|
Response::new(Body::from(format!("Failed to create websocket: {error}")));
|
||||||
*res.status_mut() = StatusCode::BAD_REQUEST;
|
*res.status_mut() = StatusCode::BAD_REQUEST;
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ pub fn start_server(settings: &Settings, shutdown_rx: MpscReceiver<()>) -> Resul
|
|||||||
// give each thread a unique numeric name
|
// give each thread a unique numeric name
|
||||||
static ATOMIC_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
static ATOMIC_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
||||||
let id = ATOMIC_ID.fetch_add(1,Ordering::SeqCst);
|
let id = ATOMIC_ID.fetch_add(1,Ordering::SeqCst);
|
||||||
format!("tokio-ws-{}", id)
|
format!("tokio-ws-{id}")
|
||||||
})
|
})
|
||||||
// limit concurrent SQLite blocking threads
|
// limit concurrent SQLite blocking threads
|
||||||
.max_blocking_threads(settings.limits.max_blocking_threads)
|
.max_blocking_threads(settings.limits.max_blocking_threads)
|
||||||
@@ -478,7 +478,7 @@ pub fn start_server(settings: &Settings, shutdown_rx: MpscReceiver<()>) -> Resul
|
|||||||
.with_graceful_shutdown(ctrl_c_or_signal(webserver_shutdown_listen));
|
.with_graceful_shutdown(ctrl_c_or_signal(webserver_shutdown_listen));
|
||||||
// run hyper in this thread. This is why the thread does not return.
|
// run hyper in this thread. This is why the thread does not return.
|
||||||
if let Err(e) = server.await {
|
if let Err(e) = server.await {
|
||||||
eprintln!("server error: {}", e);
|
eprintln!("server error: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -541,6 +541,7 @@ struct ClientInfo {
|
|||||||
|
|
||||||
/// Handle new client connections. This runs through an event loop
|
/// Handle new client connections. This runs through an event loop
|
||||||
/// for all client communication.
|
/// for all client communication.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn nostr_server(
|
async fn nostr_server(
|
||||||
repo: Arc<dyn NostrRepo>,
|
repo: Arc<dyn NostrRepo>,
|
||||||
client_info: ClientInfo,
|
client_info: ClientInfo,
|
||||||
@@ -600,12 +601,14 @@ async fn nostr_server(
|
|||||||
// and how many it received from queries.
|
// and how many it received from queries.
|
||||||
let mut client_published_event_count: usize = 0;
|
let mut client_published_event_count: usize = 0;
|
||||||
let mut client_received_event_count: usize = 0;
|
let mut client_received_event_count: usize = 0;
|
||||||
debug!("new client connection (cid: {}, ip: {:?})", cid, conn.ip());
|
|
||||||
let origin = client_info.origin.unwrap_or_else(|| "<unspecified>".into());
|
let unspec = "<unspecified>".to_string();
|
||||||
|
info!("new client connection (cid: {}, ip: {:?})", cid, conn.ip());
|
||||||
|
let origin = client_info.origin.as_ref().unwrap_or_else(|| &unspec);
|
||||||
let user_agent = client_info
|
let user_agent = client_info
|
||||||
.user_agent
|
.user_agent.as_ref()
|
||||||
.unwrap_or_else(|| "<unspecified>".into());
|
.unwrap_or_else(|| &unspec);
|
||||||
debug!(
|
info!(
|
||||||
"cid: {}, origin: {:?}, user-agent: {:?}",
|
"cid: {}, origin: {:?}, user-agent: {:?}",
|
||||||
cid, origin, user_agent
|
cid, origin, user_agent
|
||||||
);
|
);
|
||||||
@@ -639,7 +642,7 @@ async fn nostr_server(
|
|||||||
// database informed us of a query result we asked for
|
// database informed us of a query result we asked for
|
||||||
let subesc = query_result.sub_id.replace('"', "");
|
let subesc = query_result.sub_id.replace('"', "");
|
||||||
if query_result.event == "EOSE" {
|
if query_result.event == "EOSE" {
|
||||||
let send_str = format!("[\"EOSE\",\"{}\"]", subesc);
|
let send_str = format!("[\"EOSE\",\"{subesc}\"]");
|
||||||
ws_stream.send(Message::Text(send_str)).await.ok();
|
ws_stream.send(Message::Text(send_str)).await.ok();
|
||||||
} else {
|
} else {
|
||||||
client_received_event_count += 1;
|
client_received_event_count += 1;
|
||||||
@@ -666,7 +669,7 @@ async fn nostr_server(
|
|||||||
// create an event response and send it
|
// create an event response and send it
|
||||||
let subesc = s.replace('"', "");
|
let subesc = s.replace('"', "");
|
||||||
metrics.sent_events.with_label_values(&["realtime"]).inc();
|
metrics.sent_events.with_label_values(&["realtime"]).inc();
|
||||||
ws_stream.send(Message::Text(format!("[\"EVENT\",\"{}\",{}]", subesc, event_str))).await.ok();
|
ws_stream.send(Message::Text(format!("[\"EVENT\",\"{subesc}\",{event_str}]"))).await.ok();
|
||||||
} else {
|
} else {
|
||||||
warn!("could not serialize event: {:?}", global_event.get_event_id_prefix());
|
warn!("could not serialize event: {:?}", global_event.get_event_id_prefix());
|
||||||
}
|
}
|
||||||
@@ -692,7 +695,7 @@ async fn nostr_server(
|
|||||||
},
|
},
|
||||||
Some(Err(WsError::Capacity(MessageTooLong{size, max_size}))) => {
|
Some(Err(WsError::Capacity(MessageTooLong{size, max_size}))) => {
|
||||||
ws_stream.send(
|
ws_stream.send(
|
||||||
make_notice_message(&Notice::message(format!("message too large ({} > {})",size, max_size)))).await.ok();
|
make_notice_message(&Notice::message(format!("message too large ({size} > {max_size})")))).await.ok();
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
None |
|
None |
|
||||||
@@ -735,13 +738,13 @@ async fn nostr_server(
|
|||||||
// check if the event is too far in the future.
|
// check if the event is too far in the future.
|
||||||
if e.is_valid_timestamp(settings.options.reject_future_seconds) {
|
if e.is_valid_timestamp(settings.options.reject_future_seconds) {
|
||||||
// Write this to the database.
|
// Write this to the database.
|
||||||
let submit_event = SubmittedEvent { event: e.clone(), notice_tx: notice_tx.clone() };
|
let submit_event = SubmittedEvent { event: e.clone(), notice_tx: notice_tx.clone(), source_ip: conn.ip().to_string(), origin: client_info.origin.clone(), user_agent: client_info.user_agent.clone()};
|
||||||
event_tx.send(submit_event).await.ok();
|
event_tx.send(submit_event).await.ok();
|
||||||
client_published_event_count += 1;
|
client_published_event_count += 1;
|
||||||
} else {
|
} else {
|
||||||
info!("client: {} sent a far future-dated event", cid);
|
info!("client: {} sent a far future-dated event", cid);
|
||||||
if let Some(fut_sec) = settings.options.reject_future_seconds {
|
if let Some(fut_sec) = settings.options.reject_future_seconds {
|
||||||
let msg = format!("The event created_at field is out of the acceptable range (+{}sec) for this relay.",fut_sec);
|
let msg = format!("The event created_at field is out of the acceptable range (+{fut_sec}sec) for this relay.");
|
||||||
let notice = Notice::invalid(e.id, &msg);
|
let notice = Notice::invalid(e.id, &msg);
|
||||||
ws_stream.send(make_notice_message(¬ice)).await.ok();
|
ws_stream.send(make_notice_message(¬ice)).await.ok();
|
||||||
}
|
}
|
||||||
@@ -749,7 +752,7 @@ async fn nostr_server(
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("client sent an invalid event (cid: {})", cid);
|
info!("client sent an invalid event (cid: {})", cid);
|
||||||
ws_stream.send(make_notice_message(&Notice::invalid(evid, &format!("{}", e)))).await.ok();
|
ws_stream.send(make_notice_message(&Notice::invalid(evid, &format!("{e}")))).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -782,7 +785,7 @@ async fn nostr_server(
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("Subscription error: {} (cid: {}, sub: {:?})", e, cid, s.id);
|
info!("Subscription error: {} (cid: {}, sub: {:?})", e, cid, s.id);
|
||||||
ws_stream.send(make_notice_message(&Notice::message(format!("Subscription error: {}", e)))).await.ok();
|
ws_stream.send(make_notice_message(&Notice::message(format!("Subscription error: {e}")))).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -70,7 +70,7 @@ impl Serialize for ReqFilter {
|
|||||||
if let Some(tags) = &self.tags {
|
if let Some(tags) = &self.tags {
|
||||||
for (k,v) in tags {
|
for (k,v) in tags {
|
||||||
let vals:Vec<&String> = v.iter().collect();
|
let vals:Vec<&String> = v.iter().collect();
|
||||||
map.serialize_entry(&format!("#{}",k), &vals)?;
|
map.serialize_entry(&format!("#{k}"), &vals)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
map.end()
|
map.end()
|
||||||
|
Reference in New Issue
Block a user