From ceb69660beb7d3fb525894e8d8265965517daa5b Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sat, 24 Oct 2026 11:39:22 +0700 Subject: [PATCH] chore: improve wot --- Cargo.lock | 181 ++++++++++++++++++++++---------------- crates/person/src/wot.rs | 185 ++++++++++++++++++++++++--------------- 2 files changed, 219 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d116db2..ee01791d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -982,9 +982,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" dependencies = [ "find-msvc-tools", "jobserver", @@ -1224,7 +1224,7 @@ name = "common" version = "2.0.0-alpha" dependencies = [ "chrono", - "dirs", + "dirs 5.0.1", "futures", "gpui-pre", "instant", @@ -1548,9 +1548,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if", ] @@ -1636,9 +1636,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0" dependencies = [ "cmov", ] @@ -1789,7 +1789,16 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", ] [[package]] @@ -1800,10 +1809,22 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", - "redox_users", + "redox_users 0.4.6", "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.3", + "windows-sys 0.61.2", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -2945,7 +2966,7 @@ dependencies = [ "log", "rustls", "rustls-platform-verifier", - "webpki-roots 1.0.10", + "webpki-roots 1.0.11", ] [[package]] @@ -3659,7 +3680,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.10", + "webpki-roots 1.0.11", ] [[package]] @@ -3712,9 +3733,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -3726,9 +3747,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -3739,9 +3760,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -3753,15 +3774,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ "displaydoc", "icu_collections", @@ -3774,15 +3795,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -4396,9 +4417,9 @@ dependencies = [ [[package]] name = "lyon_algorithms" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" +checksum = "f3a6b9d4e8b48acd531fbf1241ad9b270df6e60b6b92bdea1469f4f40899d805" dependencies = [ "lyon_path", "num-traits", @@ -4406,9 +4427,9 @@ dependencies = [ [[package]] name = "lyon_geom" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +checksum = "d82dd26aa4dc0b5b70b9115de59824842bcb43cf4a28e1b1e6d0ad148c93f93e" dependencies = [ "arrayvec", "euclid", @@ -4427,9 +4448,9 @@ dependencies = [ [[package]] name = "lyon_tessellation" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b8dcf906637ecef61b3c0740c7a4e7f27caeb31257cfac0cc579ce15be6005" +checksum = "fd4872650e70dc907dd4f7ebc449d7d657a0d2658e423b2e724970870bbbf75b" dependencies = [ "float_next_after", "lyon_path", @@ -4535,9 +4556,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] @@ -4841,7 +4862,7 @@ dependencies = [ [[package]] name = "nostr" version = "0.45.5" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "aes", "base64 0.22.1", @@ -4867,7 +4888,7 @@ dependencies = [ [[package]] name = "nostr-blossom" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "base64 0.22.1", "bitcoin_hashes 1.2.0", @@ -4880,7 +4901,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.45.2" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "async-utility", "nostr", @@ -4893,7 +4914,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.45.2" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "nostr", "opaquerr 0.2.0", @@ -4902,7 +4923,7 @@ dependencies = [ [[package]] name = "nostr-gossip" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "nostr", "opaquerr 0.2.0", @@ -4911,7 +4932,7 @@ dependencies = [ [[package]] name = "nostr-gossip-memory" version = "0.45.1" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "indexmap 2.14.2", "lru", @@ -4923,7 +4944,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.45.3" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "async-utility", "flatbuffers", @@ -4938,7 +4959,7 @@ dependencies = [ [[package]] name = "nostr-memory" version = "0.45.2" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "btreecap", "nostr", @@ -4949,7 +4970,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.45.4" -source = "git+https://github.com/rust-nostr/nostr#20096765b95f4840dd3df66cad49db35e9634462" +source = "git+https://github.com/rust-nostr/nostr#f7ebc0a88ea27ac4e3d74b941e3a44ddb10b306c" dependencies = [ "async-utility", "async-wsocket", @@ -5539,9 +5560,9 @@ checksum = "4f933a4265d5cdad61d19bbdfc972ea5726d56cd8d3d57b8f2d3c365dd42bee9" [[package]] name = "open" -version = "5.4.4" +version = "5.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" dependencies = [ "is-wsl", "libc", @@ -6377,6 +6398,16 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.21", +] + [[package]] name = "ref-cast" version = "1.0.27" @@ -6468,7 +6499,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.10", + "webpki-roots 1.0.11", ] [[package]] @@ -6753,9 +6784,9 @@ checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -7199,7 +7230,7 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -7311,9 +7342,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.3" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" [[package]] name = "smol" @@ -7334,9 +7365,9 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +checksum = "eeb7109d23f319619d4cc0de5464613c8b7b1a09db2774a58101459cf6be2f10" dependencies = [ "serde_core", ] @@ -7822,9 +7853,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -7838,9 +7869,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.54.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -8329,9 +8360,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.4.2" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +checksum = "cde93ff05758a36a851a398a772b0b79a82fbe3c186a46f2696baaeaf84c966b" dependencies = [ "base64 0.23.1", "cookie_store", @@ -8344,7 +8375,7 @@ dependencies = [ "serde_json", "ureq-proto", "utf8-zero", - "webpki-roots 1.0.10", + "webpki-roots 1.0.11", ] [[package]] @@ -8446,9 +8477,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.28.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -8742,9 +8773,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" +checksum = "d2c8ddfbbf66e501434657400268cefe51b95140a0266236415cda7627732df7" dependencies = [ "jni", "log", @@ -8758,9 +8789,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ec80e73fd8baf57bf0d9615554c75348bda990d6d5bbeab964dd068319fecb" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -8771,14 +8802,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.10", + "webpki-roots 1.0.11", ] [[package]] name = "webpki-roots" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" +checksum = "f5f280bbb2b287bc8e7de7e3852945a0ff50d1bbcb5ed42b6c7a568c546df171" dependencies = [ "rustls-pki-types", ] @@ -9806,9 +9837,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -9944,7 +9975,7 @@ dependencies = [ "core-foundation 0.10.1", "core-graphics 0.24.0", "core-text", - "dirs", + "dirs 5.0.1", "dwrote", "float-ord", "freetype-sys", @@ -10074,9 +10105,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -10085,9 +10116,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", diff --git a/crates/person/src/wot.rs b/crates/person/src/wot.rs index f0475edb..2a062a5c 100644 --- a/crates/person/src/wot.rs +++ b/crates/person/src/wot.rs @@ -4,7 +4,7 @@ use gpui::{Context, EventEmitter, Task}; use instant::Duration; use nostr_sdk::prelude::*; use smallvec::{SmallVec, smallvec}; -use state::NostrRegistry; +use state::{BOOTSTRAP_RELAYS, NostrRegistry}; /// Disable scoring when the follow set exceeds this size const MAX_FOLLOWS: usize = 2000; @@ -14,6 +14,10 @@ const MAX_TAGS_PER_LIST: usize = 5000; const SYNC_INTERVAL: u64 = 15 * 60; /// Longest wait between retries while the graph has no data yet const MAX_RETRY_WAIT: u64 = 60; +/// Time for a relay to prove negentropy support before the reconcile gives up +const SYNC_INITIAL_TIMEOUT: u64 = 30; +/// Longest silence from a relay before the reconcile gives up +const SYNC_IDLE_TIMEOUT: u64 = 60; /// The web of trust graph changed #[derive(Debug, Clone)] @@ -77,6 +81,36 @@ impl Snapshot { snapshot } + + /// Build the snapshot from the contact lists the local database holds + async fn load(client: &Client, self_key: PublicKey) -> Option { + let own = stored_contact_list(client, self_key).await?; + let follows: HashSet = own.tags.public_keys().take(MAX_FOLLOWS + 1).collect(); + + let events: Vec = if follows.is_empty() { + Vec::new() + } else { + let authors: Vec = follows.iter().copied().collect(); + let filter = Filter::new().kind(Kind::ContactList).authors(authors); + + client + .database() + .query(filter) + .await + .unwrap_or_default() + .into_iter() + .collect() + }; + + let snapshot = Self::build(self_key, follows, events); + log::debug!( + "wot loaded from database: {} follows, {} scored keys", + snapshot.follows.len(), + snapshot.scores.len() + ); + + Some(snapshot) + } } impl EventEmitter for WebOfTrust {} @@ -88,8 +122,10 @@ impl EventEmitter for WebOfTrust {} pub struct WebOfTrust { /// Current user's public key self_key: Option, - /// Latest built snapshot, none until the first refresh completes + /// Latest built snapshot, none until the first load completes snapshot: Option, + /// Whether a reconciliation with the relays is in flight + syncing: bool, /// In-flight refresh, replaced by newer ones refresh_task: Option>, /// Keeps the periodic reconciliation loop alive @@ -110,9 +146,9 @@ impl WebOfTrust { let mut has_data = false; - // Ignore errors, the next refresh will retry + // Ignore errors, the next sync will retry let _ = this.update(cx, |this, cx| { - this.refresh(cx); + this.sync(cx); has_data = this.has_data(); }); @@ -133,34 +169,14 @@ impl WebOfTrust { Self { self_key: None, snapshot: None, + syncing: false, refresh_task: None, _tasks: tasks, } } - /// Load the graph for the current account + /// Load the graph for the current account from the local database pub(crate) fn hydrate(&mut self, cx: &mut Context) { - let nostr = NostrRegistry::global(cx); - let Some(self_key) = nostr.read(cx).current_user() else { - return; - }; - - self.self_key = Some(self_key); - self.refresh(cx); - } - - /// Clear the graph on sign out or account switch - pub(crate) fn reset(&mut self, cx: &mut Context) { - self.self_key = None; - self.snapshot = None; - self.refresh_task = None; - - cx.emit(WoTEvent); - cx.notify(); - } - - /// Reconcile the follow set's contact lists with the relays and rebuild. - fn refresh(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); @@ -168,6 +184,55 @@ impl WebOfTrust { return; }; + self.self_key = Some(self_key); + // Replacing the task below cancels any in-flight work + self.syncing = false; + + self.refresh_task = Some(cx.spawn(async move |this, cx| { + let Some(snapshot) = Snapshot::load(&client, self_key).await else { + return; + }; + + this.update(cx, |this, cx| { + this.snapshot = Some(snapshot); + cx.emit(WoTEvent); + cx.notify(); + }) + .ok(); + })); + } + + /// Clear the graph on sign out or account switch + pub(crate) fn reset(&mut self, cx: &mut Context) { + self.self_key = None; + self.snapshot = None; + self.syncing = false; + self.refresh_task = None; + + cx.emit(WoTEvent); + cx.notify(); + } + + /// Reconcile the follow set's contact lists with the bootstrap relays and rebuild. + fn sync(&mut self, cx: &mut Context) { + if self.syncing { + return; + } + + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + + let Some(self_key) = nostr.read(cx).current_user() else { + return; + }; + + // The defaults are too short for relays streaming large batches + let opts = SyncOptions::new() + .initial_timeout(Duration::from_secs(SYNC_INITIAL_TIMEOUT)) + .idle_timeout(Duration::from_secs(SYNC_IDLE_TIMEOUT)); + + self.syncing = true; + self.refresh_task = Some(cx.spawn(async move |this, cx| { // Scope the reconcile to the follow set known so far let known: HashSet = stored_contact_list(&client, self_key) @@ -178,60 +243,36 @@ impl WebOfTrust { let mut authors: Vec = known.iter().copied().collect(); authors.push(self_key); - // Negentropy transfers only the lists the relays hold - // and the database is missing let filter = Filter::new().kind(Kind::ContactList).authors(authors); - if let Err(error) = client.sync(filter).await { - log::warn!("Failed to reconcile contact lists: {error}"); + match client.sync(filter).with(BOOTSTRAP_RELAYS).opts(opts).await { + Ok(output) => { + for (url, error) in output.failed { + log::warn!("Failed to reconcile contact lists with {url}: {error}"); + } + } + Err(error) => log::warn!("Failed to reconcile contact lists: {error}"), } // The follow set after the reconcile - let Some(own) = stored_contact_list(&client, self_key).await else { - return; - }; - - let follows: HashSet = - own.tags.public_keys().take(MAX_FOLLOWS + 1).collect(); - - let mut authors: Vec = follows.iter().copied().collect(); - authors.push(self_key); - - let events: Vec = if follows.is_empty() { - Vec::new() - } else { - let filter = Filter::new().kind(Kind::ContactList).authors(authors); - client - .database() - .query(filter) - .await - .unwrap_or_default() - .into_iter() - .collect() - }; - - let snapshot = Snapshot::build(self_key, follows.clone(), events); - log::debug!( - "Web of trust rebuilt: {} follows, {} scored keys", - snapshot.follows.len(), - snapshot.scores.len() - ); - - // Newly followed authors were not part of the reconcile - let grew = !known.is_superset(&follows); + let snapshot = Snapshot::load(&client, self_key).await; this.update(cx, |this, cx| { - this.snapshot = Some(snapshot); - cx.emit(WoTEvent); - cx.notify(); + this.syncing = false; + + if let Some(snapshot) = snapshot { + // Newly followed authors were not part of the reconcile + let grew = !known.is_superset(&snapshot.follows); + + this.snapshot = Some(snapshot); + cx.emit(WoTEvent); + cx.notify(); + + if grew { + this.sync(cx); + } + } }) .ok(); - - if grew { - this.update(cx, |this, cx| { - this.refresh(cx); - }) - .ok(); - } })); }