commit a854579e00d43d8741f3afeba80946811ac06c51
parent 224f8f709ce49d84ef0c078403cd81822cf1b0e6
Author: triesap <tyson@radroots.org>
Date: Mon, 3 Aug 2026 18:08:44 +0000
radrootsd: migrate to final protocol and transport crates
- adopt the versioned V5 daemon protocol DTO surface
- keep identity custody and relay client adaptation daemon-owned
- align lower-layer crate versions and the Rust toolchain
- cover final transport, RPC, storage, and source boundaries
Diffstat:
27 files changed, 1582 insertions(+), 1456 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -197,6 +197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -313,12 +319,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
-name = "cfg_aliases"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
-
-[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -441,6 +441,12 @@ dependencies = [
]
[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -530,6 +536,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -547,6 +565,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -602,6 +630,23 @@ dependencies = [
]
[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -644,6 +689,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
+name = "ff"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -695,7 +750,6 @@ checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
- "futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@@ -794,6 +848,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
+ "zeroize",
]
[[package]]
@@ -816,11 +871,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
- "js-sys",
"libc",
"r-efi 5.3.0",
"wasip2",
- "wasm-bindgen",
]
[[package]]
@@ -849,6 +902,17 @@ dependencies = [
]
[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1010,24 +1074,6 @@ dependencies = [
"pin-utils",
"smallvec",
"tokio",
- "want",
-]
-
-[[package]]
-name = "hyper-rustls"
-version = "0.27.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
-dependencies = [
- "http",
- "hyper",
- "hyper-util",
- "rustls",
- "rustls-pki-types",
- "tokio",
- "tokio-rustls",
- "tower-service",
- "webpki-roots 1.0.6",
]
[[package]]
@@ -1036,21 +1082,13 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "base64 0.22.1",
"bytes",
- "futures-channel",
- "futures-util",
"http",
"http-body",
"hyper",
- "ipnet",
- "libc",
- "percent-encoding",
"pin-project-lite",
- "socket2",
"tokio",
"tower-service",
- "tracing",
]
[[package]]
@@ -1166,6 +1204,16 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
@@ -1220,22 +1268,6 @@ dependencies = [
]
[[package]]
-name = "ipnet"
-version = "2.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
-
-[[package]]
-name = "iri-string"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
-dependencies = [
- "memchr",
- "serde",
-]
-
-[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1351,6 +1383,16 @@ dependencies = [
]
[[package]]
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "elliptic-curve",
+]
+
+[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1413,12 +1455,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
[[package]]
-name = "lru-slab"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
-
-[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1495,6 +1531,7 @@ dependencies = [
"serde_json",
"unicode-normalization",
"url",
+ "url-fork",
]
[[package]]
@@ -1800,61 +1837,6 @@ dependencies = [
]
[[package]]
-name = "quinn"
-version = "0.11.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
-dependencies = [
- "bytes",
- "cfg_aliases",
- "pin-project-lite",
- "quinn-proto",
- "quinn-udp",
- "rustc-hash",
- "rustls",
- "socket2",
- "thiserror 2.0.18",
- "tokio",
- "tracing",
- "web-time",
-]
-
-[[package]]
-name = "quinn-proto"
-version = "0.11.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
-dependencies = [
- "bytes",
- "getrandom 0.3.4",
- "lru-slab",
- "rand 0.9.2",
- "ring",
- "rustc-hash",
- "rustls",
- "rustls-pki-types",
- "slab",
- "thiserror 2.0.18",
- "tinyvec",
- "tracing",
- "web-time",
-]
-
-[[package]]
-name = "quinn-udp"
-version = "0.5.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
-dependencies = [
- "cfg_aliases",
- "libc",
- "once_cell",
- "socket2",
- "tracing",
- "windows-sys 0.60.2",
-]
-
-[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1877,7 +1859,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "radroots_blossom"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"mediatype",
"serde",
@@ -1888,22 +1870,22 @@ dependencies = [
[[package]]
name = "radroots_core"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"rust_decimal",
- "rust_decimal_macros",
"serde",
]
[[package]]
name = "radroots_event"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"hex",
"jiff-tzdb",
"radroots_blossom",
"radroots_core",
- "secp256k1",
+ "radroots_identity",
+ "radroots_protocol",
"serde",
"serde_json",
"sha2",
@@ -1913,34 +1895,30 @@ dependencies = [
[[package]]
name = "radroots_event_codec"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
- "nostr",
"radroots_blossom",
"radroots_core",
"radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
+ "secp256k1",
"serde",
"serde_json",
]
[[package]]
name = "radroots_identity"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
- "nostr",
- "radroots_protected_store",
- "radroots_runtime",
- "radroots_runtime_paths",
- "radroots_secret_vault",
+ "k256",
"serde",
- "serde_json",
- "thiserror 1.0.69",
- "tracing",
+ "thiserror 2.0.18",
]
[[package]]
name = "radroots_log"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"chrono",
"serde_json",
@@ -1952,14 +1930,12 @@ dependencies = [
[[package]]
name = "radroots_nostr"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"nostr",
- "nostr-sdk",
"radroots_event",
"radroots_event_codec",
"radroots_identity",
- "reqwest",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -1967,7 +1943,7 @@ dependencies = [
[[package]]
name = "radroots_protected_store"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"chacha20poly1305",
"getrandom 0.2.17",
@@ -1978,8 +1954,15 @@ dependencies = [
]
[[package]]
+name = "radroots_protocol"
+version = "0.1.0-alpha"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "radroots_runtime"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"anyhow",
"chacha20poly1305",
@@ -2002,7 +1985,7 @@ dependencies = [
[[package]]
name = "radroots_runtime_paths"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
"serde",
"thiserror 1.0.69",
@@ -2010,40 +1993,36 @@ dependencies = [
[[package]]
name = "radroots_secret_vault"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
[[package]]
name = "radroots_transport"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
- "serde",
+ "radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
"sha2",
]
[[package]]
name = "radroots_transport_nostr"
-version = "1.0.0-alpha.1"
+version = "0.1.0-alpha"
dependencies = [
- "futures",
- "nostr",
- "radroots_event",
+ "async-wsocket",
+ "nostr-relay-pool",
+ "nostr-sdk",
+ "radroots_event_codec",
"radroots_nostr",
+ "radroots_protocol",
"radroots_transport",
- "serde",
"serde_json",
- "thiserror 1.0.69",
+ "tokio",
+ "tokio-tungstenite",
"url",
]
[[package]]
-name = "radroots_transport_publish_protocol"
-version = "1.0.0-alpha.1"
-dependencies = [
- "radroots_transport",
- "serde",
-]
-
-[[package]]
name = "radrootsd"
version = "0.1.0"
dependencies = [
@@ -2052,15 +2031,16 @@ dependencies = [
"futures-executor",
"jsonrpsee",
"nostr",
+ "nostr-sdk",
"radroots_event",
"radroots_event_codec",
"radroots_identity",
"radroots_nostr",
+ "radroots_protocol",
"radroots_runtime",
"radroots_runtime_paths",
"radroots_transport",
"radroots_transport_nostr",
- "radroots_transport_publish_protocol",
"rand 0.9.2",
"serde",
"serde_json",
@@ -2162,44 +2142,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
-name = "reqwest"
-version = "0.12.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
-dependencies = [
- "base64 0.22.1",
- "bytes",
- "futures-core",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-rustls",
- "hyper-util",
- "js-sys",
- "log",
- "percent-encoding",
- "pin-project-lite",
- "quinn",
- "rustls",
- "rustls-pki-types",
- "serde",
- "serde_json",
- "serde_urlencoded",
- "sync_wrapper",
- "tokio",
- "tokio-rustls",
- "tower",
- "tower-http",
- "tower-service",
- "url",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
- "webpki-roots 1.0.6",
-]
-
-[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2253,16 +2195,6 @@ dependencies = [
]
[[package]]
-name = "rust_decimal_macros"
-version = "1.40.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2301,7 +2233,6 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
- "web-time",
"zeroize",
]
@@ -2356,6 +2287,19 @@ dependencies = [
]
[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
name = "secp256k1"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2446,18 +2390,6 @@ dependencies = [
]
[[package]]
-name = "serde_urlencoded"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
-dependencies = [
- "form_urlencoded",
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2686,9 +2618,6 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
-dependencies = [
- "futures-core",
-]
[[package]]
name = "synstructure"
@@ -2971,25 +2900,6 @@ dependencies = [
"futures-util",
"pin-project-lite",
"sync_wrapper",
- "tokio",
- "tower-layer",
- "tower-service",
-]
-
-[[package]]
-name = "tower-http"
-version = "0.6.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
-dependencies = [
- "bitflags",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "iri-string",
- "pin-project-lite",
- "tower",
"tower-layer",
"tower-service",
]
@@ -3081,12 +2991,6 @@ dependencies = [
]
[[package]]
-name = "try-lock"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
-
-[[package]]
name = "tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3118,6 +3022,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3173,13 +3083,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
- "idna",
+ "idna 1.1.0",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
+name = "url-fork"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fa3323c39b8e786154d3000b70ae9af0e9bd746c9791456da0d4a1f68ad89d6"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.5.0",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3227,15 +3149,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
-name = "want"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
-dependencies = [
- "try-lock",
-]
-
-[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3363,16 +3276,6 @@ dependencies = [
]
[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3455,16 +3358,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
-dependencies = [
- "windows-targets 0.53.5",
+ "windows-targets",
]
[[package]]
@@ -3482,31 +3376,14 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.53.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
-dependencies = [
- "windows-link",
- "windows_aarch64_gnullvm 0.53.1",
- "windows_aarch64_msvc 0.53.1",
- "windows_i686_gnu 0.53.1",
- "windows_i686_gnullvm 0.53.1",
- "windows_i686_msvc 0.53.1",
- "windows_x86_64_gnu 0.53.1",
- "windows_x86_64_gnullvm 0.53.1",
- "windows_x86_64_msvc 0.53.1",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
@@ -3516,96 +3393,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
-
-[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
-
-[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
-name = "windows_i686_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
-
-[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
-
-[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
-name = "windows_i686_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
-
-[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
-
-[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
-
-[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
-
-[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -3,38 +3,39 @@ name = "radrootsd"
version = "0.1.0"
edition = "2024"
authors = ["Radroots Authors"]
-rust-version = "1.97.0"
+rust-version = "1.97.1"
license = "AGPL-3.0-or-later"
description = "Radroots local runtime daemon for storage, sync, and relay publishing"
[workspace]
-resolver = "2"
+resolver = "3"
[workspace.dependencies]
-radroots_event = { path = "../lib/crates/event", version = "=1.0.0-alpha.1" }
-radroots_event_codec = { path = "../lib/crates/event_codec", version = "=1.0.0-alpha.1" }
-radroots_identity = { path = "../lib/crates/identity", version = "=1.0.0-alpha.1" }
-radroots_nostr = { path = "../lib/crates/nostr", version = "=1.0.0-alpha.1" }
-radroots_transport_publish_protocol = { path = "../lib/crates/transport_publish_protocol", version = "=1.0.0-alpha.1" }
-radroots_transport_nostr = { path = "../lib/crates/transport_nostr", version = "=1.0.0-alpha.1", default-features = false }
-radroots_transport = { path = "../lib/crates/transport", version = "=1.0.0-alpha.1", default-features = false }
-radroots_runtime = { path = "../lib/crates/runtime", version = "=1.0.0-alpha.1" }
-radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "=1.0.0-alpha.1" }
+radroots_event = { path = "../lib/crates/event", version = "=0.1.0-alpha" }
+radroots_event_codec = { path = "../lib/crates/event_codec", version = "=0.1.0-alpha" }
+radroots_identity = { path = "../lib/crates/identity", version = "=0.1.0-alpha" }
+radroots_nostr = { path = "../lib/crates/nostr", version = "=0.1.0-alpha" }
+radroots_protocol = { path = "../lib/crates/protocol", version = "=0.1.0-alpha", default-features = false }
+radroots_transport_nostr = { path = "../lib/crates/transport_nostr", version = "=0.1.0-alpha", default-features = false }
+radroots_transport = { path = "../lib/crates/transport", version = "=0.1.0-alpha", default-features = false }
+radroots_runtime = { path = "../lib/crates/runtime", version = "=0.1.0-alpha" }
+radroots_runtime_paths = { path = "../lib/crates/runtime_paths", version = "=0.1.0-alpha" }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
[dependencies]
-radroots_event = { workspace = true, features = ["serde", "signature"] }
-radroots_event_codec = { workspace = true, features = ["serde_json"] }
+radroots_event = { workspace = true, features = ["serde"] }
+radroots_event_codec = { workspace = true, features = ["json"] }
radroots_identity = { workspace = true }
-radroots_nostr = { workspace = true, features = ["client", "codec", "events", "http"] }
-radroots_transport_publish_protocol = { workspace = true, features = ["std", "serde"] }
-radroots_transport_nostr = { workspace = true, features = ["std", "client"] }
+radroots_nostr = { workspace = true, features = ["events"] }
+radroots_protocol = { workspace = true, features = ["std", "serde"] }
+radroots_transport_nostr = { workspace = true }
radroots_transport = { workspace = true }
radroots_runtime = { workspace = true, features = ["cli"] }
radroots_runtime_paths = { workspace = true }
nostr = { version = "0.44.2", features = ["nip46"] }
+nostr-sdk = { version = "0.44.1" }
anyhow = { version = "1" }
clap = { version = "4", features = ["derive"] }
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
@@ -1,2 +1,2 @@
[toolchain]
-channel = "1.97.0"
+channel = "1.97.1"
diff --git a/src/app/config.rs b/src/app/config.rs
@@ -1,6 +1,6 @@
+use crate::host_nostr::Metadata;
use anyhow::{Context, Result, bail};
-use radroots_event::profile::{RadrootsAuthoredProfile, RadrootsNip05Identifier};
-use radroots_nostr::prelude::RadrootsNostrMetadata;
+use radroots_event::profile::{AuthoredProfile, Nip05Identifier};
use radroots_runtime::RadrootsNostrServiceConfig;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
@@ -176,7 +176,7 @@ struct RawConfiguration {
#[derive(Debug, Deserialize, Clone)]
struct RawSettings {
- pub metadata: RadrootsNostrMetadata,
+ pub metadata: Metadata,
pub config: RawConfiguration,
}
@@ -390,18 +390,18 @@ impl Configuration {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
- pub metadata: RadrootsNostrMetadata,
+ pub metadata: Metadata,
pub config: Configuration,
}
impl Settings {
- pub fn authored_profile(&self) -> Result<RadrootsAuthoredProfile> {
+ pub fn authored_profile(&self) -> Result<AuthoredProfile> {
let metadata = &self.metadata;
let name = metadata
.name
.as_deref()
.ok_or_else(|| anyhow::anyhow!("metadata.name is required for the authored Profile"))?;
- let mut profile = RadrootsAuthoredProfile::new(name.to_owned())
+ let mut profile = AuthoredProfile::new(name.to_owned())
.context("metadata.name is invalid for the authored Profile")?;
if let Some(display_name) = metadata.display_name.as_ref() {
@@ -411,7 +411,7 @@ impl Settings {
profile = profile.with_about(about.clone());
}
if let Some(nip05) = metadata.nip05.as_deref() {
- let nip05 = RadrootsNip05Identifier::parse(nip05)
+ let nip05 = Nip05Identifier::parse(nip05)
.context("metadata.nip05 is invalid for the authored Profile")?;
profile = profile.with_nip05(nip05);
}
@@ -485,7 +485,7 @@ mod tests {
RadrootsdRuntimeContractOutput, default_runtime_paths_for_process,
resolve_runtime_paths_with_resolver, runtime_contract_with_selection,
};
- use radroots_event::profile::RadrootsNip05Identifier;
+ use radroots_event::profile::Nip05Identifier;
use radroots_runtime::RadrootsNostrServiceConfig;
use radroots_runtime_paths::{
RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform,
@@ -609,7 +609,7 @@ mod tests {
#[test]
fn authored_profile_accepts_only_strict_metadata_fields() {
- let mut metadata = radroots_nostr::prelude::RadrootsNostrMetadata::new()
+ let mut metadata = crate::host_nostr::Metadata::new()
.name("radrootsd")
.display_name("Radroots daemon")
.about("local relay publishing")
@@ -631,7 +631,7 @@ mod tests {
assert_eq!(profile.display_name(), Some("Radroots daemon"));
assert_eq!(profile.about(), Some("local relay publishing"));
assert_eq!(
- profile.nip05().map(RadrootsNip05Identifier::as_str),
+ profile.nip05().map(Nip05Identifier::as_str),
Some("daemon@radroots.example")
);
assert_eq!(profile.bot(), Some(true));
@@ -641,7 +641,7 @@ mod tests {
#[test]
fn authored_profile_rejects_url_only_media() {
- let metadata = radroots_nostr::prelude::RadrootsNostrMetadata::new()
+ let metadata = crate::host_nostr::Metadata::new()
.name("radrootsd")
.picture(url::Url::parse("https://blossom.example/blob").expect("picture URL"));
let settings = super::Settings {
@@ -668,7 +668,7 @@ mod tests {
#[test]
fn authored_profile_rejects_missing_invalid_and_unsupported_fields() {
let mut settings = super::Settings {
- metadata: radroots_nostr::prelude::RadrootsNostrMetadata::new(),
+ metadata: crate::host_nostr::Metadata::new(),
config: Configuration {
service: service_config(),
rpc: RpcConfig::default(),
diff --git a/src/app/identity_storage.rs b/src/app/identity_storage.rs
@@ -1,42 +1,126 @@
use std::path::{Path, PathBuf};
-use anyhow::Result;
-use radroots_identity::{IdentityError, RadrootsIdentity, RadrootsIdentityFile};
+use anyhow::{Result, bail};
+use nostr::{Keys, SecretKey};
+use serde::{Deserialize, Serialize};
const RADROOTSD_IDENTITY_KEY_SLOT: &str = "radrootsd_identity";
+/// Host-private service signing identity.
+///
+/// Public identity values cross package boundaries through
+/// `radroots_identity`; secret-key generation and custody remain daemon-owned.
+#[derive(Clone)]
+pub(crate) struct DaemonIdentity {
+ keys: Keys,
+}
+
+impl core::fmt::Debug for DaemonIdentity {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ formatter
+ .debug_struct("DaemonIdentity")
+ .field("public_key", &self.public_key_hex())
+ .finish_non_exhaustive()
+ }
+}
+
+#[derive(Deserialize, Serialize)]
+#[serde(deny_unknown_fields)]
+struct DaemonIdentityFile {
+ secret_key: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ public_key: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ identifier: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ metadata: Option<nostr::Event>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ application_handler: Option<nostr::Event>,
+}
+
+impl DaemonIdentity {
+ pub(crate) fn generate() -> Self {
+ Self {
+ keys: Keys::generate(),
+ }
+ }
+
+ pub(crate) const fn keys(&self) -> &Keys {
+ &self.keys
+ }
+
+ pub(crate) fn public_key(&self) -> nostr::PublicKey {
+ self.keys.public_key()
+ }
+
+ pub(crate) fn public_key_hex(&self) -> String {
+ self.public_key().to_hex()
+ }
+
+ #[cfg(test)]
+ pub(crate) fn id(&self) -> String {
+ self.public_key_hex()
+ }
+
+ fn to_file(&self) -> DaemonIdentityFile {
+ DaemonIdentityFile {
+ secret_key: self.keys.secret_key().to_secret_hex(),
+ public_key: Some(self.public_key_hex()),
+ identifier: None,
+ metadata: None,
+ application_handler: None,
+ }
+ }
+
+ fn from_file(file: DaemonIdentityFile) -> Result<Self> {
+ let secret_key = SecretKey::parse(file.secret_key.as_str())
+ .map_err(|_| anyhow::anyhow!("invalid daemon identity secret"))?;
+ let identity = Self {
+ keys: Keys::new(secret_key),
+ };
+ if file
+ .public_key
+ .as_deref()
+ .is_some_and(|expected| expected != identity.public_key_hex())
+ {
+ bail!("daemon identity public key does not match encrypted secret");
+ }
+ Ok(identity)
+ }
+}
+
#[cfg(test)]
pub fn encrypted_identity_key_path(path: impl AsRef<Path>) -> PathBuf {
radroots_runtime::local_wrapping_key_path(path)
}
-pub fn load_service_identity(
- path: Option<&Path>,
- allow_generate: bool,
-) -> Result<RadrootsIdentity> {
+pub fn load_service_identity(path: Option<&Path>, allow_generate: bool) -> Result<DaemonIdentity> {
let path = resolved_identity_path(path);
if path.exists() {
return load_encrypted_identity(&path);
}
if !allow_generate {
- return Err(IdentityError::GenerationNotAllowed(path).into());
+ bail!(
+ "daemon identity generation is not allowed at {}",
+ path.display()
+ );
}
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
store_encrypted_identity(&path, &identity)?;
Ok(identity)
}
-pub fn store_encrypted_identity(path: impl AsRef<Path>, identity: &RadrootsIdentity) -> Result<()> {
+pub fn store_encrypted_identity(path: impl AsRef<Path>, identity: &DaemonIdentity) -> Result<()> {
let payload = serde_json::to_vec(&identity.to_file())?;
radroots_runtime::seal_local_secret_file(path, RADROOTSD_IDENTITY_KEY_SLOT, &payload)?;
Ok(())
}
-pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<RadrootsIdentity> {
+pub fn load_encrypted_identity(path: impl AsRef<Path>) -> Result<DaemonIdentity> {
let payload = radroots_runtime::open_local_secret_file(path, RADROOTSD_IDENTITY_KEY_SLOT)?;
- let file: RadrootsIdentityFile = serde_json::from_slice(&payload)?;
- Ok(RadrootsIdentity::try_from(file)?)
+ let file: DaemonIdentityFile = serde_json::from_slice(&payload)?;
+ DaemonIdentity::from_file(file)
}
fn resolved_identity_path(path: Option<&Path>) -> PathBuf {
diff --git a/src/app/mod.rs b/src/app/mod.rs
@@ -1,6 +1,6 @@
pub mod cli;
pub mod config;
-mod identity_storage;
+pub(crate) mod identity_storage;
mod paths;
mod runtime;
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
@@ -1,6 +1,6 @@
+use crate::app::identity_storage::DaemonIdentity;
use anyhow::{Context, Result, bail};
use jsonrpsee::server::ServerHandle;
-use radroots_identity::RadrootsIdentity;
use std::time::Duration;
use tracing::{info, warn};
@@ -12,16 +12,13 @@ use crate::core::transport_publish::{
parse_explicit_transport_kind, parse_nostr_source_policy, parse_target_policy,
write_token_file,
};
+use crate::host_nostr::{ApplicationHandlerSpec, Kind, build_application_handler, build_profile};
use crate::transport::jsonrpc;
#[cfg(not(test))]
use crate::transport::nostr::listener::spawn_nip46_listener;
#[cfg(not(test))]
use clap::Parser;
-use radroots_event::profile::RadrootsAuthoredProfile;
-use radroots_nostr::prelude::{
- RadrootsNostrApplicationHandlerSpec, RadrootsNostrKind,
- radroots_nostr_build_application_handler_event, radroots_nostr_build_profile_event,
-};
+use radroots_event::profile::AuthoredProfile;
use std::path::PathBuf;
#[cfg(test)]
@@ -263,10 +260,10 @@ fn log_runtime_startup_report(report: &RadrootsdRuntimeStartupReport) {
#[cfg_attr(coverage_nightly, coverage(off))]
async fn bootstrap_presence(
- client: &radroots_nostr::prelude::RadrootsNostrClient,
- identity: &RadrootsIdentity,
- profile: &RadrootsAuthoredProfile,
- handler_spec: &RadrootsNostrApplicationHandlerSpec,
+ client: &crate::host_nostr::DaemonNostrClient,
+ identity: &DaemonIdentity,
+ profile: &AuthoredProfile,
+ handler_spec: &ApplicationHandlerSpec,
) -> Result<()> {
if let Some(result) = take_bootstrap_hook_result() {
return result.map_err(anyhow::Error::msg);
@@ -292,15 +289,15 @@ async fn bootstrap_presence(
}
fn build_service_presence_events(
- identity: &RadrootsIdentity,
- profile: &RadrootsAuthoredProfile,
- handler_spec: &RadrootsNostrApplicationHandlerSpec,
+ identity: &DaemonIdentity,
+ profile: &AuthoredProfile,
+ handler_spec: &ApplicationHandlerSpec,
) -> Result<(nostr::Event, nostr::Event)> {
- let profile_event = radroots_nostr_build_profile_event(profile)
+ let profile_event = build_profile(profile)
.context("build service Profile event")?
.sign_with_keys(identity.keys())
.context("sign service Profile event")?;
- let handler_event = radroots_nostr_build_application_handler_event(handler_spec)
+ let handler_event = build_application_handler(handler_spec)
.context("build NIP-89 application handler event")?
.sign_with_keys(identity.keys())
.context("sign NIP-89 application handler event")?;
@@ -308,7 +305,7 @@ fn build_service_presence_events(
}
async fn publish_presence_event(
- client: &radroots_nostr::prelude::RadrootsNostrClient,
+ client: &crate::host_nostr::DaemonNostrClient,
event: &nostr::Event,
event_name: &str,
) -> Result<()> {
@@ -324,31 +321,33 @@ async fn publish_presence_event(
#[cfg_attr(coverage_nightly, coverage(off))]
async fn publish_service_presence(
- client: radroots_nostr::prelude::RadrootsNostrClient,
- identity: RadrootsIdentity,
- profile: RadrootsAuthoredProfile,
- metadata: radroots_nostr::prelude::RadrootsNostrMetadata,
+ client: crate::host_nostr::DaemonNostrClient,
+ identity: DaemonIdentity,
+ profile: AuthoredProfile,
+ metadata: crate::host_nostr::Metadata,
service_cfg: radroots_runtime::RadrootsNostrServiceConfig,
nip46_config: config::Nip46Config,
) -> Result<()> {
let kinds = service_presence_kinds();
- let handler_spec = RadrootsNostrApplicationHandlerSpec {
- kinds,
- identifier: service_cfg.nip89_identifier.clone(),
- metadata: Some(metadata.clone()),
- extra_tags: service_cfg.nip89_extra_tags.clone(),
- relays: service_cfg.relays.clone(),
- nostrconnect_url: nip46_config.nostrconnect_url.clone(),
- };
+ let mut handler_spec = ApplicationHandlerSpec::new(kinds)
+ .with_metadata(metadata.clone())
+ .with_extra_tags(service_cfg.nip89_extra_tags.clone())
+ .with_relays(service_cfg.relays.clone());
+ if let Some(identifier) = service_cfg.nip89_identifier.clone() {
+ handler_spec = handler_spec.with_identifier(identifier);
+ }
+ if let Some(url) = nip46_config.nostrconnect_url.clone() {
+ handler_spec = handler_spec.with_nostr_connect_url(url);
+ }
bootstrap_presence(&client, &identity, &profile, &handler_spec).await
}
#[cfg_attr(coverage_nightly, coverage(off))]
async fn maybe_publish_service_presence(
- client: radroots_nostr::prelude::RadrootsNostrClient,
- identity: RadrootsIdentity,
- profile: RadrootsAuthoredProfile,
- metadata: radroots_nostr::prelude::RadrootsNostrMetadata,
+ client: crate::host_nostr::DaemonNostrClient,
+ identity: DaemonIdentity,
+ profile: AuthoredProfile,
+ metadata: crate::host_nostr::Metadata,
service_cfg: radroots_runtime::RadrootsNostrServiceConfig,
nip46_config: config::Nip46Config,
) {
@@ -560,7 +559,7 @@ pub async fn run() -> Result<()> {
}
fn service_presence_kinds() -> Vec<u32> {
- let mut kinds = vec![RadrootsNostrKind::NostrConnect.as_u16() as u32];
+ let mut kinds = vec![Kind::NostrConnect.as_u16() as u32];
kinds.sort_unstable();
kinds.dedup();
kinds
@@ -574,13 +573,11 @@ mod tests {
run_bootstrap_hook, run_load_hook, run_start_rpc_hook, run_wait_hook,
runtime_startup_report,
};
+ use crate::app::identity_storage::DaemonIdentity;
use crate::app::{cli, config, paths};
use crate::core::Radrootsd;
+ use crate::host_nostr::{ApplicationHandlerSpec, Kind, Metadata};
use crate::transport::jsonrpc;
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::{
- RadrootsNostrApplicationHandlerSpec, RadrootsNostrKind, RadrootsNostrMetadata,
- };
use std::path::Path;
use std::path::PathBuf;
use tokio::sync::{Mutex, MutexGuard};
@@ -631,7 +628,7 @@ mod tests {
}
fn settings_with_relays(relays: Vec<String>) -> config::Settings {
- let metadata: RadrootsNostrMetadata =
+ let metadata: Metadata =
serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
config::Settings {
metadata,
@@ -688,7 +685,7 @@ mod tests {
}
async fn make_handle(settings: &config::Settings) -> jsonrpsee::server::ServerHandle {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let state = Radrootsd::new(
identity,
settings.metadata.clone(),
@@ -884,33 +881,25 @@ mod tests {
fn service_presence_kinds_include_nostr_connect_only() {
let kinds = super::service_presence_kinds();
- assert!(
- kinds.contains(
- &(radroots_nostr::prelude::RadrootsNostrKind::NostrConnect.as_u16() as u32)
- )
- );
+ assert!(kinds.contains(&(crate::host_nostr::Kind::NostrConnect.as_u16() as u32)));
assert_eq!(kinds.len(), 1);
}
#[test]
fn service_presence_events_use_strict_profile_and_nip89_contracts() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let settings = settings_with_relays(vec!["wss://relay.example.com".to_owned()]);
let profile = settings.authored_profile().expect("authored profile");
- let handler_spec = RadrootsNostrApplicationHandlerSpec {
- kinds: super::service_presence_kinds(),
- identifier: Some("radrootsd".to_owned()),
- metadata: Some(settings.metadata),
- extra_tags: Vec::new(),
- relays: settings.config.service.relays,
- nostrconnect_url: None,
- };
+ let handler_spec = ApplicationHandlerSpec::new(super::service_presence_kinds())
+ .with_identifier("radrootsd")
+ .with_metadata(settings.metadata)
+ .with_relays(settings.config.service.relays);
let (profile_event, handler_event) =
build_service_presence_events(&identity, &profile, &handler_spec)
.expect("service presence events");
- assert_eq!(profile_event.kind, RadrootsNostrKind::Metadata);
+ assert_eq!(profile_event.kind, Kind::Metadata);
assert!(profile_event.tags.is_empty());
assert_eq!(profile_event.pubkey, identity.public_key());
assert_eq!(
@@ -920,7 +909,7 @@ mod tests {
);
assert_eq!(
handler_event.kind,
- RadrootsNostrKind::Custom(radroots_event::kinds::KIND_APPLICATION_HANDLER as u16)
+ Kind::Custom(radroots_event::envelope::kind::KIND_APPLICATION_HANDLER as u16)
);
assert_eq!(handler_event.pubkey, identity.public_key());
assert!(
@@ -929,12 +918,7 @@ mod tests {
})
);
assert!(handler_event.tags.iter().any(|tag| {
- tag.as_slice()
- == [
- "k".to_owned(),
- RadrootsNostrKind::NostrConnect.as_u16().to_string(),
- ]
- .as_slice()
+ tag.as_slice() == ["k".to_owned(), Kind::NostrConnect.as_u16().to_string()].as_slice()
}));
assert!(handler_event.tags.iter().any(|tag| {
tag.as_slice() == ["relay".to_owned(), "wss://relay.example.com".to_owned()].as_slice()
@@ -945,18 +929,13 @@ mod tests {
#[tokio::test]
async fn bootstrap_presence_fails_closed_without_a_connected_relay() {
let _guard = test_guard().await;
- let identity = RadrootsIdentity::generate();
- let client = radroots_nostr::prelude::RadrootsNostrClient::from_identity(&identity);
+ let identity = DaemonIdentity::generate();
+ let client = crate::host_nostr::DaemonNostrClient::from_identity(&identity);
let settings = settings_with_relays(Vec::new());
let profile = settings.authored_profile().expect("authored profile");
- let handler_spec = RadrootsNostrApplicationHandlerSpec {
- kinds: super::service_presence_kinds(),
- identifier: Some("radrootsd".to_owned()),
- metadata: Some(settings.metadata),
- extra_tags: Vec::new(),
- relays: Vec::new(),
- nostrconnect_url: None,
- };
+ let handler_spec = ApplicationHandlerSpec::new(super::service_presence_kinds())
+ .with_identifier("radrootsd")
+ .with_metadata(settings.metadata);
let error = super::bootstrap_presence(&client, &identity, &profile, &handler_spec)
.await
diff --git a/src/core/nip46/session.rs b/src/core/nip46/session.rs
@@ -7,8 +7,8 @@ use std::time::{Duration, Instant};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
+use crate::host_nostr::{DaemonNostrClient, Keys, PublicKey};
use nostr::nips::nip46::NostrConnectRequest;
-use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrKeys, RadrootsNostrPublicKey};
#[derive(Clone)]
pub struct Nip46SessionStore {
@@ -19,7 +19,7 @@ pub struct Nip46SessionStore {
#[derive(Clone)]
pub struct PendingNostrRequest {
pub request_id: String,
- pub client_pubkey: RadrootsNostrPublicKey,
+ pub client_pubkey: PublicKey,
pub request: NostrConnectRequest,
}
@@ -65,11 +65,11 @@ pub struct Nip46SessionAuthority {
#[derive(Clone)]
pub struct Nip46Session {
pub id: String,
- pub client: RadrootsNostrClient,
- pub client_keys: RadrootsNostrKeys,
- pub client_pubkey: RadrootsNostrPublicKey,
- pub remote_signer_pubkey: RadrootsNostrPublicKey,
- pub user_pubkey: Option<RadrootsNostrPublicKey>,
+ pub(crate) client: DaemonNostrClient,
+ pub client_keys: Keys,
+ pub client_pubkey: PublicKey,
+ pub remote_signer_pubkey: PublicKey,
+ pub user_pubkey: Option<PublicKey>,
pub relays: Vec<String>,
pub perms: Vec<String>,
pub name: Option<String>,
@@ -114,7 +114,7 @@ impl Nip46SessionStore {
sessions.remove(session_id).is_some()
}
- pub async fn set_user_pubkey(&self, session_id: &str, pubkey: RadrootsNostrPublicKey) -> bool {
+ pub async fn set_user_pubkey(&self, session_id: &str, pubkey: PublicKey) -> bool {
let mut sessions = self.inner.lock().await;
match sessions.get_mut(session_id) {
Some(session) => {
@@ -322,8 +322,8 @@ mod tests {
use super::*;
fn build_session(id: &str, expires_at: Option<Instant>) -> Nip46Session {
- let keys = RadrootsNostrKeys::generate();
- let client = RadrootsNostrClient::new(keys.clone());
+ let keys = Keys::generate();
+ let client = DaemonNostrClient::with_keys(keys.clone());
let pubkey = keys.public_key();
Nip46Session {
id: id.to_string(),
@@ -372,11 +372,11 @@ mod tests {
#[test]
fn public_view_marks_outbound_remote_signer_sessions() {
- let client_keys = RadrootsNostrKeys::generate();
- let remote_signer_keys = RadrootsNostrKeys::generate();
+ let client_keys = Keys::generate();
+ let remote_signer_keys = Keys::generate();
let session = Nip46Session {
id: "outbound".to_string(),
- client: RadrootsNostrClient::new(client_keys.clone()),
+ client: DaemonNostrClient::with_keys(client_keys.clone()),
client_keys: client_keys.clone(),
client_pubkey: client_keys.public_key(),
remote_signer_pubkey: remote_signer_keys.public_key(),
@@ -410,12 +410,12 @@ mod tests {
#[test]
fn public_view_keeps_remote_signer_and_user_pubkeys_distinct() {
- let client_keys = RadrootsNostrKeys::generate();
- let remote_signer_keys = RadrootsNostrKeys::generate();
- let user_keys = RadrootsNostrKeys::generate();
+ let client_keys = Keys::generate();
+ let remote_signer_keys = Keys::generate();
+ let user_keys = Keys::generate();
let session = Nip46Session {
id: "hydrated-outbound".to_string(),
- client: RadrootsNostrClient::new(client_keys.clone()),
+ client: DaemonNostrClient::with_keys(client_keys.clone()),
client_keys: client_keys.clone(),
client_pubkey: client_keys.public_key(),
remote_signer_pubkey: remote_signer_keys.public_key(),
@@ -538,7 +538,7 @@ mod tests {
#[tokio::test]
async fn session_store_set_user_pubkey_handles_missing_and_expired() {
let store = Nip46SessionStore::new();
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
assert!(!store.set_user_pubkey("missing", keys.public_key()).await);
let session = build_session(
@@ -560,7 +560,7 @@ mod tests {
"active-user",
Some(Instant::now() + Duration::from_secs(30)),
);
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
let pubkey = keys.public_key();
store.insert(session).await;
assert!(store.set_user_pubkey("active-user", pubkey).await);
@@ -572,7 +572,7 @@ mod tests {
async fn session_store_require_auth_sets_flags_and_clears_pending() {
let store = Nip46SessionStore::new();
let mut session = build_session("auth", Some(Instant::now() + Duration::from_secs(30)));
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
session.pending_request = Some(PendingNostrRequest {
request_id: "req-1".to_string(),
client_pubkey: keys.public_key(),
@@ -615,7 +615,7 @@ mod tests {
let store = Nip46SessionStore::new();
let mut session =
build_session("authorize", Some(Instant::now() + Duration::from_secs(30)));
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
session.pending_request = Some(PendingNostrRequest {
request_id: "req-2".to_string(),
client_pubkey: keys.public_key(),
@@ -646,7 +646,7 @@ mod tests {
#[tokio::test]
async fn session_store_set_pending_request_handles_missing_and_expired() {
let store = Nip46SessionStore::new();
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
let pending = PendingNostrRequest {
request_id: "req-3".to_string(),
client_pubkey: keys.public_key(),
@@ -671,7 +671,7 @@ mod tests {
Some(Instant::now() + Duration::from_secs(30)),
))
.await;
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
let pending = PendingNostrRequest {
request_id: "req-active".to_string(),
client_pubkey: keys.public_key(),
diff --git a/src/core/state.rs b/src/core/state.rs
@@ -1,18 +1,16 @@
+use crate::app::identity_storage::DaemonIdentity;
+use crate::host_nostr::{DaemonNostrClient, Keys, Metadata, PublicKey};
use anyhow::Result;
-use radroots_identity::RadrootsIdentity;
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrKeys, RadrootsNostrMetadata, RadrootsNostrPublicKey,
-};
use crate::app::config::{Nip46Config, TransportPublishConfig};
use crate::core::transport_publish::TransportPublish;
#[derive(Clone)]
pub struct Radrootsd {
- pub client: RadrootsNostrClient,
- pub keys: RadrootsNostrKeys,
- pub pubkey: RadrootsNostrPublicKey,
- pub metadata: RadrootsNostrMetadata,
+ pub(crate) client: DaemonNostrClient,
+ pub keys: Keys,
+ pub pubkey: PublicKey,
+ pub metadata: Metadata,
pub info: serde_json::Value,
pub transport_publish: TransportPublish,
pub(crate) nip46_sessions: crate::core::nip46::session::Nip46SessionStore,
@@ -20,15 +18,15 @@ pub struct Radrootsd {
}
impl Radrootsd {
- pub fn new(
- identity: RadrootsIdentity,
- metadata: RadrootsNostrMetadata,
+ pub(crate) fn new(
+ identity: DaemonIdentity,
+ metadata: Metadata,
transport_publish_config: TransportPublishConfig,
nip46_config: Nip46Config,
) -> Result<Self> {
- let keys: RadrootsNostrKeys = identity.keys().clone();
+ let keys: Keys = identity.keys().clone();
let pubkey = keys.public_key();
- let client = RadrootsNostrClient::new(keys.clone());
+ let client = DaemonNostrClient::with_keys(keys.clone());
let info = serde_json::json!({
"version": env!("CARGO_PKG_VERSION"),
"build": option_env!("GIT_HASH").unwrap_or("unknown"),
@@ -56,13 +54,13 @@ impl Radrootsd {
mod tests {
use super::Radrootsd;
use crate::app::config::{Nip46Config, TransportPublishConfig};
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::RadrootsNostrMetadata;
+ use crate::app::identity_storage::DaemonIdentity;
+ use crate::host_nostr::Metadata;
#[test]
fn new_sets_core_fields() {
- let identity = RadrootsIdentity::generate();
- let metadata: RadrootsNostrMetadata =
+ let identity = DaemonIdentity::generate();
+ let metadata: Metadata =
serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
let transport_publish_cfg = TransportPublishConfig::default();
let cfg = Nip46Config::default();
diff --git a/src/core/transport_publish.rs b/src/core/transport_publish.rs
@@ -8,30 +8,29 @@ use std::str::FromStr;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
-use radroots_event::draft::{
- RadrootsSignatureVerificationError, RadrootsSignedEvent, RadrootsSignedEventError,
+use crate::host_nostr::{DaemonNostrClient, Filter, Kind, PublicKey};
+use crate::transport::relay_publish::{
+ LiveRelayPublishAdapter as DaemonNostrClientPublishAdapter,
+ RelayOutcome as RadrootsRelayOutcome, RelayOutcomeKind as RadrootsRelayOutcomeKind,
+ RelayPublishAdapter as RadrootsRelayPublishAdapter,
+ RelayPublishReceipt as RadrootsRelayPublishRelayReceipt,
+ RelayPublishRequest as RadrootsRelayPublishRequest, RelayTargetSet as RadrootsRelayTargetSet,
+ RelayUrl as RadrootsRelayUrl, RelayUrlPolicy as RadrootsRelayUrlPolicy,
};
-use radroots_event::wire::{RadrootsEventWireError, RadrootsNip01EventWire};
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrPublicKey,
+use radroots_event::draft::{SignedEvent, SignedEventError};
+use radroots_event::wire::{EventWireError, Nip01EventWire};
+use radroots_protocol::radrootsd::transport_publish::v5::{
+ DeliveryPolicy, EventRequest, EventResponse, Job, JobStatus, NostrTargetSourcePolicy,
+ OutcomeKind, RETICULUM_ENDPOINT_URI as RADROOTS_RETICULUM_ENDPOINT_URI,
+ RETICULUM_UNAVAILABLE_MESSAGE as RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, Target,
+ TargetFingerprint as ProtocolTargetFingerprint, TargetOutcome, TargetPolicy, TargetPolicyName,
+ TargetSource,
};
use radroots_transport::{
- RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportKind,
- RadrootsTransportMeshScopeId, RadrootsTransportSatisfactionClass,
+ RadrootsTransportKind, RadrootsTransportMeshScopeId, RadrootsTransportSatisfactionClass,
RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
RadrootsTransportTargetFingerprint, RadrootsTransportTargetLabel,
};
-use radroots_transport_nostr::{
- RadrootsNostrClientPublishAdapter, RadrootsRelayOutcome, RadrootsRelayOutcomeKind,
- RadrootsRelayPublishAdapter, RadrootsRelayPublishRelayReceipt, RadrootsRelayPublishRequest,
- RadrootsRelayTargetSet, RadrootsRelayTransportError, RadrootsRelayUrl, RadrootsRelayUrlPolicy,
-};
-use radroots_transport_publish_protocol::{
- NostrPublishTargetSourcePolicy, TransportPublishDeliveryPolicy, TransportPublishEventRequest,
- TransportPublishEventResponse, TransportPublishJobStatus, TransportPublishJobView,
- TransportPublishOutcomeKind, TransportPublishTarget, TransportPublishTargetOutcome,
- TransportPublishTargetPolicy, TransportPublishTargetPolicyName, TransportPublishTargetSource,
-};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection, SqliteRow};
@@ -144,13 +143,11 @@ pub enum TransportPublishError {
#[error("invalid signed Nostr event: {0}")]
InvalidSignedEvent(String),
#[error("signed event wire error: {0}")]
- EventWire(#[from] RadrootsEventWireError),
+ EventWire(#[from] EventWireError),
#[error("signed event conversion error: {0}")]
- SignedEvent(#[from] RadrootsSignedEventError),
- #[error("signed event signature verification failed: {0}")]
- SignedEventSignature(#[from] RadrootsSignatureVerificationError),
+ SignedEvent(#[from] SignedEventError),
#[error("transport publish relay error: {0}")]
- Relay(#[from] RadrootsRelayTransportError),
+ Relay(String),
#[error("transport publish transport error: {0}")]
Transport(String),
#[error("transport publish schema incompatible for table `{table}`: {detail}")]
@@ -200,7 +197,11 @@ impl TransportPublish {
})
}
- pub fn with_publisher(mut self, publisher: Arc<dyn RadrootsRelayPublishAdapter>) -> Self {
+ #[cfg(test)]
+ pub(crate) fn with_publisher(
+ mut self,
+ publisher: Arc<dyn RadrootsRelayPublishAdapter>,
+ ) -> Self {
self.publisher = Some(publisher);
self
}
@@ -230,8 +231,8 @@ impl TransportPublish {
pub async fn publish_event(
&self,
principal: &PublishPrincipal,
- request: TransportPublishEventRequest,
- ) -> Result<TransportPublishEventResponse, TransportPublishError> {
+ request: EventRequest,
+ ) -> Result<EventResponse, TransportPublishError> {
request
.validate(self.config.max_targets_per_request)
.map_err(|error| {
@@ -254,8 +255,9 @@ impl TransportPublish {
&request,
effective_timeout_ms,
)?;
+ let signed_pubkey = signed_event.pubkey().to_hex();
let resolution = self
- .resolve_targets_for_request(signed_event.pubkey_str(), &request)
+ .resolve_targets_for_request(signed_pubkey.as_str(), &request)
.await?;
validate_delivery_policy_for_resolution(&request.delivery_policy, &resolution)?;
let target_snapshots = target_snapshots_from_resolution(&resolution);
@@ -280,7 +282,7 @@ impl TransportPublish {
resolution,
)
.await?;
- Ok(TransportPublishEventResponse {
+ Ok(EventResponse {
deduplicated: false,
job: completed,
})
@@ -289,30 +291,30 @@ impl TransportPublish {
pub async fn resolve_targets_for_request(
&self,
pubkey: &str,
- request: &TransportPublishEventRequest,
+ request: &EventRequest,
) -> Result<PublishRelayResolution, TransportPublishError> {
match &request.target_policy {
- TransportPublishTargetPolicy::ExplicitTargets { targets } => {
+ TargetPolicy::ExplicitTargets { targets } => {
self.resolve_explicit_targets(targets).await
}
- TransportPublishTargetPolicy::Nostr {
+ TargetPolicy::Nostr {
source_policy,
relay_urls,
} => match source_policy {
- NostrPublishTargetSourcePolicy::ExplicitOnly => {
+ NostrTargetSourcePolicy::ExplicitOnly => {
self.resolve_request_relays(relay_urls).await
}
- NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault => {
+ NostrTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault => {
if !relay_urls.is_empty() {
self.resolve_request_relays(relay_urls).await
} else {
self.resolve_author_or_default_relays(pubkey).await
}
}
- NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault => {
+ NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault => {
self.resolve_author_or_default_relays(pubkey).await
}
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly => {
+ NostrTargetSourcePolicy::DaemonDefaultOnly => {
self.resolve_daemon_default_relays().await
}
},
@@ -321,7 +323,7 @@ impl TransportPublish {
async fn resolve_explicit_targets(
&self,
- targets: &[TransportPublishTarget],
+ targets: &[Target],
) -> Result<PublishRelayResolution, TransportPublishError> {
let mut resolved = Vec::new();
let mut outcomes = Vec::new();
@@ -352,8 +354,8 @@ impl TransportPublish {
async fn resolve_request_target(
&self,
targets: &mut Vec<ResolvedPublishRelay>,
- outcomes: &mut Vec<TransportPublishTargetOutcome>,
- target: &TransportPublishTarget,
+ outcomes: &mut Vec<TargetOutcome>,
+ target: &Target,
) {
match RadrootsRelayUrl::parse(target.endpoint_uri.as_str(), relay_url_policy(&self.config))
{
@@ -362,19 +364,19 @@ impl TransportPublish {
targets,
outcomes,
url,
- TransportPublishTargetSource::Request,
+ TargetSource::Request,
PublishTargetMetadata::from_target(target),
)
.await;
}
- Err(error) => outcomes.push(TransportPublishTargetOutcome {
+ Err(error) => outcomes.push(TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
- endpoint_uri: target.endpoint_uri.trim().to_owned(),
+ endpoint_uri: target.endpoint_uri.clone(),
target_scope: target.target_scope.clone(),
target_label: target.target_label.clone(),
- source: TransportPublishTargetSource::Request,
+ source: TargetSource::Request,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::TargetRejected,
+ outcome_kind: OutcomeKind::TargetRejected,
message: Some(error.to_string()),
latency_ms: None,
}),
@@ -407,19 +409,19 @@ impl TransportPublish {
&mut targets,
&mut outcomes,
url,
- TransportPublishTargetSource::Request,
+ TargetSource::Request,
PublishTargetMetadata::default(),
)
.await;
}
- Err(error) => outcomes.push(TransportPublishTargetOutcome {
+ Err(error) => outcomes.push(TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
- endpoint_uri: relay.trim().to_owned(),
+ endpoint_uri: relay.clone(),
target_scope: None,
target_label: None,
- source: TransportPublishTargetSource::Request,
+ source: TargetSource::Request,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::TargetRejected,
+ outcome_kind: OutcomeKind::TargetRejected,
message: Some(error.to_string()),
latency_ms: None,
}),
@@ -443,7 +445,7 @@ impl TransportPublish {
let mut discovery_targets = self
.resolve_config_relays(
&self.config.nostr.author_relay_discovery_relays,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)
.await?;
if discovery_targets.targets.is_empty() {
@@ -484,22 +486,12 @@ impl TransportPublish {
&mut targets,
&mut outcomes,
url,
- TransportPublishTargetSource::NostrAuthorWrite,
+ TargetSource::NostrAuthorWrite,
PublishTargetMetadata::default(),
)
.await;
}
- Err(error) => outcomes.push(TransportPublishTargetOutcome {
- transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
- endpoint_uri: relay.trim().to_owned(),
- target_scope: None,
- target_label: None,
- source: TransportPublishTargetSource::NostrAuthorWrite,
- attempted: false,
- outcome_kind: TransportPublishOutcomeKind::TargetRejected,
- message: Some(error.to_string()),
- latency_ms: None,
- }),
+ Err(_error) => {}
}
}
Ok(PublishRelayResolution { targets, outcomes })
@@ -510,7 +502,7 @@ impl TransportPublish {
) -> Result<PublishRelayResolution, TransportPublishError> {
self.resolve_config_relays(
&self.config.nostr.daemon_default_relays,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)
.await
}
@@ -518,7 +510,7 @@ impl TransportPublish {
async fn resolve_config_relays(
&self,
relays: &[String],
- source: TransportPublishTargetSource,
+ source: TargetSource,
) -> Result<PublishRelayResolution, TransportPublishError> {
let mut targets = Vec::new();
let mut outcomes = Vec::new();
@@ -534,17 +526,7 @@ impl TransportPublish {
)
.await;
}
- Err(error) => outcomes.push(TransportPublishTargetOutcome {
- transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
- endpoint_uri: relay.trim().to_owned(),
- target_scope: None,
- target_label: None,
- source,
- attempted: false,
- outcome_kind: TransportPublishOutcomeKind::TargetRejected,
- message: Some(error.to_string()),
- latency_ms: None,
- }),
+ Err(_error) => {}
}
}
Ok(PublishRelayResolution { targets, outcomes })
@@ -553,9 +535,9 @@ impl TransportPublish {
async fn push_checked_relay_target(
&self,
targets: &mut Vec<ResolvedPublishRelay>,
- outcomes: &mut Vec<TransportPublishTargetOutcome>,
+ outcomes: &mut Vec<TargetOutcome>,
url: RadrootsRelayUrl,
- source: TransportPublishTargetSource,
+ source: TargetSource,
metadata: PublishTargetMetadata,
) {
if relay_url_policy(&self.config) == RadrootsRelayUrlPolicy::Localhost {
@@ -573,14 +555,14 @@ impl TransportPublish {
}
Ok(addresses) => match url.validate_public_resolved_ip_addrs(addresses) {
Ok(()) => push_resolved_relay(targets, url, source, metadata),
- Err(error) => outcomes.push(TransportPublishTargetOutcome {
+ Err(error) => outcomes.push(TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
endpoint_uri: url.as_str().to_owned(),
target_scope: metadata.target_scope,
target_label: metadata.target_label,
source,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::TargetRejected,
+ outcome_kind: OutcomeKind::TargetRejected,
message: Some(error.to_string()),
latency_ms: None,
}),
@@ -597,15 +579,15 @@ impl TransportPublish {
async fn complete_job_execution(
&self,
job_id: &str,
- signed_event: RadrootsSignedEvent,
- delivery_policy: TransportPublishDeliveryPolicy,
+ signed_event: SignedEvent,
+ delivery_policy: DeliveryPolicy,
timeout_ms: u64,
resolution: PublishRelayResolution,
- ) -> Result<TransportPublishJobView, TransportPublishError> {
+ ) -> Result<Job, TransportPublishError> {
let target_count = resolution.target_count();
if resolution.targets.is_empty() {
let status = if resolution.outcomes.is_empty() {
- TransportPublishJobStatus::Rejected
+ JobStatus::Rejected
} else {
delivery_status(&delivery_policy, target_count, &resolution.outcomes)
};
@@ -622,7 +604,7 @@ impl TransportPublish {
if required_target_count > target_count {
self.store.complete_publish_job(
job_id,
- TransportPublishJobStatus::Rejected,
+ JobStatus::Rejected,
resolution.outcomes,
Some("delivery_quorum_exceeds_target_count".to_owned()),
)?;
@@ -634,7 +616,8 @@ impl TransportPublish {
.iter()
.map(|target| target.url.clone())
.collect(),
- )?;
+ )
+ .map_err(|error| TransportPublishError::Relay(error.to_string()))?;
let satisfaction_policy = satisfaction_policy_from_delivery_policy(
&delivery_policy,
target_count,
@@ -674,13 +657,13 @@ impl TransportPublish {
return publisher
.publish(request)
.await
- .map_err(TransportPublishError::Relay);
+ .map_err(|error| TransportPublishError::Relay(error.to_string()));
}
- let adapter = RadrootsNostrClientPublishAdapter::new(RadrootsNostrClient::new_signerless());
+ let adapter = DaemonNostrClientPublishAdapter::new(DaemonNostrClient::signerless());
adapter
.publish(request)
.await
- .map_err(TransportPublishError::Relay)
+ .map_err(|error| TransportPublishError::Relay(error.to_string()))
}
}
@@ -725,9 +708,9 @@ pub struct PublishPrincipalInit {
pub token_hash: String,
pub allowed_pubkeys: Vec<String>,
pub allowed_kinds: Vec<u32>,
- pub allowed_target_policies: Vec<TransportPublishTargetPolicyName>,
+ pub allowed_target_policies: Vec<TargetPolicyName>,
pub allowed_explicit_transport_kinds: Vec<String>,
- pub allowed_nostr_source_policies: Vec<NostrPublishTargetSourcePolicy>,
+ pub allowed_nostr_source_policies: Vec<NostrTargetSourcePolicy>,
pub allow_request_targets: bool,
pub job_visibility: PublishJobVisibility,
pub expires_at_unix: Option<i64>,
@@ -739,9 +722,9 @@ pub struct PublishPrincipal {
pub label: String,
pub allowed_pubkeys: Vec<String>,
pub allowed_kinds: Vec<u32>,
- pub allowed_target_policies: Vec<TransportPublishTargetPolicyName>,
+ pub allowed_target_policies: Vec<TargetPolicyName>,
pub allowed_explicit_transport_kinds: Vec<String>,
- pub allowed_nostr_source_policies: Vec<NostrPublishTargetSourcePolicy>,
+ pub allowed_nostr_source_policies: Vec<NostrTargetSourcePolicy>,
pub allow_request_targets: bool,
pub job_visibility: PublishJobVisibility,
pub expires_at_unix: Option<i64>,
@@ -750,15 +733,15 @@ pub struct PublishPrincipal {
impl PublishPrincipal {
pub fn allows_event(
&self,
- signed_event: &RadrootsSignedEvent,
- request: &TransportPublishEventRequest,
+ signed_event: &SignedEvent,
+ request: &EventRequest,
) -> Result<(), TransportPublishError> {
- let pubkey = signed_event.pubkey_str();
- ensure_lower_hex("pubkey", pubkey, 64)?;
+ let pubkey = signed_event.pubkey().to_hex();
+ ensure_lower_hex("pubkey", pubkey.as_str(), 64)?;
if !self
.allowed_pubkeys
.iter()
- .any(|allowed_pubkey| allowed_pubkey == pubkey)
+ .any(|allowed_pubkey| allowed_pubkey == pubkey.as_str())
{
return Err(TransportPublishError::InvalidScope(
"principal is not allowed to publish for event pubkey".to_owned(),
@@ -770,10 +753,10 @@ impl PublishPrincipal {
));
}
match &request.target_policy {
- TransportPublishTargetPolicy::ExplicitTargets { targets } => {
+ TargetPolicy::ExplicitTargets { targets } => {
if !self
.allowed_target_policies
- .contains(&TransportPublishTargetPolicyName::ExplicitTargets)
+ .contains(&TargetPolicyName::ExplicitTargets)
{
return Err(TransportPublishError::InvalidScope(
"principal is not allowed to use explicit transport targets".to_owned(),
@@ -804,13 +787,13 @@ impl PublishPrincipal {
}
}
}
- TransportPublishTargetPolicy::Nostr {
+ TargetPolicy::Nostr {
source_policy,
relay_urls,
} => {
if !self
.allowed_target_policies
- .contains(&TransportPublishTargetPolicyName::Nostr)
+ .contains(&TargetPolicyName::Nostr)
{
return Err(TransportPublishError::InvalidScope(
"principal is not allowed to use Nostr target policy".to_owned(),
@@ -841,10 +824,10 @@ pub struct PublishJobInsert {
pub principal_id: String,
pub idempotency_key: Option<String>,
pub event: PublishEventMetadata,
- pub request: TransportPublishEventRequest,
+ pub request: EventRequest,
pub request_fingerprint: String,
pub effective_target_count: usize,
- pub target_snapshots: Vec<TransportPublishTargetOutcome>,
+ pub target_snapshots: Vec<TargetOutcome>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -855,10 +838,10 @@ pub struct PublishEventMetadata {
}
impl PublishEventMetadata {
- fn from_signed_event(signed_event: &RadrootsSignedEvent) -> Self {
+ fn from_signed_event(signed_event: &SignedEvent) -> Self {
Self {
event_id: signed_event.id_str().to_owned(),
- pubkey: signed_event.pubkey_str().to_owned(),
+ pubkey: signed_event.pubkey().to_hex(),
kind: signed_event.kind(),
}
}
@@ -866,8 +849,8 @@ impl PublishEventMetadata {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedPublishRelay {
- pub url: RadrootsRelayUrl,
- pub source: TransportPublishTargetSource,
+ pub(crate) url: RadrootsRelayUrl,
+ pub source: TargetSource,
target_scope: Option<String>,
target_label: Option<String>,
}
@@ -875,7 +858,7 @@ pub struct ResolvedPublishRelay {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublishRelayResolution {
pub targets: Vec<ResolvedPublishRelay>,
- pub outcomes: Vec<TransportPublishTargetOutcome>,
+ pub outcomes: Vec<TargetOutcome>,
}
impl PublishRelayResolution {
@@ -914,7 +897,7 @@ impl ResolvedPublishRelay {
let target =
RadrootsTransportTarget::nostr_relay_with_metadata(self.url.as_str(), scope, label)
.map_err(|error| TransportPublishError::Transport(error.to_string()))?;
- Ok(target.fingerprint)
+ Ok(target.fingerprint().clone())
}
}
@@ -925,7 +908,7 @@ struct PublishTargetMetadata {
}
impl PublishTargetMetadata {
- fn from_target(target: &TransportPublishTarget) -> Self {
+ fn from_target(target: &Target) -> Self {
Self {
target_scope: target.target_scope.clone(),
target_label: target.target_label.clone(),
@@ -976,18 +959,18 @@ impl PublishAuthorRelayDiscovery for NostrPublishAuthorRelayDiscovery {
connect_timeout_secs: u64,
) -> PublishAuthorRelayDiscoveryFuture<'a> {
Box::pin(async move {
- let Ok(public_key) = RadrootsNostrPublicKey::from_hex(pubkey) else {
+ let Ok(public_key) = PublicKey::from_hex(pubkey) else {
return Ok(Vec::new());
};
- let client = RadrootsNostrClient::new_signerless();
+ let client = DaemonNostrClient::signerless();
for target in discovery_targets {
if client.add_read_relay(target.url.as_str()).await.is_err() {
return Ok(Vec::new());
}
}
- let filter = RadrootsNostrFilter::new()
+ let filter = Filter::new()
.author(public_key)
- .kind(RadrootsNostrKind::Custom(10_002))
+ .kind(Kind::Custom(10_002))
.limit(10);
let timeout = Duration::from_secs(connect_timeout_secs);
let Ok(events) = client.fetch_events(filter, timeout).await else {
@@ -1178,7 +1161,7 @@ impl TransportPublishStore {
pub fn record_publish_job(
&self,
insert: PublishJobInsert,
- ) -> Result<TransportPublishEventResponse, TransportPublishError> {
+ ) -> Result<EventResponse, TransportPublishError> {
if insert.effective_target_count != insert.target_snapshots.len() {
return Err(TransportPublishError::InvalidScope(
"publish job target snapshot count must match effective target count".to_owned(),
@@ -1193,7 +1176,7 @@ impl TransportPublishStore {
idempotency_key.to_owned(),
));
}
- return Ok(TransportPublishEventResponse {
+ return Ok(EventResponse {
deduplicated: true,
job: existing.view,
});
@@ -1241,9 +1224,7 @@ impl TransportPublishStore {
.bind(insert.principal_id.as_str())
.bind(insert.idempotency_key.as_deref())
.bind(insert.request_fingerprint.as_str())
- .bind(serde_json::to_string(
- &TransportPublishJobStatus::Publishing,
- )?)
+ .bind(serde_json::to_string(&JobStatus::Publishing)?)
.bind(insert.event.event_id.as_str())
.bind(insert.event.pubkey.as_str())
.bind(i64::from(insert.event.kind))
@@ -1284,7 +1265,7 @@ impl TransportPublishStore {
}
drop(connection);
let job = self.job_by_id(job_id.as_str())?;
- Ok(TransportPublishEventResponse {
+ Ok(EventResponse {
deduplicated: false,
job,
})
@@ -1294,7 +1275,7 @@ impl TransportPublishStore {
&self,
job_id: &str,
principal: &PublishPrincipal,
- ) -> Result<Option<TransportPublishJobView>, TransportPublishError> {
+ ) -> Result<Option<Job>, TransportPublishError> {
let mut connection = self
.inner
.lock()
@@ -1322,7 +1303,7 @@ impl TransportPublishStore {
&self,
principal: &PublishPrincipal,
limit: usize,
- ) -> Result<Vec<TransportPublishJobView>, TransportPublishError> {
+ ) -> Result<Vec<Job>, TransportPublishError> {
let limit = i64::try_from(limit.clamp(1, 200)).unwrap_or(200);
let mut connection = self
.inner
@@ -1389,10 +1370,7 @@ impl TransportPublishStore {
Ok(Some(job))
}
- pub fn job_by_id(
- &self,
- job_id: &str,
- ) -> Result<TransportPublishJobView, TransportPublishError> {
+ pub fn job_by_id(&self, job_id: &str) -> Result<Job, TransportPublishError> {
let mut connection = self
.inner
.lock()
@@ -1418,8 +1396,8 @@ impl TransportPublishStore {
pub fn complete_publish_job(
&self,
job_id: &str,
- status: TransportPublishJobStatus,
- outcomes: Vec<TransportPublishTargetOutcome>,
+ status: JobStatus,
+ outcomes: Vec<TargetOutcome>,
last_error: Option<String>,
) -> Result<(), TransportPublishError> {
let now = current_unix_millis();
@@ -1500,10 +1478,7 @@ impl TransportPublishStore {
Ok(())
}
- fn target_outcomes(
- &self,
- job_id: &str,
- ) -> Result<Vec<TransportPublishTargetOutcome>, TransportPublishError> {
+ fn target_outcomes(&self, job_id: &str) -> Result<Vec<TargetOutcome>, TransportPublishError> {
let mut connection = self
.inner
.lock()
@@ -1543,7 +1518,7 @@ impl TransportPublishStore {
struct PublishJobRow {
principal_id: String,
request_fingerprint: String,
- view: TransportPublishJobView,
+ view: Job,
}
enum TransportPublishSchemaState {
@@ -2165,7 +2140,7 @@ fn recover_interrupted_publish_jobs(
connection: &mut SqliteConnection,
) -> Result<(), TransportPublishError> {
let now = current_unix_millis();
- let publishing = serde_json::to_string(&TransportPublishJobStatus::Publishing)?;
+ let publishing = serde_json::to_string(&JobStatus::Publishing)?;
let sql = job_select_sql("WHERE status = ?1");
let rows = block_on_sqlite(
sqlx::query(sqlx::AssertSqlSafe(sql.as_str()))
@@ -2193,7 +2168,7 @@ fn recover_interrupted_publish_jobs(
"#,
)
.bind(job_id.as_str())
- .bind(serde_json::to_string(&TransportPublishJobStatus::Rejected)?)
+ .bind(serde_json::to_string(&JobStatus::Rejected)?)
.bind(now)
.bind(now)
.bind("publish_attempt_interrupted_missing_target_snapshot")
@@ -2204,7 +2179,7 @@ fn recover_interrupted_publish_jobs(
}
let status = delivery_status(&row.view.delivery_policy, snapshots.len(), &snapshots);
let effective_target_count = storage_count_i64(snapshots.len(), "effective_target_count")?;
- let last_error = if status == TransportPublishJobStatus::DeliveryUnsatisfiedRetryable {
+ let last_error = if status == JobStatus::DeliveryUnsatisfiedRetryable {
Some("publish_attempt_interrupted".to_owned())
} else {
last_error_for_status(status).map(str::to_owned)
@@ -2237,7 +2212,7 @@ fn recover_interrupted_publish_jobs(
fn insert_target_snapshots(
connection: &mut SqliteConnection,
job_id: &str,
- outcomes: &[TransportPublishTargetOutcome],
+ outcomes: &[TargetOutcome],
now: i64,
) -> Result<(), TransportPublishError> {
for (target_index, outcome) in outcomes.iter().enumerate() {
@@ -2286,7 +2261,7 @@ fn insert_target_snapshots(
fn replace_target_outcomes(
connection: &mut SqliteConnection,
job_id: &str,
- outcomes: &[TransportPublishTargetOutcome],
+ outcomes: &[TargetOutcome],
now: i64,
) -> Result<(), TransportPublishError> {
block_on_sqlite(
@@ -2338,7 +2313,7 @@ fn replace_target_outcomes(
fn target_snapshot_outcomes(
connection: &mut SqliteConnection,
job_id: &str,
-) -> Result<Vec<TransportPublishTargetOutcome>, TransportPublishError> {
+) -> Result<Vec<TargetOutcome>, TransportPublishError> {
let rows = block_on_sqlite(
sqlx::query(
r#"
@@ -2402,14 +2377,13 @@ fn principal_from_row(row: &SqliteRow) -> Result<PublishPrincipal, TransportPubl
}
fn job_from_row(row: &SqliteRow) -> Result<PublishJobRow, TransportPublishError> {
- let status: TransportPublishJobStatus = json_text(row, 3, "status")?;
- let target_policy: TransportPublishTargetPolicy = json_text(row, 7, "target_policy_json")?;
- let delivery_policy: TransportPublishDeliveryPolicy =
- json_text(row, 8, "delivery_policy_json")?;
+ let status: JobStatus = json_text(row, 3, "status")?;
+ let target_policy: TargetPolicy = json_text(row, 7, "target_policy_json")?;
+ let delivery_policy: DeliveryPolicy = json_text(row, 8, "delivery_policy_json")?;
Ok(PublishJobRow {
principal_id: row.try_get(1)?,
request_fingerprint: row.try_get(2)?,
- view: TransportPublishJobView {
+ view: Job {
job_id: row.try_get(0)?,
status,
terminal: false,
@@ -2431,12 +2405,10 @@ fn job_from_row(row: &SqliteRow) -> Result<PublishJobRow, TransportPublishError>
})
}
-fn target_outcome_from_row(
- row: &SqliteRow,
-) -> Result<TransportPublishTargetOutcome, TransportPublishError> {
- let source: TransportPublishTargetSource = json_text(row, 4, "source")?;
- let outcome_kind: TransportPublishOutcomeKind = json_text(row, 6, "outcome_kind")?;
- Ok(TransportPublishTargetOutcome {
+fn target_outcome_from_row(row: &SqliteRow) -> Result<TargetOutcome, TransportPublishError> {
+ let source: TargetSource = json_text(row, 4, "source")?;
+ let outcome_kind: OutcomeKind = json_text(row, 6, "outcome_kind")?;
+ Ok(TargetOutcome {
transport_kind: row.try_get(0)?,
endpoint_uri: row.try_get(1)?,
target_scope: storage_target_scope_to_protocol(row.try_get::<String, _>(2)?),
@@ -2449,7 +2421,7 @@ fn target_outcome_from_row(
})
}
-fn finalize_job_view(view: &mut TransportPublishJobView) {
+fn finalize_job_view(view: &mut Job) {
view.acknowledged_count = view
.targets
.iter()
@@ -2467,13 +2439,13 @@ fn finalize_job_view(view: &mut TransportPublishJobView) {
.count();
view.terminal = matches!(
view.status,
- TransportPublishJobStatus::DeliverySatisfied
- | TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
- | TransportPublishJobStatus::DeliveryDeferred
- | TransportPublishJobStatus::DeliveryDeferredUntilImplemented
- | TransportPublishJobStatus::Rejected
+ JobStatus::DeliverySatisfied
+ | JobStatus::DeliveryUnsatisfiedTerminal
+ | JobStatus::DeliveryDeferred
+ | JobStatus::DeliveryDeferredUntilImplemented
+ | JobStatus::Rejected
);
- view.delivery_satisfied = view.status == TransportPublishJobStatus::DeliverySatisfied;
+ view.delivery_satisfied = view.status == JobStatus::DeliverySatisfied;
}
fn validate_principal_init(input: &PublishPrincipalInit) -> Result<(), TransportPublishError> {
@@ -2507,7 +2479,7 @@ fn validate_principal_init(input: &PublishPrincipalInit) -> Result<(), Transport
}
let allows_explicit_targets = input
.allowed_target_policies
- .contains(&TransportPublishTargetPolicyName::ExplicitTargets);
+ .contains(&TargetPolicyName::ExplicitTargets);
if allows_explicit_targets && input.allowed_explicit_transport_kinds.is_empty() {
return Err(TransportPublishError::InvalidScope(
"principal must include at least one allowed explicit transport kind".to_owned(),
@@ -2530,7 +2502,7 @@ fn validate_principal_init(input: &PublishPrincipalInit) -> Result<(), Transport
}
if input
.allowed_target_policies
- .contains(&TransportPublishTargetPolicyName::Nostr)
+ .contains(&TargetPolicyName::Nostr)
&& input.allowed_nostr_source_policies.is_empty()
{
return Err(TransportPublishError::InvalidScope(
@@ -2562,28 +2534,26 @@ fn hex_lower(bytes: &[u8]) -> String {
pub fn parse_nostr_source_policy(
value: &str,
-) -> Result<NostrPublishTargetSourcePolicy, TransportPublishError> {
+) -> Result<NostrTargetSourcePolicy, TransportPublishError> {
match value {
- "explicit_only" => Ok(NostrPublishTargetSourcePolicy::ExplicitOnly),
+ "explicit_only" => Ok(NostrTargetSourcePolicy::ExplicitOnly),
"request_then_author_write_then_daemon_default" => {
- Ok(NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault)
+ Ok(NostrTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault)
}
"author_write_then_daemon_default" => {
- Ok(NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault)
+ Ok(NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault)
}
- "daemon_default_only" => Ok(NostrPublishTargetSourcePolicy::DaemonDefaultOnly),
+ "daemon_default_only" => Ok(NostrTargetSourcePolicy::DaemonDefaultOnly),
other => Err(TransportPublishError::InvalidScope(format!(
"unknown Nostr source policy `{other}`"
))),
}
}
-pub fn parse_target_policy(
- value: &str,
-) -> Result<TransportPublishTargetPolicyName, TransportPublishError> {
+pub fn parse_target_policy(value: &str) -> Result<TargetPolicyName, TransportPublishError> {
match value {
- "explicit_targets" => Ok(TransportPublishTargetPolicyName::ExplicitTargets),
- "nostr" => Ok(TransportPublishTargetPolicyName::Nostr),
+ "explicit_targets" => Ok(TargetPolicyName::ExplicitTargets),
+ "nostr" => Ok(TargetPolicyName::Nostr),
other => Err(TransportPublishError::InvalidScope(format!(
"unknown target policy `{other}`"
))),
@@ -2596,29 +2566,40 @@ pub fn parse_explicit_transport_kind(value: &str) -> Result<String, TransportPub
"unknown explicit transport kind `{value}`: {error}"
))
})?;
- Ok(kind.canonical_label())
+ let canonical = kind.canonical_label();
+ if !matches!(canonical.as_str(), "local" | "nostr" | "reticulum") {
+ return Err(TransportPublishError::InvalidScope(format!(
+ "unknown explicit transport kind `{value}`"
+ )));
+ }
+ Ok(canonical)
}
-fn signed_event_from_raw_json(
- raw_json: &str,
-) -> Result<RadrootsSignedEvent, TransportPublishError> {
- let wire = RadrootsNip01EventWire::parse_json(raw_json)?;
- let signed_event = RadrootsSignedEvent::from_wire_verified_id(wire, raw_json.to_owned())?;
- Ok(signed_event.verify_signature()?.into_signed_event())
+fn signed_event_from_raw_json(raw_json: &str) -> Result<SignedEvent, TransportPublishError> {
+ let wire = Nip01EventWire::parse_json(raw_json)?;
+ let signed_event = SignedEvent::from_wire_verified_id(wire, raw_json.to_owned())?;
+ if radroots_nostr::event::verify(signed_event.envelope())
+ != radroots_nostr::event::Verification::Verified
+ {
+ return Err(TransportPublishError::InvalidSignedEvent(
+ "signature verification failed".to_owned(),
+ ));
+ }
+ Ok(signed_event)
}
fn request_intent_fingerprint(
principal_id: &str,
canonical_event_json: &str,
- request: &TransportPublishEventRequest,
+ request: &EventRequest,
effective_timeout_ms: u64,
) -> Result<String, TransportPublishError> {
#[derive(Serialize)]
struct FingerprintInput<'a> {
principal_id: &'a str,
canonical_event_json: &'a str,
- target_policy: &'a TransportPublishTargetPolicy,
- delivery_policy: &'a TransportPublishDeliveryPolicy,
+ target_policy: &'a TargetPolicy,
+ delivery_policy: &'a DeliveryPolicy,
effective_timeout_ms: u64,
}
@@ -2657,7 +2638,7 @@ fn effective_publish_timeout_ms(
fn push_resolved_relay(
targets: &mut Vec<ResolvedPublishRelay>,
url: RadrootsRelayUrl,
- source: TransportPublishTargetSource,
+ source: TargetSource,
metadata: PublishTargetMetadata,
) {
if !targets
@@ -2673,29 +2654,29 @@ fn push_resolved_relay(
}
}
-fn reticulum_unavailable_outcome(target: &TransportPublishTarget) -> TransportPublishTargetOutcome {
- TransportPublishTargetOutcome {
+fn reticulum_unavailable_outcome(target: &Target) -> TargetOutcome {
+ TargetOutcome {
transport_kind: TRANSPORT_KIND_RETICULUM.to_owned(),
endpoint_uri: target.endpoint_uri.trim().to_owned(),
target_scope: target.target_scope.clone(),
target_label: target.target_label.clone(),
- source: TransportPublishTargetSource::Reticulum,
+ source: TargetSource::Reticulum,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::DeferredUntilImplemented,
+ outcome_kind: OutcomeKind::DeferredUntilImplemented,
message: Some(RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE.to_owned()),
latency_ms: None,
}
}
-fn unsupported_transport_outcome(target: &TransportPublishTarget) -> TransportPublishTargetOutcome {
- TransportPublishTargetOutcome {
+fn unsupported_transport_outcome(target: &Target) -> TargetOutcome {
+ TargetOutcome {
transport_kind: target.transport_kind.trim().to_owned(),
endpoint_uri: target.endpoint_uri.trim().to_owned(),
target_scope: target.target_scope.clone(),
target_label: target.target_label.clone(),
- source: TransportPublishTargetSource::Request,
+ source: TargetSource::Request,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::Unsupported,
+ outcome_kind: OutcomeKind::Unsupported,
message: Some("transport kind is not supported by radrootsd transport publish".to_owned()),
latency_ms: None,
}
@@ -2703,43 +2684,36 @@ fn unsupported_transport_outcome(target: &TransportPublishTarget) -> TransportPu
fn relay_resolution_connection_failure(
relay_url: impl Into<String>,
- source: TransportPublishTargetSource,
+ source: TargetSource,
metadata: &PublishTargetMetadata,
message: impl Into<String>,
-) -> TransportPublishTargetOutcome {
- TransportPublishTargetOutcome {
+) -> TargetOutcome {
+ TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
endpoint_uri: relay_url.into(),
target_scope: metadata.target_scope.clone(),
target_label: metadata.target_label.clone(),
source,
attempted: false,
- outcome_kind: TransportPublishOutcomeKind::ConnectionFailed,
+ outcome_kind: OutcomeKind::ConnectionFailed,
message: Some(message.into()),
latency_ms: None,
}
}
-fn target_snapshots_from_resolution(
- resolution: &PublishRelayResolution,
-) -> Vec<TransportPublishTargetOutcome> {
+fn target_snapshots_from_resolution(resolution: &PublishRelayResolution) -> Vec<TargetOutcome> {
let mut snapshots = resolution.outcomes.clone();
- snapshots.extend(
- resolution
- .targets
- .iter()
- .map(|target| TransportPublishTargetOutcome {
- transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
- endpoint_uri: target.url.as_str().to_owned(),
- target_scope: target.target_scope.clone(),
- target_label: target.target_label.clone(),
- source: target.source,
- attempted: true,
- outcome_kind: TransportPublishOutcomeKind::ConnectionFailed,
- message: Some("publish_attempt_interrupted".to_owned()),
- latency_ms: None,
- }),
- );
+ snapshots.extend(resolution.targets.iter().map(|target| TargetOutcome {
+ transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
+ endpoint_uri: target.url.as_str().to_owned(),
+ target_scope: target.target_scope.clone(),
+ target_label: target.target_label.clone(),
+ source: target.source,
+ attempted: true,
+ outcome_kind: OutcomeKind::ConnectionFailed,
+ message: Some("publish_attempt_interrupted".to_owned()),
+ latency_ms: None,
+ }));
snapshots
}
@@ -2772,9 +2746,7 @@ fn relay_url_policy(config: &TransportPublishConfig) -> RadrootsRelayUrlPolicy {
}
}
-fn author_write_relays_from_nip65_event(
- event: &radroots_nostr::prelude::RadrootsNostrEvent,
-) -> Vec<String> {
+fn author_write_relays_from_nip65_event(event: &crate::host_nostr::Event) -> Vec<String> {
event
.tags
.iter()
@@ -2799,11 +2771,11 @@ fn publish_outcomes_from_receipt(
receipt: RadrootsRelayPublishRelayReceipt,
targets: &[ResolvedPublishRelay],
latency_ms: Option<u64>,
-) -> Vec<TransportPublishTargetOutcome> {
+) -> Vec<TargetOutcome> {
targets
.iter()
.filter(|target| target.url.as_str() == receipt.relay_url.as_str())
- .map(|target| TransportPublishTargetOutcome {
+ .map(|target| TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
endpoint_uri: receipt.relay_url.clone(),
target_scope: target.target_scope.clone(),
@@ -2817,34 +2789,28 @@ fn publish_outcomes_from_receipt(
.collect()
}
-fn publish_outcome_kind(kind: RadrootsRelayOutcomeKind) -> TransportPublishOutcomeKind {
+fn publish_outcome_kind(kind: RadrootsRelayOutcomeKind) -> OutcomeKind {
match kind {
- RadrootsRelayOutcomeKind::Accepted => TransportPublishOutcomeKind::Accepted,
- RadrootsRelayOutcomeKind::DuplicateAccepted => {
- TransportPublishOutcomeKind::DuplicateAccepted
- }
- RadrootsRelayOutcomeKind::Blocked => TransportPublishOutcomeKind::Blocked,
- RadrootsRelayOutcomeKind::RateLimited => TransportPublishOutcomeKind::RateLimited,
- RadrootsRelayOutcomeKind::Invalid => TransportPublishOutcomeKind::Invalid,
- RadrootsRelayOutcomeKind::PowRequired => TransportPublishOutcomeKind::PowRequired,
- RadrootsRelayOutcomeKind::Restricted => TransportPublishOutcomeKind::Restricted,
- RadrootsRelayOutcomeKind::AuthRequired => TransportPublishOutcomeKind::AuthRequired,
- RadrootsRelayOutcomeKind::Muted => TransportPublishOutcomeKind::Muted,
- RadrootsRelayOutcomeKind::Unsupported => TransportPublishOutcomeKind::Unsupported,
- RadrootsRelayOutcomeKind::PaymentRequired => TransportPublishOutcomeKind::PaymentRequired,
- RadrootsRelayOutcomeKind::Error => TransportPublishOutcomeKind::Error,
- RadrootsRelayOutcomeKind::Timeout => TransportPublishOutcomeKind::Timeout,
- RadrootsRelayOutcomeKind::ConnectionFailed => TransportPublishOutcomeKind::ConnectionFailed,
- RadrootsRelayOutcomeKind::RelayUrlRejected => TransportPublishOutcomeKind::TargetRejected,
- RadrootsRelayOutcomeKind::SkippedAlreadyAccepted => {
- TransportPublishOutcomeKind::SkippedAlreadyAccepted
- }
- RadrootsRelayOutcomeKind::Unknown => TransportPublishOutcomeKind::Unknown,
+ RadrootsRelayOutcomeKind::Accepted => OutcomeKind::Accepted,
+ RadrootsRelayOutcomeKind::DuplicateAccepted => OutcomeKind::DuplicateAccepted,
+ RadrootsRelayOutcomeKind::Blocked => OutcomeKind::Blocked,
+ RadrootsRelayOutcomeKind::RateLimited => OutcomeKind::RateLimited,
+ RadrootsRelayOutcomeKind::Invalid => OutcomeKind::Invalid,
+ RadrootsRelayOutcomeKind::PowRequired => OutcomeKind::PowRequired,
+ RadrootsRelayOutcomeKind::Restricted => OutcomeKind::Restricted,
+ RadrootsRelayOutcomeKind::AuthRequired => OutcomeKind::AuthRequired,
+ RadrootsRelayOutcomeKind::Muted => OutcomeKind::Muted,
+ RadrootsRelayOutcomeKind::Unsupported => OutcomeKind::Unsupported,
+ RadrootsRelayOutcomeKind::PaymentRequired => OutcomeKind::PaymentRequired,
+ RadrootsRelayOutcomeKind::Error => OutcomeKind::Error,
+ RadrootsRelayOutcomeKind::Timeout => OutcomeKind::Timeout,
+ RadrootsRelayOutcomeKind::ConnectionFailed => OutcomeKind::ConnectionFailed,
+ RadrootsRelayOutcomeKind::Unknown => OutcomeKind::Unknown,
}
}
fn target_outcome_fingerprint(
- target: &TransportPublishTargetOutcome,
+ target: &TargetOutcome,
index: usize,
) -> Result<RadrootsTransportTargetFingerprint, TransportPublishError> {
let transport_kind = RadrootsTransportKind::parse_canonical(target.transport_kind.as_str())
@@ -2884,7 +2850,7 @@ fn target_outcome_fingerprint(
"target outcome {index} fingerprint failed: {error}"
))
})?;
- Ok(target.fingerprint)
+ Ok(target.fingerprint().clone())
}
fn transport_target_from_outcome_parts(
@@ -2906,40 +2872,41 @@ fn transport_target_from_outcome_parts(
RadrootsTransportKind::Local => {
RadrootsTransportTarget::local_with_metadata(endpoint_uri, scope, label)
}
+ _ => Err(radroots_transport::RadrootsTransportError::InvalidTargetUri),
}
}
fn validate_delivery_policy_for_resolution(
- delivery_policy: &TransportPublishDeliveryPolicy,
+ delivery_policy: &DeliveryPolicy,
resolution: &PublishRelayResolution,
) -> Result<(), TransportPublishError> {
- if !matches!(
- delivery_policy,
- TransportPublishDeliveryPolicy::RequiredTargets { .. }
- ) {
+ let DeliveryPolicy::RequiredTargets { targets } = delivery_policy else {
return Ok(());
- }
+ };
let target_fingerprints = resolution.target_fingerprints()?;
- delivery_policy
- .validate_target_membership(target_fingerprints.as_slice())
- .map_err(|error| {
- TransportPublishError::InvalidSignedEvent(format!(
- "publish request delivery policy validation failed: {error}"
- ))
- })
+ if targets.iter().any(|required| {
+ !target_fingerprints
+ .iter()
+ .any(|actual| actual.as_str() == required.as_str())
+ }) {
+ return Err(TransportPublishError::InvalidSignedEvent(
+ "publish request requires a target outside the resolved set".to_owned(),
+ ));
+ }
+ Ok(())
}
fn required_outcomes_for_policy<'a>(
- required_targets: &[RadrootsTransportTargetFingerprint],
- outcomes: &'a [TransportPublishTargetOutcome],
-) -> Vec<&'a TransportPublishTargetOutcome> {
+ required_targets: &[ProtocolTargetFingerprint],
+ outcomes: &'a [TargetOutcome],
+) -> Vec<&'a TargetOutcome> {
required_targets
.iter()
.filter_map(|required| {
outcomes.iter().enumerate().find_map(|(index, outcome)| {
target_outcome_fingerprint(outcome, index)
.ok()
- .filter(|fingerprint| fingerprint == required)
+ .filter(|fingerprint| fingerprint.as_str() == required.as_str())
.map(|_| outcome)
})
})
@@ -2947,24 +2914,20 @@ fn required_outcomes_for_policy<'a>(
}
fn satisfaction_policy_from_delivery_policy(
- delivery_policy: &TransportPublishDeliveryPolicy,
+ delivery_policy: &DeliveryPolicy,
target_count: usize,
nostr_targets: &[ResolvedPublishRelay],
) -> Result<RadrootsTransportSatisfactionPolicy, TransportPublishError> {
match delivery_policy {
- TransportPublishDeliveryPolicy::Any => {
- Ok(RadrootsTransportSatisfactionPolicy::any_accepted())
- }
- TransportPublishDeliveryPolicy::All => {
- Ok(RadrootsTransportSatisfactionPolicy::all_accepted())
- }
- TransportPublishDeliveryPolicy::Quorum { quorum } => {
+ DeliveryPolicy::Any => Ok(RadrootsTransportSatisfactionPolicy::any_accepted()),
+ DeliveryPolicy::All => Ok(RadrootsTransportSatisfactionPolicy::all_accepted()),
+ DeliveryPolicy::Quorum { quorum } => {
let required = (*quorum).min(target_count).min(nostr_targets.len()).max(1);
Ok(RadrootsTransportSatisfactionPolicy::quorum_accepted(
u16::try_from(required).unwrap_or(u16::MAX),
))
}
- TransportPublishDeliveryPolicy::RequiredTargets { targets } => {
+ DeliveryPolicy::RequiredTargets { targets } => {
let nostr_required_targets = targets
.iter()
.filter_map(|required| {
@@ -2972,7 +2935,7 @@ fn satisfaction_policy_from_delivery_policy(
target
.fingerprint()
.ok()
- .filter(|fingerprint| fingerprint == required)
+ .filter(|fingerprint| fingerprint.as_str() == required.as_str())
})
})
.collect::<Vec<_>>();
@@ -2990,12 +2953,12 @@ fn satisfaction_policy_from_delivery_policy(
}
fn delivery_status(
- delivery_policy: &TransportPublishDeliveryPolicy,
+ delivery_policy: &DeliveryPolicy,
target_count: usize,
- outcomes: &[TransportPublishTargetOutcome],
-) -> TransportPublishJobStatus {
+ outcomes: &[TargetOutcome],
+) -> JobStatus {
let (satisfied, status_outcomes) = match delivery_policy {
- TransportPublishDeliveryPolicy::RequiredTargets { targets } => {
+ DeliveryPolicy::RequiredTargets { targets } => {
let required_outcomes = required_outcomes_for_policy(targets, outcomes);
let satisfied = required_outcomes.len() == targets.len()
&& required_outcomes
@@ -3003,9 +2966,7 @@ fn delivery_status(
.all(|outcome| outcome.outcome_kind.counts_toward_accepted_delivery());
(satisfied, required_outcomes)
}
- TransportPublishDeliveryPolicy::Any
- | TransportPublishDeliveryPolicy::All
- | TransportPublishDeliveryPolicy::Quorum { .. } => {
+ DeliveryPolicy::Any | DeliveryPolicy::All | DeliveryPolicy::Quorum { .. } => {
let required = delivery_policy.required_target_count(target_count);
let acknowledged = outcomes
.iter()
@@ -3018,37 +2979,36 @@ fn delivery_status(
}
};
if satisfied {
- return TransportPublishJobStatus::DeliverySatisfied;
+ return JobStatus::DeliverySatisfied;
}
if status_outcomes
.iter()
.any(|outcome| outcome.outcome_kind.is_retryable())
{
- TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- } else if status_outcomes.iter().any(|outcome| {
- outcome.outcome_kind == TransportPublishOutcomeKind::DeferredUntilImplemented
- }) && status_outcomes
+ JobStatus::DeliveryUnsatisfiedRetryable
+ } else if status_outcomes
.iter()
- .all(|outcome| !outcome.outcome_kind.is_terminal_failure())
+ .any(|outcome| outcome.outcome_kind == OutcomeKind::DeferredUntilImplemented)
+ && status_outcomes
+ .iter()
+ .all(|outcome| !outcome.outcome_kind.is_terminal_failure())
{
- TransportPublishJobStatus::DeliveryDeferredUntilImplemented
+ JobStatus::DeliveryDeferredUntilImplemented
} else {
- TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
+ JobStatus::DeliveryUnsatisfiedTerminal
}
}
-fn last_error_for_status(status: TransportPublishJobStatus) -> Option<&'static str> {
+fn last_error_for_status(status: JobStatus) -> Option<&'static str> {
match status {
- TransportPublishJobStatus::DeliverySatisfied => None,
- TransportPublishJobStatus::Rejected => Some("no_transport_publish_targets"),
- TransportPublishJobStatus::DeliveryDeferred => Some("delivery_deferred_until_implemented"),
- TransportPublishJobStatus::DeliveryDeferredUntilImplemented => {
- Some("delivery_deferred_until_implemented")
- }
- TransportPublishJobStatus::Accepted
- | TransportPublishJobStatus::Publishing
- | TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- | TransportPublishJobStatus::DeliveryUnsatisfiedTerminal => Some("delivery_unsatisfied"),
+ JobStatus::DeliverySatisfied => None,
+ JobStatus::Rejected => Some("no_transport_publish_targets"),
+ JobStatus::DeliveryDeferred => Some("delivery_deferred_until_implemented"),
+ JobStatus::DeliveryDeferredUntilImplemented => Some("delivery_deferred_until_implemented"),
+ JobStatus::Accepted
+ | JobStatus::Publishing
+ | JobStatus::DeliveryUnsatisfiedRetryable
+ | JobStatus::DeliveryUnsatisfiedTerminal => Some("delivery_unsatisfied"),
}
}
@@ -3258,22 +3218,24 @@ mod tests {
use crate::app::config::{
NostrRelayUrlPolicy, TransportPublishConfig, TransportPublishNostrConfig,
};
+ use crate::app::identity_storage::DaemonIdentity;
+ use crate::host_nostr::Timestamp;
+ use crate::transport::relay_publish::{
+ MockRelayPublishAdapter as RadrootsMockRelayPublishAdapter,
+ RelayOutcome as RadrootsRelayOutcome,
+ };
use nostr::JsonUtil;
use nostr::{EventBuilder, Kind, Tag};
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::RadrootsNostrTimestamp;
- use radroots_transport::{
- RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
- RadrootsTransportTarget,
+ use radroots_protocol::radrootsd::transport_publish::v5::{
+ DeliveryPolicy, EventRequest, JobStatus, NostrTargetSourcePolicy, OutcomeKind,
+ ReticulumBehavior, Target, TargetFingerprint as ProtocolTargetFingerprint, TargetOutcome,
+ TargetPolicy, TargetPolicyName, TargetSource,
};
- use radroots_transport_nostr::{RadrootsMockRelayPublishAdapter, RadrootsRelayOutcome};
- use radroots_transport_publish_protocol::{
- NostrPublishTargetSourcePolicy, TransportPublishDeliveryPolicy,
- TransportPublishEventRequest, TransportPublishJobStatus, TransportPublishOutcomeKind,
- TransportPublishReticulumBehavior, TransportPublishTarget, TransportPublishTargetOutcome,
- TransportPublishTargetPolicy, TransportPublishTargetPolicyName,
- TransportPublishTargetSource,
+ use radroots_protocol::radrootsd::transport_publish::v5::{
+ RETICULUM_ENDPOINT_URI as RADROOTS_RETICULUM_ENDPOINT_URI,
+ RETICULUM_UNAVAILABLE_MESSAGE as RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
};
+ use radroots_transport::RadrootsTransportTarget;
use sqlx::Row;
use sqlx::sqlite::{SqliteConnectOptions, SqliteConnection};
use std::collections::BTreeMap;
@@ -3302,14 +3264,14 @@ mod tests {
)
}
- fn request(pubkey: &str, kind: u32) -> TransportPublishEventRequest {
- TransportPublishEventRequest {
+ fn request(pubkey: &str, kind: u32) -> EventRequest {
+ EventRequest {
raw_event_json: raw_event_json(pubkey, kind),
- target_policy: TransportPublishTargetPolicy::nostr(
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
+ target_policy: TargetPolicy::nostr(
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
Vec::new(),
),
- delivery_policy: TransportPublishDeliveryPolicy::Any,
+ delivery_policy: DeliveryPolicy::Any,
idempotency_key: Some("idem-1".to_owned()),
timeout_ms: None,
}
@@ -3383,12 +3345,12 @@ mod tests {
.collect()
}
- fn signed_event(identity: &RadrootsIdentity, content: &str) -> String {
+ fn signed_event(identity: &DaemonIdentity, content: &str) -> String {
// Transport tests require an already-signed wire fixture; they do not
// exercise a Radroots product-authoring boundary.
let event = EventBuilder::new(Kind::Custom(30_402), content)
.tag(Tag::identifier("listing-1"))
- .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
+ .custom_created_at(Timestamp::from_secs(1_700_000_000))
.sign_with_keys(identity.keys())
.expect("signed event");
event.as_json()
@@ -3408,13 +3370,13 @@ mod tests {
fn publish_request(
raw_event_json: String,
relays: Vec<String>,
- source_policy: NostrPublishTargetSourcePolicy,
- delivery_policy: TransportPublishDeliveryPolicy,
+ source_policy: NostrTargetSourcePolicy,
+ delivery_policy: DeliveryPolicy,
idempotency_key: Option<&str>,
- ) -> TransportPublishEventRequest {
- TransportPublishEventRequest {
+ ) -> EventRequest {
+ EventRequest {
raw_event_json,
- target_policy: TransportPublishTargetPolicy::nostr(source_policy, relays),
+ target_policy: TargetPolicy::nostr(source_policy, relays),
delivery_policy,
idempotency_key: idempotency_key.map(str::to_owned),
timeout_ms: Some(5_000),
@@ -3423,58 +3385,50 @@ mod tests {
fn reticulum_publish_request(
raw_event_json: String,
- behavior: TransportPublishReticulumBehavior,
- ) -> TransportPublishEventRequest {
- TransportPublishEventRequest {
+ behavior: ReticulumBehavior,
+ ) -> EventRequest {
+ EventRequest {
raw_event_json,
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::reticulum(behavior),
- ]),
- delivery_policy: TransportPublishDeliveryPolicy::Any,
+ target_policy: TargetPolicy::explicit_targets(vec![Target::reticulum(behavior)]),
+ delivery_policy: DeliveryPolicy::Any,
idempotency_key: None,
timeout_ms: Some(5_000),
}
}
- fn interrupted_target_snapshot(
- endpoint_uri: &str,
- source: TransportPublishTargetSource,
- ) -> TransportPublishTargetOutcome {
- TransportPublishTargetOutcome {
+ fn interrupted_target_snapshot(endpoint_uri: &str, source: TargetSource) -> TargetOutcome {
+ TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
endpoint_uri: endpoint_uri.to_owned(),
target_scope: None,
target_label: None,
source,
attempted: true,
- outcome_kind: TransportPublishOutcomeKind::ConnectionFailed,
+ outcome_kind: OutcomeKind::ConnectionFailed,
message: Some("publish_attempt_interrupted".to_owned()),
latency_ms: None,
}
}
- fn accepted_target_outcome(
- endpoint_uri: &str,
- source: TransportPublishTargetSource,
- ) -> TransportPublishTargetOutcome {
- TransportPublishTargetOutcome {
+ fn accepted_target_outcome(endpoint_uri: &str, source: TargetSource) -> TargetOutcome {
+ TargetOutcome {
transport_kind: TRANSPORT_KIND_NOSTR.to_owned(),
endpoint_uri: endpoint_uri.to_owned(),
target_scope: None,
target_label: None,
source,
attempted: true,
- outcome_kind: TransportPublishOutcomeKind::Accepted,
+ outcome_kind: OutcomeKind::Accepted,
message: None,
latency_ms: Some(12),
}
}
fn scoped_target_outcome(
- mut outcome: TransportPublishTargetOutcome,
+ mut outcome: TargetOutcome,
target_scope: &str,
target_label: Option<&str>,
- ) -> TransportPublishTargetOutcome {
+ ) -> TargetOutcome {
outcome.target_scope = Some(target_scope.to_owned());
outcome.target_label = target_label.map(str::to_owned);
outcome
@@ -3487,11 +3441,9 @@ mod tests {
token_hash: hash_bearer_token(generate_bearer_token().as_str()),
allowed_pubkeys: vec![pubkey.to_owned()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
@@ -3542,7 +3494,7 @@ mod tests {
fn principal(
proxy: &TransportPublish,
pubkey: String,
- nostr_source_policies: Vec<NostrPublishTargetSourcePolicy>,
+ nostr_source_policies: Vec<NostrTargetSourcePolicy>,
allow_request_targets: bool,
visibility: PublishJobVisibility,
) -> PublishPrincipal {
@@ -3553,7 +3505,7 @@ mod tests {
token_hash: hash_bearer_token(generate_bearer_token().as_str()),
allowed_pubkeys: vec![pubkey],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
allowed_nostr_source_policies: nostr_source_policies,
allow_request_targets,
@@ -3592,7 +3544,7 @@ mod tests {
token_hash: hash_bearer_token(generate_bearer_token().as_str()),
allowed_pubkeys: vec![pubkey],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::ExplicitTargets],
+ allowed_target_policies: vec![TargetPolicyName::ExplicitTargets],
allowed_explicit_transport_kinds,
allowed_nostr_source_policies: Vec::new(),
allow_request_targets: true,
@@ -3620,7 +3572,7 @@ mod tests {
token_hash: hash_bearer_token(generate_bearer_token().as_str()),
allowed_pubkeys: vec!["a".repeat(64)],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::ExplicitTargets],
+ allowed_target_policies: vec![TargetPolicyName::ExplicitTargets],
allowed_explicit_transport_kinds: Vec::new(),
allowed_nostr_source_policies: Vec::new(),
allow_request_targets: true,
@@ -3663,12 +3615,11 @@ mod tests {
));
let mut nostr_policy_with_explicit_kinds = base;
- nostr_policy_with_explicit_kinds.allowed_target_policies =
- vec![TransportPublishTargetPolicyName::Nostr];
+ nostr_policy_with_explicit_kinds.allowed_target_policies = vec![TargetPolicyName::Nostr];
nostr_policy_with_explicit_kinds.allowed_explicit_transport_kinds =
vec![TRANSPORT_KIND_NOSTR.to_owned()];
nostr_policy_with_explicit_kinds.allowed_nostr_source_policies =
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly];
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly];
assert!(matches!(
store.create_principal(nostr_policy_with_explicit_kinds),
Err(TransportPublishError::InvalidScope(message))
@@ -3700,7 +3651,7 @@ mod tests {
impl super::PublishRelayResolver for StaticPublishRelayResolver {
fn resolve<'a>(
&'a self,
- url: &'a radroots_transport_nostr::RadrootsRelayUrl,
+ url: &'a super::RadrootsRelayUrl,
) -> super::PublishRelayResolveFuture<'a> {
Box::pin(async move {
match self.results.get(url.as_str()) {
@@ -3749,7 +3700,7 @@ mod tests {
fn nostr_source_policy_parser_accepts_contract_values() {
assert_eq!(
parse_nostr_source_policy("explicit_only").expect("policy"),
- NostrPublishTargetSourcePolicy::ExplicitOnly
+ NostrTargetSourcePolicy::ExplicitOnly
);
assert!(parse_nostr_source_policy("unknown").is_err());
}
@@ -3759,19 +3710,17 @@ mod tests {
let store = TransportPublishStore::memory().expect("store");
let token = generate_bearer_token();
let token_hash = hash_bearer_token(token.as_str());
- let accepted_identity = RadrootsIdentity::generate();
- let denied_identity = RadrootsIdentity::generate();
+ let accepted_identity = DaemonIdentity::generate();
+ let denied_identity = DaemonIdentity::generate();
let principal = store
.create_principal(PublishPrincipalInit {
label: "tester".to_owned(),
token_hash: token_hash.clone(),
allowed_pubkeys: vec![accepted_identity.public_key_hex()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
@@ -3788,8 +3737,8 @@ mod tests {
let denied = publish_request(
signed_event(&denied_identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
);
let denied_signed =
@@ -3799,8 +3748,8 @@ mod tests {
let accepted = publish_request(
signed_event(&accepted_identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
);
let accepted_signed = super::signed_event_from_raw_json(accepted.raw_event_json.as_str())
@@ -3818,7 +3767,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![interrupted_target_snapshot(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
@@ -3833,7 +3782,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![interrupted_target_snapshot(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("dedupe");
@@ -3864,17 +3813,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
None,
)
@@ -3917,7 +3866,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect_err("invalid dedupe"),
@@ -3942,17 +3891,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
None,
)
@@ -3996,7 +3945,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect_err("invalid dedupe"),
@@ -4021,17 +3970,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
None,
)
@@ -4074,7 +4023,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect_err("invalid dedupe"),
@@ -4098,17 +4047,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
None,
)
@@ -4151,7 +4100,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect_err("invalid dedupe"),
@@ -4165,10 +4114,7 @@ mod tests {
let pubkey = "a".repeat(64);
let principal = store_principal(&store, pubkey.as_str());
let mut request = request(pubkey.as_str(), 30_402);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget::nostr(
- RELAY_PRIMARY,
- )]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target::nostr(RELAY_PRIMARY)]);
let response = store
.record_publish_job(PublishJobInsert {
principal_id: principal.principal_id.clone(),
@@ -4179,17 +4125,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_PRIMARY,
- TransportPublishTargetSource::Request,
+ TargetSource::Request,
)],
})
.expect("record job");
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![accepted_target_outcome(
RELAY_SECONDARY,
- TransportPublishTargetSource::Request,
+ TargetSource::Request,
)],
None,
)
@@ -4209,8 +4155,8 @@ mod tests {
let pubkey = "a".repeat(64);
let principal = store_principal(&store, pubkey.as_str());
let mut request = request(pubkey.as_str(), 30_402);
- request.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY)
+ request.target_policy = TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY)
.with_scope("farm.local")
.with_label("Farm relay"),
]);
@@ -4223,7 +4169,7 @@ mod tests {
request_fingerprint: "fingerprint-explicit-scope-drift".to_owned(),
effective_target_count: 1,
target_snapshots: vec![scoped_target_outcome(
- accepted_target_outcome(RELAY_PRIMARY, TransportPublishTargetSource::Request),
+ accepted_target_outcome(RELAY_PRIMARY, TargetSource::Request),
"farm.local",
Some("Farm relay"),
)],
@@ -4232,9 +4178,9 @@ mod tests {
store
.complete_publish_job(
response.job.job_id.as_str(),
- TransportPublishJobStatus::DeliverySatisfied,
+ JobStatus::DeliverySatisfied,
vec![scoped_target_outcome(
- accepted_target_outcome(RELAY_PRIMARY, TransportPublishTargetSource::Request),
+ accepted_target_outcome(RELAY_PRIMARY, TargetSource::Request),
"farm.remote",
Some("Farm relay"),
)],
@@ -4265,11 +4211,9 @@ mod tests {
token_hash,
allowed_pubkeys: vec![pubkey.clone()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
@@ -4285,20 +4229,17 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![interrupted_target_snapshot(
RELAY_PRIMARY,
- TransportPublishTargetSource::DaemonDefault,
+ TargetSource::DaemonDefault,
)],
})
.expect("record job");
- assert_eq!(response.job.status, TransportPublishJobStatus::Publishing);
+ assert_eq!(response.job.status, JobStatus::Publishing);
(response.job.job_id, principal)
};
let reopened = TransportPublishStore::open(database_path).expect("reopen store");
let recovered = reopened.job_by_id(job_id.as_str()).expect("recovered job");
- assert_eq!(
- recovered.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- );
+ assert_eq!(recovered.status, JobStatus::DeliveryUnsatisfiedRetryable);
assert_eq!(
recovered.last_error.as_deref(),
Some("publish_attempt_interrupted")
@@ -4307,7 +4248,7 @@ mod tests {
assert_eq!(recovered.targets.len(), 1);
assert_eq!(
recovered.targets[0].outcome_kind,
- TransportPublishOutcomeKind::ConnectionFailed
+ OutcomeKind::ConnectionFailed
);
recovered.validate().expect("valid recovered job");
let listed = reopened
@@ -4326,8 +4267,8 @@ mod tests {
let store = TransportPublishStore::open(database_path.clone()).expect("store");
let principal = store_principal(&store, pubkey.as_str());
let mut request = request(pubkey.as_str(), 30_402);
- request.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY)
+ request.target_policy = TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY)
.with_scope("farm.local")
.with_label("Farm relay"),
]);
@@ -4340,10 +4281,7 @@ mod tests {
request_fingerprint: "fingerprint-interrupted-scoped".to_owned(),
effective_target_count: 1,
target_snapshots: vec![scoped_target_outcome(
- interrupted_target_snapshot(
- RELAY_PRIMARY,
- TransportPublishTargetSource::Request,
- ),
+ interrupted_target_snapshot(RELAY_PRIMARY, TargetSource::Request),
"farm.local",
Some("Farm relay"),
)],
@@ -4354,10 +4292,7 @@ mod tests {
let reopened = TransportPublishStore::open(database_path).expect("reopen store");
let recovered = reopened.job_by_id(job_id.as_str()).expect("recovered job");
- assert_eq!(
- recovered.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- );
+ assert_eq!(recovered.status, JobStatus::DeliveryUnsatisfiedRetryable);
assert_eq!(recovered.targets.len(), 1);
assert_eq!(
recovered.targets[0].target_scope.as_deref(),
@@ -4390,9 +4325,8 @@ mod tests {
let store = TransportPublishStore::open(database_path.clone()).expect("store");
let principal = store_principal(&store, pubkey.as_str());
let mut request = request(pubkey.as_str(), 30_402);
- request.target_policy = TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- ]);
+ request.target_policy =
+ TargetPolicy::explicit_targets(vec![Target::nostr(RELAY_PRIMARY)]);
let response = store
.record_publish_job(PublishJobInsert {
principal_id: principal.principal_id.clone(),
@@ -4403,7 +4337,7 @@ mod tests {
effective_target_count: 1,
target_snapshots: vec![accepted_target_outcome(
RELAY_SECONDARY,
- TransportPublishTargetSource::Request,
+ TargetSource::Request,
)],
})
.expect("record job");
@@ -4442,11 +4376,9 @@ mod tests {
token_hash,
allowed_pubkeys: vec![pubkey.clone()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
@@ -4484,9 +4416,7 @@ mod tests {
.bind(principal.principal_id.as_str())
.bind("idem-missing-snapshot")
.bind("fingerprint-missing-snapshot")
- .bind(
- serde_json::to_string(&TransportPublishJobStatus::Publishing).expect("status"),
- )
+ .bind(serde_json::to_string(&JobStatus::Publishing).expect("status"))
.bind("0".repeat(64))
.bind(pubkey.as_str())
.bind(30_402_i64)
@@ -4512,7 +4442,7 @@ mod tests {
let recovered = reopened
.job_by_id("job-missing-snapshot")
.expect("recovered job");
- assert_eq!(recovered.status, TransportPublishJobStatus::Rejected);
+ assert_eq!(recovered.status, JobStatus::Rejected);
assert_eq!(
recovered.last_error.as_deref(),
Some("publish_attempt_interrupted_missing_target_snapshot")
@@ -4599,12 +4529,9 @@ mod tests {
.bind(token_hash.as_str())
.bind(serde_json::to_string(&vec!["a".repeat(64)]).expect("pubkeys"))
.bind(serde_json::to_string(&vec![30_402]).expect("kinds"))
+ .bind(serde_json::to_string(&vec![TargetPolicyName::Nostr]).expect("policies"))
.bind(
- serde_json::to_string(&vec![TransportPublishTargetPolicyName::Nostr])
- .expect("policies"),
- )
- .bind(
- serde_json::to_string(&vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly])
+ serde_json::to_string(&vec![NostrTargetSourcePolicy::DaemonDefaultOnly])
.expect("source policies"),
)
.bind(false)
@@ -4832,12 +4759,12 @@ mod tests {
#[tokio::test]
async fn publish_event_verifies_and_records_daemon_default_outcome() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -4849,8 +4776,8 @@ mod tests {
publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-valid"),
),
)
@@ -4858,28 +4785,22 @@ mod tests {
.expect("publish");
assert!(!response.deduplicated);
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.target_count, 1);
assert_eq!(response.job.acknowledged_count, 1);
assert_eq!(response.job.targets[0].endpoint_uri, RELAY_PRIMARY);
- assert_eq!(
- response.job.targets[0].source,
- TransportPublishTargetSource::DaemonDefault
- );
+ assert_eq!(response.job.targets[0].source, TargetSource::DaemonDefault);
assert_eq!(adapter.captured_raw_events(), vec![raw_event]);
}
#[tokio::test]
async fn publish_event_rejects_tampered_content_before_publish() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -4894,8 +4815,8 @@ mod tests {
publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
@@ -4908,12 +4829,12 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_wrong_signature_before_publish() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -4930,8 +4851,8 @@ mod tests {
publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
@@ -4940,19 +4861,19 @@ mod tests {
assert!(matches!(
error,
- TransportPublishError::SignedEventSignature(_)
+ TransportPublishError::InvalidSignedEvent(_)
));
assert!(adapter.captured_raw_events().is_empty());
}
#[tokio::test]
async fn publish_event_rejects_malformed_wire_fields() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -4966,8 +4887,8 @@ mod tests {
publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
@@ -4980,12 +4901,12 @@ mod tests {
#[tokio::test]
async fn publish_event_uses_explicit_request_relays_when_allowed() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_SECONDARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault],
+ vec![NostrTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault],
true,
PublishJobVisibility::Own,
);
@@ -4995,28 +4916,22 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
vec![RELAY_PRIMARY.to_owned()],
- NostrPublishTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::RequestThenAuthorWriteThenDaemonDefault,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.targets[0].endpoint_uri, RELAY_PRIMARY);
- assert_eq!(
- response.job.targets[0].source,
- TransportPublishTargetSource::Request
- );
+ assert_eq!(response.job.targets[0].source, TargetSource::Request);
}
#[tokio::test]
async fn publish_event_uses_cached_nip65_author_write_before_defaults() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_SECONDARY]));
proxy
.store
@@ -5028,7 +4943,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault],
+ vec![NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault],
false,
PublishJobVisibility::Own,
);
@@ -5038,8 +4953,8 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault,
+ DeliveryPolicy::Any,
None,
),
)
@@ -5049,13 +4964,13 @@ mod tests {
assert_eq!(response.job.targets[0].endpoint_uri, RELAY_PRIMARY);
assert_eq!(
response.job.targets[0].source,
- TransportPublishTargetSource::NostrAuthorWrite
+ TargetSource::NostrAuthorWrite
);
}
#[tokio::test]
- async fn publish_event_records_invalid_cached_author_write_relay() {
- let identity = RadrootsIdentity::generate();
+ async fn publish_event_discards_invalid_cached_author_write_relay() {
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_SECONDARY]));
proxy
.store
@@ -5067,7 +4982,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault],
+ vec![NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault],
false,
PublishJobVisibility::Own,
);
@@ -5077,50 +4992,36 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
let accepted = response
.job
.targets
.iter()
.find(|relay| relay.endpoint_uri == RELAY_PRIMARY)
.expect("accepted author relay");
- assert_eq!(
- accepted.source,
- TransportPublishTargetSource::NostrAuthorWrite
- );
+ assert_eq!(accepted.source, TargetSource::NostrAuthorWrite);
assert!(accepted.attempted);
- let rejected = response
- .job
- .targets
- .iter()
- .find(|relay| relay.endpoint_uri == "not a cached relay")
- .expect("rejected cached author relay");
- assert_eq!(
- rejected.source,
- TransportPublishTargetSource::NostrAuthorWrite
- );
- assert_eq!(
- rejected.outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
+ assert!(
+ response
+ .job
+ .targets
+ .iter()
+ .all(|relay| relay.endpoint_uri != "not a cached relay")
);
- assert!(!rejected.attempted);
assert_eq!(adapter.captured_raw_events().len(), 1);
}
#[tokio::test]
- async fn publish_event_preserves_author_and_discovery_rejections_through_relay_selection() {
- let identity = RadrootsIdentity::generate();
+ async fn publish_event_discards_invalid_author_and_discovery_relays() {
+ let identity = DaemonIdentity::generate();
let mut config = config_with_defaults(vec![RELAY_SECONDARY]);
config.nostr.author_relay_discovery_relays = vec!["not a discovery relay".to_owned()];
let (proxy, adapter) = transport_publish(config);
@@ -5134,7 +5035,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault],
+ vec![NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault],
false,
PublishJobVisibility::Own,
);
@@ -5144,65 +5045,33 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
let daemon_default = response
.job
.targets
.iter()
.find(|relay| relay.endpoint_uri == RELAY_SECONDARY)
.expect("daemon default relay");
- assert_eq!(
- daemon_default.source,
- TransportPublishTargetSource::DaemonDefault
- );
+ assert_eq!(daemon_default.source, TargetSource::DaemonDefault);
assert!(daemon_default.attempted);
- let cached = response
- .job
- .targets
- .iter()
- .find(|relay| relay.endpoint_uri == "not a cached relay")
- .expect("cached author rejection");
- assert_eq!(
- cached.source,
- TransportPublishTargetSource::NostrAuthorWrite
- );
- assert_eq!(
- cached.outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
- );
- assert!(!cached.attempted);
- let discovery = response
- .job
- .targets
- .iter()
- .find(|relay| relay.endpoint_uri == "not a discovery relay")
- .expect("discovery relay rejection");
- assert_eq!(
- discovery.source,
- TransportPublishTargetSource::DaemonDefault
- );
- assert_eq!(
- discovery.outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
- );
- assert!(!discovery.attempted);
+ assert!(response.job.targets.iter().all(|relay| {
+ relay.endpoint_uri != "not a cached relay"
+ && relay.endpoint_uri != "not a discovery relay"
+ }));
assert_eq!(adapter.captured_raw_events().len(), 1);
}
#[tokio::test]
- async fn publish_event_preserves_discovery_and_discovered_author_rejections() {
- let identity = RadrootsIdentity::generate();
+ async fn publish_event_preserves_valid_discovery_rejections_only() {
+ let identity = DaemonIdentity::generate();
let mut config = config_with_defaults(vec![RELAY_PRIMARY]);
config.nostr.author_relay_discovery_relays =
vec![RELAY_PRIMARY.to_owned(), RELAY_FORBIDDEN.to_owned()];
@@ -5222,7 +5091,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault],
+ vec![NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault],
false,
PublishJobVisibility::Own,
);
@@ -5232,70 +5101,50 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::AuthorWriteThenDaemonDefault,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::AuthorWriteThenDaemonDefault,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
let accepted = response
.job
.targets
.iter()
.find(|relay| relay.endpoint_uri == RELAY_SECONDARY)
.expect("discovered author relay");
- assert_eq!(
- accepted.source,
- TransportPublishTargetSource::NostrAuthorWrite
- );
+ assert_eq!(accepted.source, TargetSource::NostrAuthorWrite);
assert!(accepted.attempted);
- let discovered = response
- .job
- .targets
- .iter()
- .find(|relay| relay.endpoint_uri == "not a discovered author relay")
- .expect("discovered author rejection");
- assert_eq!(
- discovered.source,
- TransportPublishTargetSource::NostrAuthorWrite
- );
- assert_eq!(
- discovered.outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
+ assert!(
+ response
+ .job
+ .targets
+ .iter()
+ .all(|relay| relay.endpoint_uri != "not a discovered author relay")
);
- assert!(!discovered.attempted);
let discovery = response
.job
.targets
.iter()
.find(|relay| relay.endpoint_uri == RELAY_FORBIDDEN)
.expect("discovery relay rejection");
- assert_eq!(
- discovery.source,
- TransportPublishTargetSource::DaemonDefault
- );
- assert_eq!(
- discovery.outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
- );
+ assert_eq!(discovery.source, TargetSource::DaemonDefault);
+ assert_eq!(discovery.outcome_kind, OutcomeKind::TargetRejected);
assert!(!discovery.attempted);
assert_eq!(adapter.captured_raw_events().len(), 1);
}
#[tokio::test]
async fn publish_event_records_no_transport_publish_targets_failure() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -5305,15 +5154,15 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(response.job.status, TransportPublishJobStatus::Rejected);
+ assert_eq!(response.job.status, JobStatus::Rejected);
assert_eq!(
response.job.last_error.as_deref(),
Some("no_transport_publish_targets")
@@ -5324,7 +5173,7 @@ mod tests {
#[tokio::test]
async fn publish_event_records_reticulum_unavailable_as_deferred_until_implemented() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
@@ -5333,7 +5182,7 @@ mod tests {
&principal,
reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
),
)
.await
@@ -5341,7 +5190,7 @@ mod tests {
assert_eq!(
response.job.status,
- TransportPublishJobStatus::DeliveryDeferredUntilImplemented
+ JobStatus::DeliveryDeferredUntilImplemented
);
assert!(response.job.terminal);
assert!(!response.job.delivery_satisfied);
@@ -5354,7 +5203,7 @@ mod tests {
assert_eq!(response.job.targets.len(), 1);
assert_eq!(
response.job.targets[0].outcome_kind,
- TransportPublishOutcomeKind::DeferredUntilImplemented
+ OutcomeKind::DeferredUntilImplemented
);
assert_eq!(
response.job.targets[0].message.as_deref(),
@@ -5366,18 +5215,16 @@ mod tests {
#[tokio::test]
async fn publish_event_records_explicit_nostr_target_when_kind_allowed() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let event = signed_event(&identity, "{}");
let raw_event = event.clone();
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: event,
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- ]),
- delivery_policy: TransportPublishDeliveryPolicy::Any,
+ target_policy: TargetPolicy::explicit_targets(vec![Target::nostr(RELAY_PRIMARY)]),
+ delivery_policy: DeliveryPolicy::Any,
idempotency_key: None,
timeout_ms: Some(5_000),
};
@@ -5387,15 +5234,9 @@ mod tests {
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.targets.len(), 1);
- assert_eq!(
- response.job.targets[0].source,
- TransportPublishTargetSource::Request
- );
+ assert_eq!(response.job.targets[0].source, TargetSource::Request);
assert_eq!(response.job.targets[0].endpoint_uri, RELAY_PRIMARY);
assert_eq!(response.job.targets[0].target_scope, None);
assert_eq!(response.job.targets[0].target_label, None);
@@ -5404,20 +5245,20 @@ mod tests {
#[tokio::test]
async fn publish_event_preserves_explicit_nostr_target_metadata_when_kind_allowed() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let event = signed_event(&identity, "{}");
let raw_event = event.clone();
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: event,
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY)
+ target_policy: TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY)
.with_scope("farm.local")
.with_label("Farm relay"),
]),
- delivery_policy: TransportPublishDeliveryPolicy::Any,
+ delivery_policy: DeliveryPolicy::Any,
idempotency_key: None,
timeout_ms: Some(5_000),
};
@@ -5427,10 +5268,7 @@ mod tests {
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.targets.len(), 1);
assert_eq!(response.job.targets[0].endpoint_uri, RELAY_PRIMARY);
assert_eq!(
@@ -5441,33 +5279,30 @@ mod tests {
response.job.targets[0].target_label.as_deref(),
Some("Farm relay")
);
- assert_eq!(
- response.job.targets[0].source,
- TransportPublishTargetSource::Request
- );
+ assert_eq!(response.job.targets[0].source, TargetSource::Request);
assert_eq!(adapter.captured_raw_events(), vec![raw_event]);
response.job.validate().expect("valid scoped job");
}
#[tokio::test]
async fn publish_event_records_scoped_targets_with_shared_relay_url() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let event = signed_event(&identity, "{}");
let raw_event = event.clone();
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: event,
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY)
+ target_policy: TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY)
.with_scope("farm.a")
.with_label("Farm A"),
- TransportPublishTarget::nostr(RELAY_PRIMARY)
+ Target::nostr(RELAY_PRIMARY)
.with_scope("farm.b")
.with_label("Farm B"),
]),
- delivery_policy: TransportPublishDeliveryPolicy::All,
+ delivery_policy: DeliveryPolicy::All,
idempotency_key: None,
timeout_ms: Some(5_000),
};
@@ -5477,10 +5312,7 @@ mod tests {
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.targets.len(), 2);
assert!(
response
@@ -5513,7 +5345,7 @@ mod tests {
#[tokio::test]
async fn publish_event_required_targets_do_not_count_optional_success() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let adapter = RadrootsMockRelayPublishAdapter::new()
.with_outcome(
RELAY_PRIMARY,
@@ -5526,14 +5358,15 @@ mod tests {
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let required_target =
RadrootsTransportTarget::nostr_relay(RELAY_PRIMARY).expect("required target");
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: signed_event(&identity, "{}"),
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- TransportPublishTarget::nostr(RELAY_SECONDARY),
+ target_policy: TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY),
+ Target::nostr(RELAY_SECONDARY),
]),
- delivery_policy: TransportPublishDeliveryPolicy::required_targets(vec![
- required_target.fingerprint,
+ delivery_policy: DeliveryPolicy::required_targets(vec![
+ ProtocolTargetFingerprint::parse(required_target.fingerprint().as_str())
+ .expect("protocol target fingerprint"),
])
.expect("required targets"),
idempotency_key: None,
@@ -5545,10 +5378,7 @@ mod tests {
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
- );
+ assert_eq!(response.job.status, JobStatus::DeliveryUnsatisfiedTerminal);
assert!(!response.job.delivery_satisfied);
assert_eq!(response.job.acknowledged_count, 1);
assert_eq!(adapter.captured_raw_events().len(), 1);
@@ -5560,7 +5390,7 @@ mod tests {
#[tokio::test]
async fn publish_event_required_targets_ignore_optional_retryable_failures() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let adapter = RadrootsMockRelayPublishAdapter::new()
.with_outcome(RELAY_PRIMARY, RadrootsRelayOutcome::accepted())
.with_outcome(
@@ -5573,14 +5403,15 @@ mod tests {
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let required_target =
RadrootsTransportTarget::nostr_relay(RELAY_PRIMARY).expect("required target");
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: signed_event(&identity, "{}"),
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- TransportPublishTarget::nostr(RELAY_SECONDARY),
+ target_policy: TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY),
+ Target::nostr(RELAY_SECONDARY),
]),
- delivery_policy: TransportPublishDeliveryPolicy::required_targets(vec![
- required_target.fingerprint,
+ delivery_policy: DeliveryPolicy::required_targets(vec![
+ ProtocolTargetFingerprint::parse(required_target.fingerprint().as_str())
+ .expect("protocol target fingerprint"),
])
.expect("required targets"),
idempotency_key: None,
@@ -5592,10 +5423,7 @@ mod tests {
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert!(response.job.delivery_satisfied);
assert_eq!(response.job.acknowledged_count, 1);
assert_eq!(response.job.retryable_count, 1);
@@ -5608,19 +5436,18 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_required_target_not_in_resolved_set() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let stale_target =
RadrootsTransportTarget::nostr_relay(RELAY_SECONDARY).expect("stale target");
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: signed_event(&identity, "{}"),
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- ]),
- delivery_policy: TransportPublishDeliveryPolicy::required_targets(vec![
- stale_target.fingerprint,
+ target_policy: TargetPolicy::explicit_targets(vec![Target::nostr(RELAY_PRIMARY)]),
+ delivery_policy: DeliveryPolicy::required_targets(vec![
+ ProtocolTargetFingerprint::parse(stale_target.fingerprint().as_str())
+ .expect("protocol target fingerprint"),
])
.expect("required targets"),
idempotency_key: None,
@@ -5632,11 +5459,14 @@ mod tests {
.await
.expect_err("stale required target");
- assert!(matches!(
- err,
- TransportPublishError::InvalidSignedEvent(ref message)
- if message.contains("required target")
- ));
+ assert!(
+ matches!(
+ err,
+ TransportPublishError::InvalidSignedEvent(ref message)
+ if message.contains("requires a target")
+ ),
+ "{err:?}"
+ );
assert!(adapter.captured_raw_events().is_empty());
assert!(
proxy
@@ -5649,17 +5479,17 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_duplicate_explicit_targets_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
- let request = TransportPublishEventRequest {
+ let request = EventRequest {
raw_event_json: signed_event(&identity, "{}"),
- target_policy: TransportPublishTargetPolicy::explicit_targets(vec![
- TransportPublishTarget::nostr(RELAY_PRIMARY),
- TransportPublishTarget::nostr(RELAY_PRIMARY),
+ target_policy: TargetPolicy::explicit_targets(vec![
+ Target::nostr(RELAY_PRIMARY),
+ Target::nostr(RELAY_PRIMARY),
]),
- delivery_policy: TransportPublishDeliveryPolicy::Any,
+ delivery_policy: DeliveryPolicy::Any,
idempotency_key: None,
timeout_ms: Some(5_000),
};
@@ -5686,7 +5516,7 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_explicit_target_kind_not_allowed_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal = explicit_target_principal_with_kinds(
&proxy,
@@ -5700,7 +5530,7 @@ mod tests {
&principal,
reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
),
)
.await
@@ -5719,7 +5549,7 @@ mod tests {
#[tokio::test]
async fn publish_event_records_reticulum_deferred_as_terminal_nonfailure() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
@@ -5728,7 +5558,7 @@ mod tests {
&principal,
reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::DeferDeliveryPlans,
+ ReticulumBehavior::DeferDeliveryPlans,
),
)
.await
@@ -5736,7 +5566,7 @@ mod tests {
assert_eq!(
response.job.status,
- TransportPublishJobStatus::DeliveryDeferredUntilImplemented
+ JobStatus::DeliveryDeferredUntilImplemented
);
assert!(response.job.terminal);
assert!(!response.job.delivery_satisfied);
@@ -5749,7 +5579,7 @@ mod tests {
assert_eq!(response.job.targets.len(), 1);
assert_eq!(
response.job.targets[0].outcome_kind,
- TransportPublishOutcomeKind::DeferredUntilImplemented
+ OutcomeKind::DeferredUntilImplemented
);
assert!(!response.job.targets[0].attempted);
assert!(adapter.captured_raw_events().is_empty());
@@ -5757,22 +5587,21 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_noncanonical_reticulum_endpoint_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let mut request = reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: "reticulum".to_owned(),
- endpoint_uri: "reticulum:unavailable-alt".to_owned(),
- target_scope: None,
- target_label: None,
- reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
- }]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target {
+ transport_kind: "reticulum".to_owned(),
+ endpoint_uri: "reticulum:unavailable-alt".to_owned(),
+ target_scope: None,
+ target_label: None,
+ reticulum_behavior: Some(ReticulumBehavior::RejectDeliveryAttempts),
+ }]);
let err = proxy
.publish_event(&principal, request)
@@ -5792,25 +5621,24 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_reticulum_behavior_on_non_reticulum_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let mut request = publish_request(
signed_event(&identity, "{}"),
vec![RELAY_PRIMARY.to_owned()],
- NostrPublishTargetSourcePolicy::ExplicitOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::ExplicitOnly,
+ DeliveryPolicy::Any,
None,
);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: "nostr".to_owned(),
- endpoint_uri: RELAY_PRIMARY.to_owned(),
- target_scope: None,
- target_label: None,
- reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
- }]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target {
+ transport_kind: "nostr".to_owned(),
+ endpoint_uri: RELAY_PRIMARY.to_owned(),
+ target_scope: None,
+ target_label: None,
+ reticulum_behavior: Some(ReticulumBehavior::RejectDeliveryAttempts),
+ }]);
let err = proxy
.publish_event(&principal, request)
@@ -5830,22 +5658,21 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_noncanonical_reticulum_kind_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let mut request = reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: "Reticulum".to_owned(),
- endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
- target_scope: None,
- target_label: None,
- reticulum_behavior: Some(TransportPublishReticulumBehavior::RejectDeliveryAttempts),
- }]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target {
+ transport_kind: "Reticulum".to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_ENDPOINT_URI.to_owned(),
+ target_scope: None,
+ target_label: None,
+ reticulum_behavior: Some(ReticulumBehavior::RejectDeliveryAttempts),
+ }]);
let err = proxy
.publish_event(&principal, request)
@@ -5865,22 +5692,21 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_removed_execution_kind_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let mut request = reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: removed_execution_kind_string(),
- endpoint_uri: removed_execution_endpoint_uri(),
- target_scope: None,
- target_label: None,
- reticulum_behavior: None,
- }]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target {
+ transport_kind: removed_execution_kind_string(),
+ endpoint_uri: removed_execution_endpoint_uri(),
+ target_scope: None,
+ target_label: None,
+ reticulum_behavior: None,
+ }]);
let err = proxy
.publish_event(&principal, request)
@@ -5900,22 +5726,21 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_removed_execution_target_before_recording_job() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal =
explicit_target_principal(&proxy, identity.public_key_hex(), PublishJobVisibility::Own);
let mut request = reticulum_publish_request(
signed_event(&identity, "{}"),
- TransportPublishReticulumBehavior::RejectDeliveryAttempts,
+ ReticulumBehavior::RejectDeliveryAttempts,
);
- request.target_policy =
- TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
- transport_kind: removed_proxy_transport_kind_string(),
- endpoint_uri: removed_execution_endpoint_uri(),
- target_scope: None,
- target_label: None,
- reticulum_behavior: None,
- }]);
+ request.target_policy = TargetPolicy::explicit_targets(vec![Target {
+ transport_kind: removed_proxy_transport_kind_string(),
+ endpoint_uri: removed_execution_endpoint_uri(),
+ target_scope: None,
+ target_label: None,
+ reticulum_behavior: None,
+ }]);
let err = proxy
.publish_event(&principal, request)
@@ -5947,12 +5772,12 @@ mod tests {
#[tokio::test]
async fn publish_event_records_unsafe_request_relay_rejection() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(TransportPublishConfig::default());
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::ExplicitOnly],
+ vec![NostrTargetSourcePolicy::ExplicitOnly],
true,
PublishJobVisibility::Own,
);
@@ -5962,22 +5787,19 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
vec!["wss://127.0.0.1:7777".to_owned()],
- NostrPublishTargetSourcePolicy::ExplicitOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::ExplicitOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
- );
+ assert_eq!(response.job.status, JobStatus::DeliveryUnsatisfiedTerminal);
assert_eq!(response.job.targets.len(), 1);
assert_eq!(
response.job.targets[0].outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
+ OutcomeKind::TargetRejected
);
assert!(!response.job.targets[0].attempted);
assert!(adapter.captured_raw_events().is_empty());
@@ -5985,7 +5807,7 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_forbidden_public_dns_destination_before_publish() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let resolver = StaticPublishRelayResolver::new()
.with_addresses(RELAY_PRIMARY, vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]);
let (proxy, adapter) = transport_publish_with_resolver(
@@ -5995,7 +5817,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6005,22 +5827,19 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedTerminal
- );
+ assert_eq!(response.job.status, JobStatus::DeliveryUnsatisfiedTerminal);
assert_eq!(response.job.targets.len(), 1);
assert_eq!(
response.job.targets[0].outcome_kind,
- TransportPublishOutcomeKind::TargetRejected
+ OutcomeKind::TargetRejected
);
assert!(!response.job.targets[0].attempted);
assert!(adapter.captured_raw_events().is_empty());
@@ -6028,7 +5847,7 @@ mod tests {
#[tokio::test]
async fn publish_event_records_dns_failure_as_unattempted_retryable_outcome() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let resolver = StaticPublishRelayResolver::new().with_failure(RELAY_PRIMARY, "no records");
let (proxy, adapter) = transport_publish_with_resolver(
config_with_defaults(vec![RELAY_PRIMARY]),
@@ -6037,7 +5856,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6047,18 +5866,15 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- );
+ assert_eq!(response.job.status, JobStatus::DeliveryUnsatisfiedRetryable);
assert_eq!(
response.job.last_error.as_deref(),
Some("delivery_unsatisfied")
@@ -6066,7 +5882,7 @@ mod tests {
assert_eq!(response.job.targets.len(), 1);
assert_eq!(
response.job.targets[0].outcome_kind,
- TransportPublishOutcomeKind::ConnectionFailed
+ OutcomeKind::ConnectionFailed
);
assert!(!response.job.targets[0].attempted);
assert!(adapter.captured_raw_events().is_empty());
@@ -6074,7 +5890,7 @@ mod tests {
#[tokio::test]
async fn publish_event_localhost_policy_skips_public_dns_guard() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let mut config = config_with_defaults(vec!["ws://localhost:7777"]);
config.nostr.relay_url_policy = NostrRelayUrlPolicy::Localhost;
let resolver = StaticPublishRelayResolver::new()
@@ -6083,7 +5899,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6093,38 +5909,35 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliverySatisfied
- );
+ assert_eq!(response.job.status, JobStatus::DeliverySatisfied);
assert_eq!(response.job.targets[0].endpoint_uri, "ws://localhost:7777");
assert!(!adapter.captured_raw_events().is_empty());
}
#[tokio::test]
async fn publish_event_deduplicates_same_intent_and_conflicts_different_intent() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
let request = publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-conflict"),
);
let first = proxy
@@ -6146,8 +5959,8 @@ mod tests {
publish_request(
signed_event(&identity, "changed"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-conflict"),
),
)
@@ -6161,20 +5974,20 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_zero_and_excessive_timeout_before_job_creation() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
let mut zero = publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-zero-timeout"),
);
zero.timeout_ms = Some(0);
@@ -6190,8 +6003,8 @@ mod tests {
let mut excessive = publish_request(
signed_event(&identity, "changed"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-excessive-timeout"),
);
excessive.timeout_ms = Some(10_001);
@@ -6215,12 +6028,12 @@ mod tests {
#[tokio::test]
async fn publish_event_default_timeout_fingerprints_as_effective_timeout() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6228,16 +6041,16 @@ mod tests {
let mut default_timeout = publish_request(
event.clone(),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-default-timeout"),
);
default_timeout.timeout_ms = None;
let mut explicit_default = publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-default-timeout"),
);
explicit_default.timeout_ms = Some(10_000);
@@ -6257,12 +6070,12 @@ mod tests {
#[tokio::test]
async fn publish_event_fingerprint_conflicts_on_different_effective_timeout() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6270,15 +6083,15 @@ mod tests {
let first = publish_request(
event.clone(),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-timeout-conflict"),
);
let mut conflict = publish_request(
event,
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-timeout-conflict"),
);
conflict.timeout_ms = Some(6_000);
@@ -6296,14 +6109,14 @@ mod tests {
#[tokio::test]
async fn publish_event_concurrency_limit_rejects_without_job_creation() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let mut config = config_with_defaults(vec![RELAY_PRIMARY]);
config.max_concurrent_publish_jobs = 1;
let (proxy, adapter) = transport_publish(config);
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6314,8 +6127,8 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
Some("idem-concurrency"),
),
)
@@ -6334,27 +6147,27 @@ mod tests {
#[tokio::test]
async fn publish_jobs_respect_own_and_admin_visibility() {
- let identity = RadrootsIdentity::generate();
- let other_identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
+ let other_identity = DaemonIdentity::generate();
let (proxy, _adapter) = transport_publish(config_with_defaults(vec![RELAY_PRIMARY]));
let owner = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
let other = principal(
&proxy,
other_identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
let admin = principal(
&proxy,
other_identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Admin,
);
@@ -6364,8 +6177,8 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
@@ -6390,7 +6203,7 @@ mod tests {
#[tokio::test]
async fn publish_event_records_retryable_relay_failures() {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let adapter = RadrootsMockRelayPublishAdapter::new().with_outcome(
RELAY_PRIMARY,
RadrootsRelayOutcome::connection_failed("error: unavailable"),
@@ -6401,7 +6214,7 @@ mod tests {
let principal = principal(
&proxy,
identity.public_key_hex(),
- vec![NostrPublishTargetSourcePolicy::DaemonDefaultOnly],
+ vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
false,
PublishJobVisibility::Own,
);
@@ -6411,18 +6224,15 @@ mod tests {
publish_request(
signed_event(&identity, "{}"),
Vec::new(),
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- TransportPublishDeliveryPolicy::Any,
+ NostrTargetSourcePolicy::DaemonDefaultOnly,
+ DeliveryPolicy::Any,
None,
),
)
.await
.expect("publish");
- assert_eq!(
- response.job.status,
- TransportPublishJobStatus::DeliveryUnsatisfiedRetryable
- );
+ assert_eq!(response.job.status, JobStatus::DeliveryUnsatisfiedRetryable);
assert_eq!(response.job.retryable_count, 1);
}
}
diff --git a/src/host_nostr.rs b/src/host_nostr.rs
@@ -0,0 +1,107 @@
+//! Daemon-owned Nostr client adapter.
+//!
+//! Radroots protocol crates deliberately do not own relay clients, process
+//! lifecycle, or service keys. This private adapter keeps those host concerns
+//! in `radrootsd` while using upstream protocol values at the explicit edge.
+
+use core::time::Duration;
+
+pub(crate) use nostr::{
+ Event, Filter, Keys, Kind, Metadata, PublicKey, SecretKey, SubscriptionId, Timestamp,
+};
+pub(crate) use nostr_sdk::RelayPoolNotification;
+pub(crate) use radroots_nostr::event::{
+ ApplicationHandlerSpec, build_application_handler, build_profile,
+};
+
+#[derive(Clone)]
+pub(crate) struct DaemonNostrClient {
+ inner: nostr_sdk::Client,
+}
+
+impl DaemonNostrClient {
+ pub(crate) fn signerless() -> Self {
+ let inner = nostr_sdk::Client::default();
+ inner.automatic_authentication(false);
+ Self { inner }
+ }
+
+ pub(crate) fn with_keys(keys: Keys) -> Self {
+ let inner = nostr_sdk::Client::new(keys);
+ inner.automatic_authentication(false);
+ Self { inner }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn from_identity(identity: &crate::app::identity_storage::DaemonIdentity) -> Self {
+ Self::with_keys(identity.keys().clone())
+ }
+
+ pub(crate) fn into_inner(self) -> nostr_sdk::Client {
+ self.inner
+ }
+
+ pub(crate) async fn connect(&self) {
+ self.inner.connect().await;
+ }
+
+ pub(crate) async fn wait_for_connection(&self, timeout: Duration) {
+ self.inner.wait_for_connection(timeout).await;
+ }
+
+ pub(crate) async fn add_relay(&self, url: &str) -> Result<bool, nostr_sdk::client::Error> {
+ self.inner.add_relay(url).await
+ }
+
+ pub(crate) async fn add_read_relay(&self, url: &str) -> Result<bool, nostr_sdk::client::Error> {
+ self.inner.add_read_relay(url).await
+ }
+
+ pub(crate) async fn relays(
+ &self,
+ ) -> std::collections::HashMap<nostr::RelayUrl, nostr_sdk::Relay> {
+ self.inner.relays().await
+ }
+
+ pub(crate) async fn fetch_events(
+ &self,
+ filter: Filter,
+ timeout: Duration,
+ ) -> Result<Vec<Event>, nostr_sdk::client::Error> {
+ self.inner
+ .fetch_events(filter, timeout)
+ .await
+ .map(|events| events.to_vec())
+ }
+
+ pub(crate) async fn subscribe(
+ &self,
+ filter: Filter,
+ options: Option<nostr_sdk::SubscribeAutoCloseOptions>,
+ ) -> Result<nostr_sdk::prelude::Output<SubscriptionId>, nostr_sdk::client::Error> {
+ self.inner.subscribe(filter, options).await
+ }
+
+ pub(crate) async fn unsubscribe(&self, subscription_id: &SubscriptionId) {
+ self.inner.unsubscribe(subscription_id).await;
+ }
+
+ pub(crate) async fn send_event(
+ &self,
+ event: &Event,
+ ) -> Result<nostr_sdk::prelude::Output<nostr::EventId>, nostr_sdk::client::Error> {
+ self.inner.send_event(event).await
+ }
+}
+
+pub(crate) fn parse_public_key(value: &str) -> Result<PublicKey, radroots_nostr::Error> {
+ radroots_nostr::key::parse_public_key(value).and_then(radroots_nostr::key::public_key_to_nostr)
+}
+
+pub(crate) fn with_filter_tag(
+ filter: Filter,
+ tag: &str,
+ values: Vec<String>,
+) -> Result<Filter, radroots_nostr::Error> {
+ radroots_nostr::filter::with_tag(filter, tag, values)
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -3,6 +3,7 @@
pub mod app;
pub mod core;
+mod host_nostr;
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod transport;
diff --git a/src/transport/jsonrpc/auth.rs b/src/transport/jsonrpc/auth.rs
@@ -59,8 +59,8 @@ pub(crate) fn require_publish_principal(
#[cfg(test)]
mod tests {
use jsonrpsee::core::server::Extensions;
- use radroots_transport_publish_protocol::{
- NostrPublishTargetSourcePolicy, TransportPublishTargetPolicyName,
+ use radroots_protocol::radrootsd::transport_publish::v5::{
+ NostrTargetSourcePolicy, TargetPolicyName,
};
use super::{
@@ -81,11 +81,9 @@ mod tests {
token_hash: hash_bearer_token(token.as_str()),
allowed_pubkeys: vec!["a".repeat(64)],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
diff --git a/src/transport/jsonrpc/methods/mod.rs b/src/transport/jsonrpc/methods/mod.rs
@@ -24,10 +24,10 @@ pub fn register_all(
#[cfg(test)]
mod tests {
+ use crate::app::identity_storage::DaemonIdentity;
+ use crate::host_nostr::Metadata;
use jsonrpsee::server::RpcModule;
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::RadrootsNostrMetadata;
- use radroots_transport::RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE;
+ use radroots_protocol::radrootsd::transport_publish::v5::RETICULUM_UNAVAILABLE_MESSAGE as RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE;
use super::register_all;
use crate::app::config::{Nip46Config, TransportPublishConfig};
@@ -40,8 +40,8 @@ mod tests {
}
fn state(transport_publish_enabled: bool, nip46_public_jsonrpc_enabled: bool) -> Radrootsd {
- let identity = RadrootsIdentity::generate();
- let metadata: RadrootsNostrMetadata =
+ let identity = DaemonIdentity::generate();
+ let metadata: Metadata =
serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
let transport_publish = TransportPublishConfig {
enabled: transport_publish_enabled,
@@ -114,11 +114,11 @@ mod tests {
allowed_pubkeys: vec!["a".repeat(64)],
allowed_kinds: vec![30_402],
allowed_target_policies: vec![
- radroots_transport_publish_protocol::TransportPublishTargetPolicyName::Nostr,
+ radroots_protocol::radrootsd::transport_publish::v5::TargetPolicyName::Nostr,
],
allowed_explicit_transport_kinds: Vec::new(),
allowed_nostr_source_policies: vec![
- radroots_transport_publish_protocol::NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
+ radroots_protocol::radrootsd::transport_publish::v5::NostrTargetSourcePolicy::DaemonDefaultOnly,
],
allow_request_targets: false,
job_visibility: crate::core::transport_publish::PublishJobVisibility::Own,
diff --git a/src/transport/jsonrpc/methods/nip46/connect.rs b/src/transport/jsonrpc/methods/nip46/connect.rs
@@ -10,6 +10,10 @@ use uuid::Uuid;
use crate::core::nip46::session::{
Nip46Session, Nip46SessionAuthority, filter_perms, session_expires_at,
};
+use crate::host_nostr::{
+ DaemonNostrClient, Filter, Keys, Kind, PublicKey, RelayPoolNotification, SecretKey,
+ SubscriptionId, Timestamp, parse_public_key, with_filter_tag,
+};
use crate::transport::jsonrpc::nip46::connection::{
Nip46ConnectInfo, Nip46ConnectMode, parse_connect_url,
};
@@ -18,12 +22,6 @@ use crate::transport::jsonrpc::{MethodRegistry, RpcContext, RpcError};
use crate::transport::nostr::protocol::sign_nip46_message;
use nostr::JsonUtil;
use nostr::nips::{nip44, nip46::NostrConnectMessage, nip46::NostrConnectRequest};
-use radroots_nostr::prelude::{
- RadrootsNostrClient, RadrootsNostrFilter, RadrootsNostrKeys, RadrootsNostrKind,
- RadrootsNostrPublicKey, RadrootsNostrRelayPoolNotification, RadrootsNostrSecretKey,
- RadrootsNostrSubscriptionId, RadrootsNostrTimestamp, radroots_nostr_filter_tag,
- radroots_nostr_parse_pubkey,
-};
#[derive(Debug, Deserialize)]
struct Nip46ConnectParams {
@@ -94,12 +92,12 @@ async fn connect_bunker(
.remote_signer_pubkey
.as_ref()
.ok_or_else(|| RpcError::InvalidParams("missing remote signer pubkey".to_string()))?;
- let remote_signer_pubkey = radroots_nostr_parse_pubkey(remote_signer_raw)
+ let remote_signer_pubkey = parse_public_key(remote_signer_raw)
.map_err(|e| RpcError::InvalidParams(format!("invalid remote signer: {e}")))?;
- let client_keys = RadrootsNostrKeys::generate();
+ let client_keys = Keys::generate();
let client_pubkey = client_keys.public_key();
- let client = RadrootsNostrClient::new(client_keys.clone());
+ let client = DaemonNostrClient::with_keys(client_keys.clone());
add_relays(&client, &info.relays).await?;
client.connect().await;
@@ -113,11 +111,7 @@ async fn connect_bunker(
};
let message = NostrConnectMessage::request(&request);
let request_id = message.id().to_string();
- let filter = connect_response_filter(
- &remote_signer_pubkey,
- &client_pubkey,
- RadrootsNostrTimestamp::now(),
- )?;
+ let filter = connect_response_filter(&remote_signer_pubkey, &client_pubkey, Timestamp::now())?;
let notifications = client.clone().into_inner().notifications();
let subscription = client
.subscribe(filter, None)
@@ -195,15 +189,15 @@ async fn connect_nostrconnect(
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.ok_or_else(|| RpcError::InvalidParams("missing client_secret_key".to_string()))?;
- let client_secret_key = RadrootsNostrSecretKey::parse(&client_secret_key)
+ let client_secret_key = SecretKey::parse(&client_secret_key)
.map_err(|e| RpcError::InvalidParams(format!("invalid client_secret_key: {e}")))?;
- let client_keys = RadrootsNostrKeys::new(client_secret_key);
+ let client_keys = Keys::new(client_secret_key);
let client_pubkey = client_keys.public_key();
let client_pubkey_raw = info
.client_pubkey
.as_ref()
.ok_or_else(|| RpcError::InvalidParams("missing client pubkey".to_string()))?;
- let expected_pubkey = radroots_nostr_parse_pubkey(client_pubkey_raw)
+ let expected_pubkey = parse_public_key(client_pubkey_raw)
.map_err(|e| RpcError::InvalidParams(format!("invalid client pubkey: {e}")))?;
if expected_pubkey != client_pubkey {
return Err(RpcError::InvalidParams(
@@ -211,7 +205,7 @@ async fn connect_nostrconnect(
));
}
- let client = RadrootsNostrClient::new(client_keys.clone());
+ let client = DaemonNostrClient::with_keys(client_keys.clone());
add_relays(&client, &info.relays).await?;
client.connect().await;
client
@@ -257,7 +251,7 @@ async fn connect_nostrconnect(
})
}
-async fn add_relays(client: &RadrootsNostrClient, relays: &[String]) -> Result<(), RpcError> {
+async fn add_relays(client: &DaemonNostrClient, relays: &[String]) -> Result<(), RpcError> {
for relay in relays.iter() {
client
.add_relay(relay)
@@ -283,9 +277,9 @@ async fn claim_secret(ctx: &RpcContext, secret: Option<&str>) -> Result<(), RpcE
}
async fn send_connect_request(
- client: &RadrootsNostrClient,
- client_keys: &RadrootsNostrKeys,
- remote_signer_pubkey: &RadrootsNostrPublicKey,
+ client: &DaemonNostrClient,
+ client_keys: &Keys,
+ remote_signer_pubkey: &PublicKey,
message: NostrConnectMessage,
) -> Result<(), RpcError> {
let event = sign_nip46_message(client_keys, *remote_signer_pubkey, message)
@@ -298,25 +292,25 @@ async fn send_connect_request(
}
fn connect_response_filter(
- remote_signer_pubkey: &RadrootsNostrPublicKey,
- client_pubkey: &RadrootsNostrPublicKey,
- since: RadrootsNostrTimestamp,
-) -> Result<RadrootsNostrFilter, RpcError> {
- let filter = RadrootsNostrFilter::new()
- .kind(RadrootsNostrKind::NostrConnect)
+ remote_signer_pubkey: &PublicKey,
+ client_pubkey: &PublicKey,
+ since: Timestamp,
+) -> Result<Filter, RpcError> {
+ let filter = Filter::new()
+ .kind(Kind::NostrConnect)
.author(*remote_signer_pubkey)
.since(since);
- radroots_nostr_filter_tag(filter, "p", vec![client_pubkey.to_hex()])
+ with_filter_tag(filter, "p", vec![client_pubkey.to_hex()])
.map_err(|e| RpcError::Other(format!("nip46 connect filter failed: {e}")))
}
async fn wait_for_connect_response(
- client: &RadrootsNostrClient,
- client_keys: &RadrootsNostrKeys,
- remote_signer_pubkey: &RadrootsNostrPublicKey,
+ client: &DaemonNostrClient,
+ client_keys: &Keys,
+ remote_signer_pubkey: &PublicKey,
request_id: &str,
- mut notifications: broadcast::Receiver<RadrootsNostrRelayPoolNotification>,
- subscription_id: &RadrootsNostrSubscriptionId,
+ mut notifications: broadcast::Receiver<RelayPoolNotification>,
+ subscription_id: &SubscriptionId,
) -> Result<NostrConnectMessage, RpcError> {
let timeout = sleep(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
tokio::pin!(timeout);
@@ -336,11 +330,11 @@ async fn wait_for_connect_response(
return Err(RpcError::Other("nip46 connect notification closed".to_string()));
}
};
- let RadrootsNostrRelayPoolNotification::Event { event, .. } = notification else {
+ let RelayPoolNotification::Event { event, .. } = notification else {
continue;
};
let event = (*event).clone();
- if event.kind != RadrootsNostrKind::NostrConnect
+ if event.kind != Kind::NostrConnect
|| event.pubkey != *remote_signer_pubkey
{
continue;
@@ -427,15 +421,15 @@ fn validate_nostrconnect_response(
}
async fn wait_for_nostrconnect_response(
- client: &RadrootsNostrClient,
- client_keys: &RadrootsNostrKeys,
- client_pubkey: &RadrootsNostrPublicKey,
+ client: &DaemonNostrClient,
+ client_keys: &Keys,
+ client_pubkey: &PublicKey,
secret: &str,
-) -> Result<(RadrootsNostrPublicKey, NostrConnectMessage), RpcError> {
- let filter = RadrootsNostrFilter::new()
- .kind(RadrootsNostrKind::NostrConnect)
- .since(RadrootsNostrTimestamp::now());
- let filter = radroots_nostr_filter_tag(filter, "p", vec![client_pubkey.to_hex()])
+) -> Result<(PublicKey, NostrConnectMessage), RpcError> {
+ let filter = Filter::new()
+ .kind(Kind::NostrConnect)
+ .since(Timestamp::now());
+ let filter = with_filter_tag(filter, "p", vec![client_pubkey.to_hex()])
.map_err(|e| RpcError::Other(format!("nip46 connect filter failed: {e}")))?;
let mut notifications = client.clone().into_inner().notifications();
let subscription = client
@@ -459,11 +453,11 @@ async fn wait_for_nostrconnect_response(
return Err(RpcError::Other("nip46 connect notification closed".to_string()));
}
};
- let RadrootsNostrRelayPoolNotification::Event { event, .. } = notification else {
+ let RelayPoolNotification::Event { event, .. } = notification else {
continue;
};
let event = (*event).clone();
- if event.kind != RadrootsNostrKind::NostrConnect {
+ if event.kind != Kind::NostrConnect {
continue;
}
let decrypted = nip44::decrypt(
diff --git a/src/transport/jsonrpc/methods/nip46/get_public_key.rs b/src/transport/jsonrpc/methods/nip46/get_public_key.rs
@@ -43,7 +43,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
async fn request_get_public_key(
session: &Nip46Session,
-) -> Result<(radroots_nostr::prelude::RadrootsNostrPublicKey, bool), RpcError> {
+) -> Result<(crate::host_nostr::PublicKey, bool), RpcError> {
let req = NostrConnectRequest::GetPublicKey;
let response = client::request(session, req, "get_public_key").await?;
let response = response
diff --git a/src/transport/jsonrpc/methods/nip46/nip04.rs b/src/transport/jsonrpc/methods/nip46/nip04.rs
@@ -42,7 +42,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let session = session::get_session(ctx.as_ref(), &session_id).await?;
session::require_permission(&session, "nip04_encrypt")?;
- let public_key = radroots_nostr::prelude::radroots_nostr_parse_pubkey(&public_key)
+ let public_key = crate::host_nostr::parse_public_key(&public_key)
.map_err(|e| RpcError::InvalidParams(format!("invalid public_key: {e}")))?;
let req = NostrConnectRequest::Nip04Encrypt { public_key, text };
let response = client::request(&session, req, "nip04_encrypt").await?;
@@ -81,7 +81,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let session = session::get_session(ctx.as_ref(), &session_id).await?;
session::require_permission(&session, "nip04_decrypt")?;
- let public_key = radroots_nostr::prelude::radroots_nostr_parse_pubkey(&public_key)
+ let public_key = crate::host_nostr::parse_public_key(&public_key)
.map_err(|e| RpcError::InvalidParams(format!("invalid public_key: {e}")))?;
let req = NostrConnectRequest::Nip04Decrypt {
public_key,
diff --git a/src/transport/jsonrpc/methods/nip46/nip44.rs b/src/transport/jsonrpc/methods/nip46/nip44.rs
@@ -42,7 +42,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let session = session::get_session(ctx.as_ref(), &session_id).await?;
session::require_permission(&session, "nip44_encrypt")?;
- let public_key = radroots_nostr::prelude::radroots_nostr_parse_pubkey(&public_key)
+ let public_key = crate::host_nostr::parse_public_key(&public_key)
.map_err(|e| RpcError::InvalidParams(format!("invalid public_key: {e}")))?;
let req = NostrConnectRequest::Nip44Encrypt { public_key, text };
let response = client::request(&session, req, "nip44_encrypt").await?;
@@ -81,7 +81,7 @@ pub fn register(m: &mut RpcModule<RpcContext>, registry: &MethodRegistry) -> Res
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
let session = session::get_session(ctx.as_ref(), &session_id).await?;
session::require_permission(&session, "nip44_decrypt")?;
- let public_key = radroots_nostr::prelude::radroots_nostr_parse_pubkey(&public_key)
+ let public_key = crate::host_nostr::parse_public_key(&public_key)
.map_err(|e| RpcError::InvalidParams(format!("invalid public_key: {e}")))?;
let req = NostrConnectRequest::Nip44Decrypt {
public_key,
diff --git a/src/transport/jsonrpc/methods/transport_publish.rs b/src/transport/jsonrpc/methods/transport_publish.rs
@@ -1,8 +1,7 @@
use anyhow::Result;
use jsonrpsee::server::RpcModule;
-use radroots_transport_publish_protocol::{
- METHOD_CAPABILITIES, METHOD_EVENT, METHOD_JOB_GET, METHOD_JOB_LIST,
- TransportPublishCapabilities, TransportPublishEventRequest,
+use radroots_protocol::radrootsd::transport_publish::v5::{
+ Capabilities, EventRequest, METHOD_CAPABILITIES, METHOD_EVENT, METHOD_JOB_GET, METHOD_JOB_LIST,
};
use serde::Deserialize;
@@ -37,7 +36,7 @@ fn register_capabilities(
registry.track(METHOD_CAPABILITIES);
module.register_async_method(METHOD_CAPABILITIES, |_params, ctx, extensions| async move {
require_publish_principal(&extensions)?;
- Ok::<TransportPublishCapabilities, RpcError>(TransportPublishCapabilities::v5(
+ Ok::<Capabilities, RpcError>(Capabilities::v5(
ctx.state.transport_publish.config.max_event_bytes,
ctx.state.transport_publish.config.max_targets_per_request,
))
@@ -49,7 +48,7 @@ fn register_event(module: &mut RpcModule<RpcContext>, registry: &MethodRegistry)
registry.track(METHOD_EVENT);
module.register_async_method(METHOD_EVENT, |params, ctx, extensions| async move {
let principal = require_publish_principal(&extensions)?;
- let request: TransportPublishEventRequest = params
+ let request: EventRequest = params
.parse()
.map_err(|error| RpcError::InvalidParams(error.to_string()))?;
ctx.state
@@ -118,7 +117,6 @@ fn rpc_error_from_transport_publish(error: TransportPublishError) -> RpcError {
TransportPublishError::InvalidSignedEvent(message) => RpcError::InvalidParams(message),
TransportPublishError::EventWire(_)
| TransportPublishError::SignedEvent(_)
- | TransportPublishError::SignedEventSignature(_)
| TransportPublishError::Relay(_) => RpcError::InvalidParams(error.to_string()),
TransportPublishError::IdempotencyConflict(_) => RpcError::Other(error.to_string()),
other => RpcError::Other(other.to_string()),
@@ -131,30 +129,30 @@ mod tests {
use std::sync::Arc;
use crate::app::config::{Nip46Config, TransportPublishConfig, TransportPublishNostrConfig};
+ use crate::app::identity_storage::DaemonIdentity;
use crate::core::Radrootsd;
use crate::core::transport_publish::{
PublishJobVisibility, PublishPrincipalInit, generate_bearer_token, hash_bearer_token,
};
+ use crate::host_nostr::{Metadata, Timestamp};
use crate::transport::jsonrpc::auth::{
TransportPublishAuthorization, authorize_transport_publish_request,
};
use crate::transport::jsonrpc::{MethodRegistry, RpcContext};
+ use crate::transport::relay_publish::MockRelayPublishAdapter as RadrootsMockRelayPublishAdapter;
use jsonrpsee::server::RpcModule;
use nostr::JsonUtil;
use nostr::{EventBuilder, Kind, Tag};
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::{RadrootsNostrMetadata, RadrootsNostrTimestamp};
- use radroots_transport_nostr::RadrootsMockRelayPublishAdapter;
- use radroots_transport_publish_protocol::{
- NostrPublishTargetSourcePolicy, TransportPublishTargetPolicyName,
+ use radroots_protocol::radrootsd::transport_publish::v5::{
+ NostrTargetSourcePolicy, TargetPolicyName,
};
- fn signed_event(identity: &RadrootsIdentity) -> String {
+ fn signed_event(identity: &DaemonIdentity) -> String {
// This method accepts an already-signed wire event; construct the test
// fixture at that explicit low-level interoperability boundary.
let event = EventBuilder::new(Kind::Custom(30_402), "{}")
.tag(Tag::identifier("listing-1"))
- .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
+ .custom_created_at(Timestamp::from_secs(1_700_000_000))
.sign_with_keys(identity.keys())
.expect("signed event");
event.as_json()
@@ -164,9 +162,9 @@ mod tests {
admin: bool,
transport_publish_config: TransportPublishConfig,
) -> (RpcModule<RpcContext>, RpcContext, String, String) {
- let identity = RadrootsIdentity::generate();
+ let identity = DaemonIdentity::generate();
let signed_event = signed_event(&identity);
- let metadata: RadrootsNostrMetadata =
+ let metadata: Metadata =
serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
let state = Radrootsd::new(
identity.clone(),
@@ -189,11 +187,9 @@ mod tests {
token_hash: hash_bearer_token(token.as_str()),
allowed_pubkeys: vec![identity.public_key_hex()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: if admin {
PublishJobVisibility::Admin
@@ -257,7 +253,7 @@ mod tests {
#[tokio::test]
async fn publish_event_rejects_principal_scope_gap() {
let (module, _ctx, _token, _pubkey) = module_with_principal(false);
- let other_identity = RadrootsIdentity::generate();
+ let other_identity = DaemonIdentity::generate();
let event = signed_event(&other_identity);
let request = format!(
r#"{{
diff --git a/src/transport/jsonrpc/nip46/client.rs b/src/transport/jsonrpc/nip46/client.rs
@@ -3,6 +3,9 @@
use std::time::Duration;
use crate::core::nip46::session::Nip46Session;
+use crate::host_nostr::{
+ Filter, Kind, RelayPoolNotification, SubscriptionId, Timestamp, with_filter_tag,
+};
use crate::transport::jsonrpc::{RpcError, params::DEFAULT_TIMEOUT_SECS};
use crate::transport::nostr::protocol::sign_nip46_message;
use nostr::JsonUtil;
@@ -11,10 +14,6 @@ use nostr::nips::{
nip44,
nip46::{NostrConnectMessage, NostrConnectMethod, NostrConnectRequest, ResponseResult},
};
-use radroots_nostr::prelude::{
- RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrRelayPoolNotification,
- RadrootsNostrSubscriptionId, RadrootsNostrTimestamp, radroots_nostr_filter_tag,
-};
use tokio::sync::broadcast;
use tokio::time::sleep;
@@ -64,7 +63,7 @@ pub async fn request(
let message = NostrConnectMessage::request(&request);
let request_id = message.id().to_string();
- let filter = response_filter(session, RadrootsNostrTimestamp::now(), label)?;
+ let filter = response_filter(session, Timestamp::now(), label)?;
let notifications = session.client.clone().into_inner().notifications();
let subscription = session
.client
@@ -118,14 +117,14 @@ fn validate_signed_event_response(
fn response_filter(
session: &Nip46Session,
- since: RadrootsNostrTimestamp,
+ since: Timestamp,
label: &str,
-) -> Result<RadrootsNostrFilter, RpcError> {
- let filter = RadrootsNostrFilter::new()
- .kind(RadrootsNostrKind::NostrConnect)
+) -> Result<Filter, RpcError> {
+ let filter = Filter::new()
+ .kind(Kind::NostrConnect)
.author(session.remote_signer_pubkey)
.since(since);
- radroots_nostr_filter_tag(filter, "p", vec![session.client_pubkey.to_hex()])
+ with_filter_tag(filter, "p", vec![session.client_pubkey.to_hex()])
.map_err(|e| RpcError::Other(format!("nip46 {label} failed: {e}")))
}
@@ -133,8 +132,8 @@ async fn wait_for_response(
session: &Nip46Session,
request_id: &str,
label: &str,
- mut notifications: broadcast::Receiver<RadrootsNostrRelayPoolNotification>,
- subscription_id: &RadrootsNostrSubscriptionId,
+ mut notifications: broadcast::Receiver<RelayPoolNotification>,
+ subscription_id: &SubscriptionId,
) -> Result<NostrConnectMessage, RpcError> {
let timeout = sleep(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
tokio::pin!(timeout);
@@ -154,11 +153,11 @@ async fn wait_for_response(
return Err(RpcError::Other(format!("nip46 {label} notification closed")));
}
};
- let RadrootsNostrRelayPoolNotification::Event { event, .. } = notification else {
+ let RelayPoolNotification::Event { event, .. } = notification else {
continue;
};
let event = (*event).clone();
- if event.kind != RadrootsNostrKind::NostrConnect
+ if event.kind != Kind::NostrConnect
|| event.pubkey != session.remote_signer_pubkey
{
continue;
@@ -182,13 +181,13 @@ async fn wait_for_response(
#[cfg(test)]
mod tests {
+ use crate::host_nostr::Keys;
use nostr::{EventBuilder, EventId, Kind, Timestamp};
- use radroots_nostr::prelude::RadrootsNostrKeys;
use super::validate_signed_event_response;
fn signed_fixture() -> (nostr::UnsignedEvent, nostr::Event) {
- let keys = RadrootsNostrKeys::generate();
+ let keys = Keys::generate();
let unsigned = EventBuilder::new(Kind::Custom(30_001), "checked")
.custom_created_at(Timestamp::from_secs(1_784_347_200))
.build(keys.public_key());
diff --git a/src/transport/jsonrpc/nip46/connection.rs b/src/transport/jsonrpc/nip46/connection.rs
@@ -1,4 +1,4 @@
-use radroots_nostr::prelude::radroots_nostr_parse_pubkey;
+use crate::host_nostr::parse_public_key;
use serde::{Deserialize, Serialize};
use url::Url;
@@ -74,7 +74,7 @@ fn parse_nostrconnect_url(url: &Url) -> Result<Nip46ConnectInfo, RpcError> {
.host_str()
.map(|host| host.to_string())
.ok_or_else(|| RpcError::InvalidParams("missing client pubkey".to_string()))?;
- radroots_nostr_parse_pubkey(&client_pubkey)
+ parse_public_key(&client_pubkey)
.map_err(|e| RpcError::InvalidParams(format!("invalid client pubkey: {e}")))?;
let query: Nip46ConnectQuery = serde_qs::from_str(url.query().unwrap_or_default())
.map_err(|e| RpcError::InvalidParams(e.to_string()))?;
diff --git a/src/transport/jsonrpc/server.rs b/src/transport/jsonrpc/server.rs
@@ -125,21 +125,21 @@ mod tests {
Nip46Config, NostrRelayUrlPolicy, RpcConfig, TransportPublishConfig,
TransportPublishNostrConfig,
};
+ use crate::app::identity_storage::DaemonIdentity;
use crate::core::Radrootsd;
use crate::core::transport_publish::{
PublishJobVisibility, PublishPrincipalInit, PublishRelayResolveFuture,
PublishRelayResolver, generate_bearer_token, hash_bearer_token,
};
+ use crate::host_nostr::{Metadata, Timestamp};
use crate::transport::jsonrpc::methods;
use crate::transport::jsonrpc::{MethodRegistry, RpcContext};
+ use crate::transport::relay_publish::MockRelayPublishAdapter as RadrootsMockRelayPublishAdapter;
use jsonrpsee::server::RpcModule;
use nostr::JsonUtil;
use nostr::{EventBuilder, Kind, Tag};
- use radroots_identity::RadrootsIdentity;
- use radroots_nostr::prelude::{RadrootsNostrMetadata, RadrootsNostrTimestamp};
- use radroots_transport_nostr::RadrootsMockRelayPublishAdapter;
- use radroots_transport_publish_protocol::{
- NostrPublishTargetSourcePolicy, TransportPublishTargetPolicyName,
+ use radroots_protocol::radrootsd::transport_publish::v5::{
+ NostrTargetSourcePolicy, TargetPolicyName,
};
use serde_json::Value;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener};
@@ -154,11 +154,11 @@ mod tests {
listener.local_addr().expect("local addr")
}
- fn signed_event_json(identity: &RadrootsIdentity) -> String {
+ fn signed_event_json(identity: &DaemonIdentity) -> String {
// The JSON-RPC server consumes an already-signed transport fixture.
EventBuilder::new(Kind::Custom(30_402), "{}")
.tag(Tag::identifier("listing-1"))
- .custom_created_at(RadrootsNostrTimestamp::from_secs(1_700_000_000))
+ .custom_created_at(Timestamp::from_secs(1_700_000_000))
.sign_with_keys(identity.keys())
.expect("signed event")
.as_json()
@@ -188,11 +188,11 @@ mod tests {
) -> (
Radrootsd,
String,
- RadrootsIdentity,
+ DaemonIdentity,
RadrootsMockRelayPublishAdapter,
) {
- let identity = RadrootsIdentity::generate();
- let metadata: RadrootsNostrMetadata =
+ let identity = DaemonIdentity::generate();
+ let metadata: Metadata =
serde_json::from_str(r#"{"name":"radrootsd-test"}"#).expect("metadata");
let mut state = Radrootsd::new(
identity.clone(),
@@ -216,11 +216,9 @@ mod tests {
token_hash: hash_bearer_token(token.as_str()),
allowed_pubkeys: vec![identity.public_key_hex()],
allowed_kinds: vec![30_402],
- allowed_target_policies: vec![TransportPublishTargetPolicyName::Nostr],
+ allowed_target_policies: vec![TargetPolicyName::Nostr],
allowed_explicit_transport_kinds: Vec::new(),
- allowed_nostr_source_policies: vec![
- NostrPublishTargetSourcePolicy::DaemonDefaultOnly,
- ],
+ allowed_nostr_source_policies: vec![NostrTargetSourcePolicy::DaemonDefaultOnly],
allow_request_targets: false,
job_visibility: PublishJobVisibility::Own,
expires_at_unix: None,
@@ -232,7 +230,7 @@ mod tests {
fn publish_server_state() -> (
Radrootsd,
String,
- RadrootsIdentity,
+ DaemonIdentity,
RadrootsMockRelayPublishAdapter,
) {
publish_server_state_with_config(
@@ -263,7 +261,7 @@ mod tests {
impl PublishRelayResolver for StaticPublishRelayResolver {
fn resolve<'a>(
&'a self,
- _url: &'a radroots_transport_nostr::RadrootsRelayUrl,
+ _url: &'a crate::transport::relay_publish::RelayUrl,
) -> PublishRelayResolveFuture<'a> {
Box::pin(async move { Ok(self.addresses.clone()) })
}
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
@@ -1,2 +1,3 @@
pub mod jsonrpc;
pub mod nostr;
+pub(crate) mod relay_publish;
diff --git a/src/transport/nostr/listener.rs b/src/transport/nostr/listener.rs
@@ -14,11 +14,8 @@ use crate::core::nip46::session::{
Nip46Session, PendingNostrRequest, session_expires_at, sign_event_allowed,
};
use crate::core::state::Radrootsd;
+use crate::host_nostr::{Filter, Kind, RelayPoolNotification, Timestamp, with_filter_tag};
use crate::transport::nostr::protocol::sign_nip46_message;
-use radroots_nostr::prelude::{
- RadrootsNostrFilter, RadrootsNostrKind, RadrootsNostrRelayPoolNotification,
- RadrootsNostrTimestamp, radroots_nostr_filter_tag,
-};
const DEFAULT_TIMEOUT_SECS: u64 = 10;
@@ -37,10 +34,10 @@ async fn run_nip46_listener(radrootsd: Radrootsd) -> Result<()> {
.wait_for_connection(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.await;
- let filter = RadrootsNostrFilter::new()
- .kind(RadrootsNostrKind::NostrConnect)
- .since(RadrootsNostrTimestamp::now());
- let filter = radroots_nostr_filter_tag(filter, "p", vec![radrootsd.pubkey.to_hex()])?;
+ let filter = Filter::new()
+ .kind(Kind::NostrConnect)
+ .since(Timestamp::now());
+ let filter = with_filter_tag(filter, "p", vec![radrootsd.pubkey.to_hex()])?;
let mut notifications = radrootsd.client.clone().into_inner().notifications();
let subscription = radrootsd.client.subscribe(filter, None).await?;
@@ -54,11 +51,11 @@ async fn run_nip46_listener(radrootsd: Radrootsd) -> Result<()> {
return Err(anyhow!("nip46 listener notification closed"));
}
};
- let RadrootsNostrRelayPoolNotification::Event { event, .. } = notification else {
+ let RelayPoolNotification::Event { event, .. } = notification else {
continue;
};
let event = (*event).clone();
- if event.kind != RadrootsNostrKind::NostrConnect {
+ if event.kind != Kind::NostrConnect {
continue;
}
@@ -99,7 +96,7 @@ async fn run_nip46_listener(radrootsd: Radrootsd) -> Result<()> {
pub(crate) async fn handle_request(
radrootsd: &Radrootsd,
- client_pubkey: &radroots_nostr::prelude::RadrootsNostrPublicKey,
+ client_pubkey: &crate::host_nostr::PublicKey,
request_id: &str,
request: NostrConnectRequest,
) -> NostrConnectResponse {
@@ -317,7 +314,7 @@ pub(crate) async fn handle_request(
async fn session_for_client(
radrootsd: &Radrootsd,
- client_pubkey: &radroots_nostr::prelude::RadrootsNostrPublicKey,
+ client_pubkey: &crate::host_nostr::PublicKey,
) -> Result<Nip46Session, NostrConnectResponse> {
let session_id = client_pubkey.to_hex();
match radrootsd.nip46_sessions.get(&session_id).await {
@@ -338,7 +335,7 @@ async fn auth_challenge(
radrootsd: &Radrootsd,
session: &Nip46Session,
request_id: &str,
- client_pubkey: &radroots_nostr::prelude::RadrootsNostrPublicKey,
+ client_pubkey: &crate::host_nostr::PublicKey,
request: NostrConnectRequest,
) -> Option<NostrConnectResponse> {
if !session.auth_required || session.authorized {
diff --git a/src/transport/nostr/protocol.rs b/src/transport/nostr/protocol.rs
@@ -1,8 +1,8 @@
#![forbid(unsafe_code)]
+use crate::host_nostr::{Keys, PublicKey};
use nostr::nips::nip46::NostrConnectMessage;
use nostr::{Event, EventBuilder};
-use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrPublicKey};
/// Encrypts and locally signs a NIP-46 transport message.
///
@@ -10,8 +10,8 @@ use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrPublicKey};
/// kind 24133 is protocol traffic, not a generic Radroots product-authoring
/// surface.
pub(crate) fn sign_nip46_message(
- sender_keys: &RadrootsNostrKeys,
- receiver_pubkey: RadrootsNostrPublicKey,
+ sender_keys: &Keys,
+ receiver_pubkey: PublicKey,
message: NostrConnectMessage,
) -> Result<Event, nostr::event::builder::Error> {
EventBuilder::nostr_connect(sender_keys, receiver_pubkey, message)?.sign_with_keys(sender_keys)
@@ -19,23 +19,23 @@ pub(crate) fn sign_nip46_message(
#[cfg(test)]
mod tests {
+ use crate::host_nostr::{Keys, Kind};
use nostr::JsonUtil;
use nostr::nips::{nip44, nip46::NostrConnectRequest};
- use radroots_nostr::prelude::{RadrootsNostrKeys, RadrootsNostrKind};
use super::sign_nip46_message;
#[test]
fn signed_nip46_message_is_bound_to_sender_and_receiver() {
- let sender = RadrootsNostrKeys::generate();
- let receiver = RadrootsNostrKeys::generate();
+ let sender = Keys::generate();
+ let receiver = Keys::generate();
let message = nostr::nips::nip46::NostrConnectMessage::request(&NostrConnectRequest::Ping);
let request_id = message.id().to_owned();
let event = sign_nip46_message(&sender, receiver.public_key(), message)
.expect("signed NIP-46 message");
- assert_eq!(event.kind, RadrootsNostrKind::NostrConnect);
+ assert_eq!(event.kind, Kind::NostrConnect);
assert_eq!(event.pubkey, sender.public_key());
assert!(
event
diff --git a/src/transport/relay_publish.rs b/src/transport/relay_publish.rs
@@ -0,0 +1,333 @@
+//! Daemon-owned adaptation from the V5 publish RPC to bounded Nostr attempts.
+
+use core::future::Future;
+use core::pin::Pin;
+use core::time::Duration;
+#[cfg(test)]
+use std::collections::BTreeMap;
+use std::net::IpAddr;
+#[cfg(test)]
+use std::sync::{Arc, Mutex};
+
+use nostr::JsonUtil;
+use radroots_event::SignedEvent;
+use radroots_transport::RadrootsTransportSatisfactionPolicy;
+
+use crate::host_nostr::DaemonNostrClient;
+
+pub(crate) type PublishFuture<'a> =
+ Pin<Box<dyn Future<Output = Result<Vec<RelayPublishReceipt>, RelayPublishError>> + Send + 'a>>;
+
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub(crate) enum RelayUrlPolicy {
+ Public,
+ Localhost,
+}
+
+impl RelayUrlPolicy {
+ const fn native(self) -> radroots_transport_nostr::RelayUrlPolicy {
+ match self {
+ Self::Public => radroots_transport_nostr::RelayUrlPolicy::Public,
+ Self::Localhost => radroots_transport_nostr::RelayUrlPolicy::Local,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub(crate) struct RelayUrl {
+ inner: radroots_transport_nostr::RelayUrl,
+ policy: RelayUrlPolicy,
+}
+
+impl RelayUrl {
+ pub(crate) fn parse(
+ value: impl AsRef<str>,
+ policy: RelayUrlPolicy,
+ ) -> Result<Self, RelayPublishError> {
+ let inner = radroots_transport_nostr::RelayUrl::parse(value, policy.native())
+ .map_err(|error| RelayPublishError(error.to_string()))?;
+ Ok(Self { inner, policy })
+ }
+
+ pub(crate) fn as_str(&self) -> &str {
+ self.inner.as_str()
+ }
+
+ pub(crate) fn validate_public_resolved_ip_addrs(
+ &self,
+ addresses: impl IntoIterator<Item = IpAddr>,
+ ) -> Result<(), RelayPublishError> {
+ self.inner
+ .validate_resolved_addresses(self.policy.native(), addresses)
+ .map_err(|error| RelayPublishError(error.to_string()))
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct RelayTargetSet {
+ relays: Vec<RelayUrl>,
+}
+
+impl RelayTargetSet {
+ pub(crate) fn from_urls(mut relays: Vec<RelayUrl>) -> Result<Self, RelayPublishError> {
+ if relays.is_empty() {
+ return Err(RelayPublishError(
+ "relay target set must not be empty".to_owned(),
+ ));
+ }
+ relays.sort();
+ relays.dedup();
+ Ok(Self { relays })
+ }
+
+ pub(crate) fn relays(&self) -> &[RelayUrl] {
+ self.relays.as_slice()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct RelayPublishRequest {
+ signed_event: SignedEvent,
+ targets: RelayTargetSet,
+ satisfaction_policy: RadrootsTransportSatisfactionPolicy,
+ now_ms: i64,
+}
+
+impl RelayPublishRequest {
+ pub(crate) fn new(signed_event: SignedEvent, targets: RelayTargetSet, now_ms: i64) -> Self {
+ Self {
+ signed_event,
+ targets,
+ satisfaction_policy: RadrootsTransportSatisfactionPolicy::all_accepted(),
+ now_ms,
+ }
+ }
+
+ pub(crate) fn with_satisfaction_policy(
+ mut self,
+ satisfaction_policy: RadrootsTransportSatisfactionPolicy,
+ ) -> Self {
+ self.satisfaction_policy = satisfaction_policy;
+ self
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub(crate) enum RelayOutcomeKind {
+ Accepted,
+ DuplicateAccepted,
+ Blocked,
+ RateLimited,
+ Invalid,
+ PowRequired,
+ Restricted,
+ AuthRequired,
+ Muted,
+ Unsupported,
+ PaymentRequired,
+ Error,
+ Timeout,
+ ConnectionFailed,
+ Unknown,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) struct RelayOutcome {
+ pub(crate) kind: RelayOutcomeKind,
+ pub(crate) message: Option<String>,
+}
+
+impl RelayOutcome {
+ pub(crate) const fn accepted() -> Self {
+ Self {
+ kind: RelayOutcomeKind::Accepted,
+ message: None,
+ }
+ }
+
+ pub(crate) fn classify(message: impl AsRef<str>) -> Self {
+ let message = message.as_ref().trim().to_owned();
+ let lower = message.to_ascii_lowercase();
+ let kind = if lower.starts_with("duplicate:") {
+ RelayOutcomeKind::DuplicateAccepted
+ } else if lower.starts_with("blocked:") {
+ RelayOutcomeKind::Blocked
+ } else if lower.starts_with("rate-limited:") || lower.contains("rate limit") {
+ RelayOutcomeKind::RateLimited
+ } else if lower.starts_with("invalid:") {
+ RelayOutcomeKind::Invalid
+ } else if lower.starts_with("pow:") {
+ RelayOutcomeKind::PowRequired
+ } else if lower.starts_with("restricted:") {
+ RelayOutcomeKind::Restricted
+ } else if lower.starts_with("auth-required:") || lower.contains("auth required") {
+ RelayOutcomeKind::AuthRequired
+ } else if lower.starts_with("mute:") {
+ RelayOutcomeKind::Muted
+ } else if lower.starts_with("unsupported:") {
+ RelayOutcomeKind::Unsupported
+ } else if lower.starts_with("payment-required:") {
+ RelayOutcomeKind::PaymentRequired
+ } else if lower.starts_with("timeout:") || lower.contains("timeout") {
+ RelayOutcomeKind::Timeout
+ } else if lower.starts_with("error:") {
+ RelayOutcomeKind::Error
+ } else {
+ RelayOutcomeKind::Unknown
+ };
+ Self {
+ kind,
+ message: Some(message),
+ }
+ }
+
+ pub(crate) fn timeout(message: impl Into<String>) -> Self {
+ Self {
+ kind: RelayOutcomeKind::Timeout,
+ message: Some(message.into()),
+ }
+ }
+
+ pub(crate) fn connection_failed(message: impl Into<String>) -> Self {
+ Self {
+ kind: RelayOutcomeKind::ConnectionFailed,
+ message: Some(message.into()),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct RelayPublishReceipt {
+ pub(crate) relay_url: String,
+ pub(crate) outcome: RelayOutcome,
+ pub(crate) attempted: bool,
+}
+
+impl RelayPublishReceipt {
+ pub(crate) fn attempted(relay_url: impl Into<String>, outcome: RelayOutcome) -> Self {
+ Self {
+ relay_url: relay_url.into(),
+ outcome,
+ attempted: true,
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("{0}")]
+pub(crate) struct RelayPublishError(pub(crate) String);
+
+pub(crate) trait RelayPublishAdapter: Send + Sync {
+ fn publish(&self, request: RelayPublishRequest) -> PublishFuture<'_>;
+}
+
+#[derive(Clone)]
+pub(crate) struct LiveRelayPublishAdapter {
+ client: DaemonNostrClient,
+}
+
+impl LiveRelayPublishAdapter {
+ pub(crate) const fn new(client: DaemonNostrClient) -> Self {
+ Self { client }
+ }
+}
+
+impl RelayPublishAdapter for LiveRelayPublishAdapter {
+ fn publish(&self, request: RelayPublishRequest) -> PublishFuture<'_> {
+ Box::pin(async move {
+ if request.now_ms < 0 {
+ return Err(RelayPublishError(
+ "publish timestamp must be nonnegative".to_owned(),
+ ));
+ }
+ let event = nostr::Event::from_json(request.signed_event.raw_json())
+ .map_err(|error| RelayPublishError(error.to_string()))?;
+ let client = self.client.clone().into_inner();
+ let mut receipts = Vec::with_capacity(request.targets.relays().len());
+ for relay in request.targets.relays() {
+ let relay_url = relay.as_str().to_owned();
+ let attempt = async {
+ client.add_relay(relay_url.as_str()).await?;
+ client
+ .try_connect_relay(relay_url.as_str(), Duration::from_secs(10))
+ .await?;
+ client.send_event_to([relay_url.as_str()], &event).await
+ };
+ let outcome = match attempt.await {
+ Ok(output)
+ if output.success.iter().any(|url| {
+ url.to_string().trim_end_matches('/') == relay_url.trim_end_matches('/')
+ }) =>
+ {
+ RelayOutcome::accepted()
+ }
+ Ok(output) => output
+ .failed
+ .values()
+ .next()
+ .map(RelayOutcome::classify)
+ .unwrap_or_else(|| RelayOutcome::connection_failed("relay omitted result")),
+ Err(error) => RelayOutcome::connection_failed(error.to_string()),
+ };
+ receipts.push(RelayPublishReceipt::attempted(relay_url, outcome));
+ }
+ Ok(receipts)
+ })
+ }
+}
+
+#[cfg(test)]
+#[derive(Clone, Default)]
+pub(crate) struct MockRelayPublishAdapter {
+ outcomes: BTreeMap<String, RelayOutcome>,
+ captured_raw_events: Arc<Mutex<Vec<String>>>,
+}
+
+#[cfg(test)]
+impl MockRelayPublishAdapter {
+ pub(crate) fn new() -> Self {
+ Self::default()
+ }
+
+ pub(crate) fn with_outcome(
+ mut self,
+ relay_url: impl Into<String>,
+ outcome: RelayOutcome,
+ ) -> Self {
+ self.outcomes.insert(relay_url.into(), outcome);
+ self
+ }
+
+ pub(crate) fn captured_raw_events(&self) -> Vec<String> {
+ self.captured_raw_events
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner)
+ .clone()
+ }
+}
+
+#[cfg(test)]
+impl RelayPublishAdapter for MockRelayPublishAdapter {
+ fn publish(&self, request: RelayPublishRequest) -> PublishFuture<'_> {
+ Box::pin(async move {
+ self.captured_raw_events
+ .lock()
+ .map_err(|_| RelayPublishError("captured event lock poisoned".to_owned()))?
+ .push(request.signed_event.raw_json().to_owned());
+ Ok(request
+ .targets
+ .relays()
+ .iter()
+ .map(|relay| {
+ RelayPublishReceipt::attempted(
+ relay.as_str(),
+ self.outcomes
+ .get(relay.as_str())
+ .cloned()
+ .unwrap_or_else(RelayOutcome::accepted),
+ )
+ })
+ .collect())
+ })
+ }
+}
diff --git a/tests/source_boundary.rs b/tests/source_boundary.rs
@@ -263,13 +263,12 @@ fn transport_publish_sources_reject_removed_execution_transport_targets() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let protocol_source = read_source(
manifest_dir
- .join("../lib/crates/transport_publish_protocol/src/lib.rs")
+ .join("../lib/crates/protocol/src/radrootsd/transport_publish/v5.rs")
.as_path(),
);
for required in [
- "TransportPublishProtocolError::InvalidTransportKind { index }",
- "RadrootsTransportKind::parse_canonical(self.transport_kind.as_str())",
- "transport_kind_error(error, index)",
+ "\"local\" | \"nostr\" | \"reticulum\" => {}",
+ "_ => return Err(Error::InvalidTransportKind { index })",
] {
assert!(
protocol_source.contains(required),
@@ -281,6 +280,7 @@ fn transport_publish_sources_reject_removed_execution_transport_targets() {
for required in [
"publish_event_rejects_removed_execution_kind_before_recording_job",
"publish_event_rejects_removed_execution_target_before_recording_job",
+ "!matches!(canonical.as_str(), \"local\" | \"nostr\" | \"reticulum\")",
] {
assert!(
daemon_source.contains(required),
@@ -371,16 +371,17 @@ fn transport_publish_required_targets_stay_fingerprint_exact() {
let daemon_source = read_source(manifest_dir.join("src/core/transport_publish.rs").as_path());
let protocol_source = read_source(
manifest_dir
- .join("../lib/crates/transport_publish_protocol/src/lib.rs")
+ .join("../lib/crates/protocol/src/radrootsd/transport_publish/v5.rs")
.as_path(),
);
for required in [
"validate_delivery_policy_for_resolution",
- "delivery_policy\n .validate_target_membership(target_fingerprints.as_slice())",
+ "let target_fingerprints = resolution.target_fingerprints()?;",
+ ".any(|actual| actual.as_str() == required.as_str())",
"fn required_outcomes_for_policy",
"target_outcome_fingerprint(outcome, index)",
- "fingerprint == required",
+ "fingerprint.as_str() == required.as_str()",
"let required_outcomes = required_outcomes_for_policy(targets, outcomes);",
"required_outcomes.len() == targets.len()",
] {
@@ -392,13 +393,13 @@ fn transport_publish_required_targets_stay_fingerprint_exact() {
let satisfaction_arm = source_window(
daemon_source.as_str(),
- "TransportPublishDeliveryPolicy::RequiredTargets { targets } => {",
+ "DeliveryPolicy::RequiredTargets { targets } => {",
"fn delivery_status(",
);
for required in [
"let nostr_required_targets = targets",
"target\n .fingerprint()",
- "filter(|fingerprint| fingerprint == required)",
+ "filter(|fingerprint| fingerprint.as_str() == required.as_str())",
"RadrootsTransportSatisfactionPolicy::required_targets(",
] {
assert!(
@@ -418,8 +419,8 @@ fn transport_publish_required_targets_stay_fingerprint_exact() {
}
for required in [
- "pub fn validate_target_membership",
- "TransportPublishProtocolError::RequiredTargetNotInTargetSet { index }",
+ "RequiredTargetNotInTargetSet",
+ "Matching fingerprints to native targets is intentionally deferred",
"Self::RequiredTargets { targets } => targets.len()",
] {
assert!(
@@ -462,22 +463,22 @@ fn transport_publish_capabilities_expose_per_transport_readiness() {
let protocol_source = read_source(
Path::new(env!("CARGO_MANIFEST_DIR"))
- .join("../lib/crates/transport_publish_protocol/src/lib.rs")
+ .join("../lib/crates/protocol/src/radrootsd/transport_publish/v5.rs")
.as_path(),
);
for required in [
"pub transport: String,",
"pub configured: bool,",
- "pub implementation: TransportPublishImplementation,",
+ "pub implementation: Implementation,",
"pub usable_for_delivery: bool,",
- "pub capabilities: TransportPublishOperationCapabilities,",
- "pub struct TransportPublishOperationCapabilities",
+ "pub capabilities: OperationCapabilities,",
+ "pub struct OperationCapabilities",
"pub deliver: bool,",
"pub fetch: bool,",
"pub discovery: bool,",
"pub gateway_forwarding: bool,",
"pub receipt_observation: bool,",
- "RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE",
+ "RETICULUM_UNAVAILABLE_MESSAGE",
] {
assert!(
protocol_source.contains(required),
@@ -486,15 +487,12 @@ fn transport_publish_capabilities_expose_per_transport_readiness() {
}
let capability_source = source_window(
protocol_source.as_str(),
- "pub struct TransportPublishTransportCapability",
- "#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(rename_all = \"snake_case\"))]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum TransportPublishDeliveryPolicyName",
+ "pub struct TransportCapability",
+ "#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]\n#[cfg_attr(feature = \"serde\", serde(rename_all = \"snake_case\"))]\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\npub enum DeliveryPolicyName",
);
for forbidden in [
"pub transport_kind: String,",
- concat!(
- "pub implementation",
- "_state: TransportPublishImplementationState,"
- ),
+ concat!("pub implementation", "_state: ImplementationState,"),
] {
assert!(
!capability_source.contains(forbidden),
@@ -710,12 +708,15 @@ fn nip46_transport_uses_completed_events_and_exact_sign_event_binding() {
"src/transport/jsonrpc/methods/nip46/connect.rs",
"src/transport/jsonrpc/methods/nip46/session_authorize.rs",
]
- .map(|path| read_source(manifest_dir.join(path).as_path()))
+ .map(|path| {
+ let source = read_source(manifest_dir.join(path).as_path());
+ production_source(source.as_str()).to_owned()
+ })
.join("\n");
assert!(protocol.contains("EventBuilder::nostr_connect"));
assert!(protocol.contains(".sign_with_keys(sender_keys)"));
- assert!(!production_sources.contains("RadrootsNostrEventBuilder"));
+ assert!(!production_sources.contains("EventBuilder"));
assert!(!production_sources.contains("radroots_nostr_build_event"));
assert!(!production_sources.contains(".send_event_builder("));
for required in [