commit f77c592056b6d85e403b8232fbc3d552bce20c05
parent 68e5e33368f4afd6aab1309dfb2b6446c0be92d9
Author: triesap <tyson@radroots.org>
Date: Sat, 21 Mar 2026 22:18:26 +0000
app: add async transport bootstrap
- add repo-local transport config for enablement, relay lists, and connection timeout policy
- prepare an async runtime and Nostr client bootstrap path backed by local radroots-nostr client support
- capture transport readiness in startup snapshots so the relay-facing listener can land on stable runtime state
- validate with cargo fmt, cargo check, cargo test, cargo check --locked, cargo test --locked, and cargo fmt --check
Diffstat:
8 files changed, 801 insertions(+), 20 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -13,6 +13,17 @@ dependencies = [
]
[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -69,6 +80,43 @@ dependencies = [
]
[[package]]
+name = "async-utility"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151"
+dependencies = [
+ "futures-util",
+ "gloo-timers",
+ "tokio",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "async-wsocket"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c92385c7c8b3eb2de1b78aeca225212e4c9a69a78b802832759b108681a5069"
+dependencies = [
+ "async-utility",
+ "futures",
+ "futures-util",
+ "js-sys",
+ "tokio",
+ "tokio-rustls",
+ "tokio-socks",
+ "tokio-tungstenite",
+ "url",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "atomic-destructor"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4"
+
+[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -160,6 +208,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -304,11 +358,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
- "rand_core",
+ "rand_core 0.6.4",
"typenum",
]
[[package]]
+name = "data-encoding"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
+
+[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -349,6 +409,12 @@ dependencies = [
]
[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -370,7 +436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -401,6 +467,70 @@ dependencies = [
]
[[package]]
+name = "futures"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -425,18 +555,42 @@ dependencies = [
[[package]]
name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
- "r-efi",
+ "r-efi 6.0.0",
"wasip2",
"wasip3",
]
[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -501,6 +655,22 @@ dependencies = [
]
[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -706,6 +876,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
+name = "lru"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
+
+[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -734,7 +910,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -742,16 +918,24 @@ name = "myc"
version = "0.1.0"
dependencies = [
"radroots-identity",
+ "radroots-nostr",
"radroots-nostr-signer",
"serde",
"tempfile",
"thiserror 2.0.18",
+ "tokio",
"toml",
"tracing",
"tracing-subscriber",
]
[[package]]
+name = "negentropy"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
+
+[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -767,6 +951,7 @@ version = "0.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3aa5e3b6a278ed061835fe1ee293b71641e6bf8b401cfe4e1834bbf4ef0a34e1"
dependencies = [
+ "aes",
"base64 0.22.1",
"bech32",
"bip39",
@@ -786,12 +971,65 @@ dependencies = [
]
[[package]]
+name = "nostr-database"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1"
+dependencies = [
+ "lru",
+ "nostr",
+ "tokio",
+]
+
+[[package]]
+name = "nostr-gossip"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade30de16869618919c6b5efc8258f47b654a98b51541eb77f85e8ec5e3c83a6"
+dependencies = [
+ "nostr",
+]
+
+[[package]]
+name = "nostr-relay-pool"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b1073ccfbaea5549fb914a9d52c68dab2aecda61535e5143dd73e95445a804b"
+dependencies = [
+ "async-utility",
+ "async-wsocket",
+ "atomic-destructor",
+ "hex",
+ "lru",
+ "negentropy",
+ "nostr",
+ "nostr-database",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "nostr-sdk"
+version = "0.44.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471732576710e779b64f04c55e3f8b5292f865fea228436daf19694f0bf70393"
+dependencies = [
+ "async-utility",
+ "nostr",
+ "nostr-database",
+ "nostr-gossip",
+ "nostr-relay-pool",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -838,7 +1076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
- "rand_core",
+ "rand_core 0.6.4",
"subtle",
]
@@ -978,6 +1216,12 @@ dependencies = [
[[package]]
name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
@@ -1023,6 +1267,18 @@ dependencies = [
]
[[package]]
+name = "radroots-nostr"
+version = "0.1.0-alpha.1"
+dependencies = [
+ "nostr",
+ "nostr-sdk",
+ "radroots-identity",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
name = "radroots-nostr-connect"
version = "0.1.0-alpha.1"
dependencies = [
@@ -1072,8 +1328,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
]
[[package]]
@@ -1083,7 +1349,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
]
[[package]]
@@ -1096,6 +1372,15 @@ dependencies = [
]
[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1113,6 +1398,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "ron"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1165,7 +1464,41 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
]
[[package]]
@@ -1201,7 +1534,7 @@ version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
- "rand",
+ "rand 0.8.5",
"secp256k1-sys",
"serde",
]
@@ -1274,6 +1607,17 @@ dependencies = [
]
[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1310,12 +1654,28 @@ dependencies = [
]
[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
+name = "socket2"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1359,7 +1719,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1482,12 +1842,14 @@ version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
+ "bytes",
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
+ "socket2",
"tokio-macros",
- "windows-sys",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1502,6 +1864,44 @@ dependencies = [
]
[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-socks"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
+dependencies = [
+ "either",
+ "futures-util",
+ "thiserror 1.0.69",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tungstenite",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1616,6 +2016,25 @@ dependencies = [
]
[[package]]
+name = "tungstenite"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
+dependencies = [
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.9.2",
+ "rustls",
+ "rustls-pki-types",
+ "sha1",
+ "thiserror 2.0.18",
+ "utf-8",
+]
+
+[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1665,6 +2084,12 @@ dependencies = [
]
[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1678,6 +2103,12 @@ dependencies = [
]
[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1744,6 +2175,20 @@ dependencies = [
]
[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
name = "wasm-bindgen-macro"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1820,6 +2265,24 @@ dependencies = [
]
[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1827,6 +2290,15 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
@@ -1835,6 +2307,70 @@ dependencies = [
]
[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "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]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[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_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[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_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[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
@@ -15,9 +15,11 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }
[dependencies]
radroots-identity = { path = "../lib/crates/identity" }
+radroots-nostr = { path = "../lib/crates/nostr", features = ["client"] }
radroots-nostr-signer = { path = "../lib/crates/nostr-signer" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "2.0"
+tokio = { version = "1.48", features = ["macros", "rt-multi-thread"] }
toml = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
diff --git a/src/app/mod.rs b/src/app/mod.rs
@@ -25,8 +25,8 @@ impl MycApp {
self.runtime.snapshot()
}
- pub fn run(self) -> Result<(), MycError> {
- self.runtime.run()
+ pub async fn run(self) -> Result<(), MycError> {
+ self.runtime.run().await
}
}
@@ -75,5 +75,6 @@ mod tests {
assert!(!snapshot.signer_public_key_hex.is_empty());
assert!(!snapshot.user_identity_id.is_empty());
assert!(!snapshot.user_public_key_hex.is_empty());
+ assert!(!snapshot.transport.enabled);
}
}
diff --git a/src/app/runtime.rs b/src/app/runtime.rs
@@ -4,6 +4,7 @@ use std::sync::Arc;
use crate::config::MycConfig;
use crate::error::MycError;
+use crate::transport::{MycNostrTransport, MycTransportSnapshot};
use radroots_identity::{RadrootsIdentity, RadrootsIdentityPublic};
use radroots_nostr_signer::prelude::{RadrootsNostrFileSignerStore, RadrootsNostrSignerManager};
@@ -29,6 +30,7 @@ pub struct MycStartupSnapshot {
pub signer_public_key_hex: String,
pub user_identity_id: String,
pub user_public_key_hex: String,
+ pub transport: MycTransportSnapshot,
}
#[derive(Clone)]
@@ -43,6 +45,7 @@ pub struct MycRuntime {
config: MycConfig,
paths: MycRuntimePaths,
signer: MycSignerContext,
+ transport: Option<MycNostrTransport>,
}
impl MycRuntime {
@@ -52,10 +55,12 @@ impl MycRuntime {
let paths = MycRuntimePaths::from_config(&config);
Self::prepare_filesystem_for(&paths)?;
let signer = MycSignerContext::bootstrap(&paths)?;
+ let transport = MycNostrTransport::bootstrap(&config.transport, &signer.signer_identity)?;
let runtime = Self {
paths,
config,
signer,
+ transport,
};
Ok(runtime)
}
@@ -88,6 +93,10 @@ impl MycRuntime {
&self.signer.manager
}
+ pub fn transport(&self) -> Option<&MycNostrTransport> {
+ self.transport.as_ref()
+ }
+
pub fn snapshot(&self) -> MycStartupSnapshot {
let signer_public = self.signer.signer_identity.to_public();
let user_public = self.signer.user_identity.to_public();
@@ -103,10 +112,15 @@ impl MycRuntime {
signer_public_key_hex: signer_public.public_key_hex,
user_identity_id: user_public.id.into_string(),
user_public_key_hex: user_public.public_key_hex,
+ transport: self
+ .transport
+ .as_ref()
+ .map(MycNostrTransport::snapshot)
+ .unwrap_or_else(MycTransportSnapshot::disabled),
}
}
- pub fn run(self) -> Result<(), MycError> {
+ pub async fn run(self) -> Result<(), MycError> {
let snapshot = self.snapshot();
tracing::info!(
instance_name = %snapshot.instance_name,
@@ -119,6 +133,9 @@ impl MycRuntime {
signer_public_key_hex = %snapshot.signer_public_key_hex,
user_identity_id = %snapshot.user_identity_id,
user_public_key_hex = %snapshot.user_public_key_hex,
+ transport_enabled = snapshot.transport.enabled,
+ transport_relay_count = snapshot.transport.relay_count,
+ transport_connect_timeout_secs = snapshot.transport.connect_timeout_secs,
"myc runtime bootstrapped"
);
Ok(())
@@ -250,6 +267,7 @@ mod tests {
runtime.user_identity().public_key_hex(),
runtime.snapshot().user_public_key_hex
);
+ assert!(!runtime.snapshot().transport.enabled);
}
#[test]
@@ -329,4 +347,31 @@ mod tests {
runtime.snapshot().user_identity_id
);
}
+
+ #[test]
+ fn bootstrap_prepares_transport_when_enabled() {
+ let temp = tempfile::tempdir().expect("tempdir");
+ let mut config = MycConfig::default();
+ config.paths.state_dir = temp.path().join("state");
+ config.paths.signer_identity_path = temp.path().join("signer.json");
+ config.paths.user_identity_path = temp.path().join("user.json");
+ config.transport.enabled = true;
+ config.transport.connect_timeout_secs = 15;
+ config.transport.relays = vec!["wss://relay.example.com".to_owned()];
+ write_test_identity(
+ &config.paths.signer_identity_path,
+ "1111111111111111111111111111111111111111111111111111111111111111",
+ );
+ write_test_identity(
+ &config.paths.user_identity_path,
+ "2222222222222222222222222222222222222222222222222222222222222222",
+ );
+
+ let runtime = MycRuntime::bootstrap(config).expect("runtime");
+
+ assert!(runtime.transport().is_some());
+ assert!(runtime.snapshot().transport.enabled);
+ assert_eq!(runtime.snapshot().transport.relay_count, 1);
+ assert_eq!(runtime.snapshot().transport.connect_timeout_secs, 15);
+ }
}
diff --git a/src/config.rs b/src/config.rs
@@ -2,6 +2,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use radroots_identity::DEFAULT_IDENTITY_PATH;
+use radroots_nostr::prelude::RadrootsNostrRelayUrl;
use serde::{Deserialize, Serialize};
use tracing_subscriber::EnvFilter;
@@ -15,6 +16,7 @@ pub struct MycConfig {
pub service: MycServiceConfig,
pub logging: MycLoggingConfig,
pub paths: MycPathsConfig,
+ pub transport: MycTransportConfig,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -37,12 +39,21 @@ pub struct MycPathsConfig {
pub user_identity_path: PathBuf,
}
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(default, deny_unknown_fields)]
+pub struct MycTransportConfig {
+ pub enabled: bool,
+ pub connect_timeout_secs: u64,
+ pub relays: Vec<String>,
+}
+
impl Default for MycConfig {
fn default() -> Self {
Self {
service: MycServiceConfig::default(),
logging: MycLoggingConfig::default(),
paths: MycPathsConfig::default(),
+ transport: MycTransportConfig::default(),
}
}
}
@@ -73,6 +84,16 @@ impl Default for MycPathsConfig {
}
}
+impl Default for MycTransportConfig {
+ fn default() -> Self {
+ Self {
+ enabled: false,
+ connect_timeout_secs: 10,
+ relays: Vec::new(),
+ }
+ }
+}
+
impl MycConfig {
pub fn load_from_default_path_if_exists() -> Result<Self, MycError> {
Self::load_from_path_if_exists(DEFAULT_CONFIG_PATH)
@@ -140,6 +161,19 @@ impl MycConfig {
));
}
+ if self.transport.connect_timeout_secs == 0 {
+ return Err(MycError::InvalidConfig(
+ "transport.connect_timeout_secs must be greater than zero".to_owned(),
+ ));
+ }
+
+ let parsed_relays = self.transport.parse_relays()?;
+ if self.transport.enabled && parsed_relays.is_empty() {
+ return Err(MycError::InvalidConfig(
+ "transport.relays must not be empty when transport.enabled is true".to_owned(),
+ ));
+ }
+
Ok(())
}
@@ -153,6 +187,21 @@ impl MycConfig {
}
}
+impl MycTransportConfig {
+ pub fn parse_relays(&self) -> Result<Vec<RadrootsNostrRelayUrl>, MycError> {
+ self.relays
+ .iter()
+ .map(|value| {
+ RadrootsNostrRelayUrl::parse(value).map_err(|source| {
+ MycError::InvalidConfig(format!(
+ "transport.relays contains invalid relay url `{value}`: {source}"
+ ))
+ })
+ })
+ .collect()
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -171,6 +220,9 @@ mod tests {
config.paths.user_identity_path,
PathBuf::from(DEFAULT_IDENTITY_PATH)
);
+ assert!(!config.transport.enabled);
+ assert_eq!(config.transport.connect_timeout_secs, 10);
+ assert!(config.transport.relays.is_empty());
}
#[test]
@@ -187,6 +239,11 @@ mod tests {
state_dir = "/tmp/myc"
signer_identity_path = "/tmp/myc-identity.json"
user_identity_path = "/tmp/myc-user.json"
+
+ [transport]
+ enabled = true
+ connect_timeout_secs = 15
+ relays = ["wss://relay.example.com", "wss://relay2.example.com"]
"#,
)
.expect("config");
@@ -202,6 +259,15 @@ mod tests {
config.paths.user_identity_path,
PathBuf::from("/tmp/myc-user.json")
);
+ assert!(config.transport.enabled);
+ assert_eq!(config.transport.connect_timeout_secs, 15);
+ assert_eq!(
+ config.transport.relays,
+ vec![
+ "wss://relay.example.com".to_owned(),
+ "wss://relay2.example.com".to_owned()
+ ]
+ );
}
#[test]
@@ -226,4 +292,13 @@ mod tests {
assert!(err.to_string().contains("config parse error"));
}
+
+ #[test]
+ fn validate_rejects_enabled_transport_without_relays() {
+ let mut config = MycConfig::default();
+ config.transport.enabled = true;
+
+ let err = config.validate().expect_err("missing relays");
+ assert!(err.to_string().contains("transport.relays"));
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
@@ -4,15 +4,18 @@ pub mod app;
pub mod config;
pub mod error;
pub mod logging;
+pub mod transport;
pub use app::{MycApp, MycRuntime, MycRuntimePaths, MycSignerContext, MycStartupSnapshot};
pub use config::{
DEFAULT_CONFIG_PATH, MycConfig, MycLoggingConfig, MycPathsConfig, MycServiceConfig,
+ MycTransportConfig,
};
pub use error::MycError;
+pub use transport::{MycNostrTransport, MycTransportSnapshot};
-pub fn run() -> Result<(), MycError> {
+pub async fn run() -> Result<(), MycError> {
let config = MycConfig::load_from_default_path_if_exists()?;
logging::init_logging(&config.logging)?;
- MycApp::bootstrap(config)?.run()
+ MycApp::bootstrap(config)?.run().await
}
diff --git a/src/main.rs b/src/main.rs
@@ -1,7 +1,8 @@
#![forbid(unsafe_code)]
-fn main() {
- if let Err(err) = myc::run() {
+#[tokio::main]
+async fn main() {
+ if let Err(err) = myc::run().await {
eprintln!("myc: {err}");
std::process::exit(1);
}
diff --git a/src/transport.rs b/src/transport.rs
@@ -0,0 +1,118 @@
+use radroots_identity::RadrootsIdentity;
+use radroots_nostr::prelude::{RadrootsNostrClient, RadrootsNostrRelayUrl};
+
+use crate::config::MycTransportConfig;
+use crate::error::MycError;
+
+#[derive(Clone)]
+pub struct MycNostrTransport {
+ client: RadrootsNostrClient,
+ relays: Vec<RadrootsNostrRelayUrl>,
+ connect_timeout_secs: u64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct MycTransportSnapshot {
+ pub enabled: bool,
+ pub relay_count: usize,
+ pub connect_timeout_secs: u64,
+}
+
+impl MycNostrTransport {
+ pub fn bootstrap(
+ config: &MycTransportConfig,
+ signer_identity: &RadrootsIdentity,
+ ) -> Result<Option<Self>, MycError> {
+ if !config.enabled {
+ return Ok(None);
+ }
+
+ Ok(Some(Self {
+ client: RadrootsNostrClient::from_identity(signer_identity),
+ relays: config.parse_relays()?,
+ connect_timeout_secs: config.connect_timeout_secs,
+ }))
+ }
+
+ pub fn client(&self) -> &RadrootsNostrClient {
+ &self.client
+ }
+
+ pub fn relays(&self) -> &[RadrootsNostrRelayUrl] {
+ self.relays.as_slice()
+ }
+
+ pub fn connect_timeout_secs(&self) -> u64 {
+ self.connect_timeout_secs
+ }
+
+ pub fn snapshot(&self) -> MycTransportSnapshot {
+ MycTransportSnapshot {
+ enabled: true,
+ relay_count: self.relays.len(),
+ connect_timeout_secs: self.connect_timeout_secs,
+ }
+ }
+}
+
+impl MycTransportSnapshot {
+ pub fn disabled() -> Self {
+ Self {
+ enabled: false,
+ relay_count: 0,
+ connect_timeout_secs: 0,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use radroots_identity::RadrootsIdentity;
+
+ use crate::config::MycTransportConfig;
+
+ use super::{MycNostrTransport, MycTransportSnapshot};
+
+ fn signer_identity() -> RadrootsIdentity {
+ RadrootsIdentity::from_secret_key_str(
+ "1111111111111111111111111111111111111111111111111111111111111111",
+ )
+ .expect("identity")
+ }
+
+ #[test]
+ fn bootstrap_returns_none_when_transport_disabled() {
+ let config = MycTransportConfig::default();
+
+ let transport =
+ MycNostrTransport::bootstrap(&config, &signer_identity()).expect("disabled transport");
+
+ assert!(transport.is_none());
+ }
+
+ #[test]
+ fn bootstrap_builds_transport_snapshot_when_enabled() {
+ let mut config = MycTransportConfig::default();
+ config.enabled = true;
+ config.connect_timeout_secs = 15;
+ config.relays = vec![
+ "wss://relay.example.com".to_owned(),
+ "wss://relay2.example.com".to_owned(),
+ ];
+
+ let transport = MycNostrTransport::bootstrap(&config, &signer_identity())
+ .expect("transport")
+ .expect("enabled transport");
+
+ assert_eq!(transport.relays().len(), 2);
+ assert_eq!(transport.connect_timeout_secs(), 15);
+ assert_eq!(
+ transport.snapshot(),
+ MycTransportSnapshot {
+ enabled: true,
+ relay_count: 2,
+ connect_timeout_secs: 15,
+ }
+ );
+ }
+}