nostr: revert the money-path split — all nostr on the mixnet (as prior builds), keep confirm-before-sent
This commit is contained in:
@@ -1,200 +0,0 @@
|
|||||||
// RUNTIME verification of the money-path narrowing (two-client split).
|
|
||||||
//
|
|
||||||
// Builds the SAME two nostr-sdk clients that `run_service` now builds:
|
|
||||||
// * MONEY client -> grim::nym::NymWebSocketTransport (mixnet; scoped exit for
|
|
||||||
// relay.floonet.dev) -> kind-1059 gift-wraps ONLY.
|
|
||||||
// * GENERAL client -> stock nostr-sdk transport (CLEARNET-direct) -> identity
|
|
||||||
// (0/10002/10050) + profile/DM-relay lookups.
|
|
||||||
//
|
|
||||||
// The transport routing under test (NymWebSocketTransport -> pool.exit_for ->
|
|
||||||
// streamexit) is 100% real grim code; only the per-op client assignment (the
|
|
||||||
// split itself) is mirrored here so we can control connection lifecycle and read
|
|
||||||
// the scratch exit's per-stream byte log cleanly.
|
|
||||||
//
|
|
||||||
// The pool is pointed at a SCRATCH scoped exit (env SCRATCH_EXIT). That exit is a
|
|
||||||
// plain floonet-mixexit whose stdout logs `stream closed (X B in, Y B out)` — our
|
|
||||||
// byte counter. Because the GENERAL client is clearnet, the scratch exit never
|
|
||||||
// even opens a stream for identity/lookup: that is the proof they are OFF the exit.
|
|
||||||
//
|
|
||||||
// HOME=/tmp/e2e-home SCRATCH_EXIT="<addr>" cargo run --example nostr_split_measure
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use nostr_sdk::{
|
|
||||||
Client, EventBuilder, Filter, Keys, Kind, Metadata, RelayUrl, SubscriptionId, Tag, TagKind,
|
|
||||||
Timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RELAY: &str = "wss://relay.floonet.dev";
|
|
||||||
|
|
||||||
async fn connect(client: &Client, url: &str) {
|
|
||||||
let _ = client.add_relay(url).await;
|
|
||||||
client.connect().await;
|
|
||||||
// Give the handshake time (clearnet ~instant, exit ~2-6s over the mixnet).
|
|
||||||
for _ in 0..40 {
|
|
||||||
if client
|
|
||||||
.relays()
|
|
||||||
.await
|
|
||||||
.values()
|
|
||||||
.any(|r| r.status() == nostr_sdk::RelayStatus::Connected)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
|
||||||
let exit =
|
|
||||||
std::env::var("SCRATCH_EXIT").expect("set SCRATCH_EXIT to the scratch exit nym addr");
|
|
||||||
println!("[split] HOME={:?}", std::env::var("HOME").ok());
|
|
||||||
println!("[split] scratch exit = {exit}");
|
|
||||||
|
|
||||||
// Bring up the wallet's REAL nym stack (tunnel + scoped-exit streamexit client).
|
|
||||||
println!("[split] warm_up(): starting nym…");
|
|
||||||
grim::nym::warm_up();
|
|
||||||
let mut ready = false;
|
|
||||||
for _ in 0..360 {
|
|
||||||
if grim::nym::is_ready() {
|
|
||||||
ready = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
||||||
}
|
|
||||||
println!("[split] nym is_ready = {ready}");
|
|
||||||
// Sanity: the pool we wrote must resolve relay.floonet.dev -> the scratch exit.
|
|
||||||
match grim::nostr::pool::load().exit_for(RELAY) {
|
|
||||||
Some(e) if e == exit => println!("[split] pool.exit_for({RELAY}) -> scratch exit OK"),
|
|
||||||
other => println!("[split] WARN pool.exit_for -> {other:?} (expected scratch exit)"),
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = Keys::generate();
|
|
||||||
let me = keys.public_key();
|
|
||||||
println!("[split] ephemeral npub pubkey = {}", me.to_hex());
|
|
||||||
|
|
||||||
// ================= PHASE 1: GENERAL (clearnet) =================
|
|
||||||
// identity publish (kinds 10050 + 10002 + 0) + a profile lookup. None of this
|
|
||||||
// must touch the scratch exit.
|
|
||||||
println!("\n[split] ===== PHASE 1: general/clearnet (identity + lookup) =====");
|
|
||||||
let general = Client::builder().signer(keys.clone()).build();
|
|
||||||
connect(&general, RELAY).await;
|
|
||||||
|
|
||||||
let dm_tags = vec![Tag::custom(TagKind::custom("relay"), [RELAY.to_string()])];
|
|
||||||
let inbox = EventBuilder::new(Kind::InboxRelays, "").tags(dm_tags);
|
|
||||||
let relay_list = EventBuilder::relay_list(RelayUrl::parse(RELAY).ok().map(|u| (u, None)));
|
|
||||||
let meta = EventBuilder::metadata(&Metadata::new().name("split-e2e"));
|
|
||||||
for (label, b) in [
|
|
||||||
("kind10050", inbox),
|
|
||||||
("kind10002", relay_list),
|
|
||||||
("kind0", meta),
|
|
||||||
] {
|
|
||||||
match general.sign_event_builder(b).await {
|
|
||||||
Ok(event) => match general.send_event_to([RELAY], &event).await {
|
|
||||||
Ok(o) => println!("[split] general publish {label} -> id {}", o.val.to_hex()),
|
|
||||||
Err(e) => println!("[split] general publish {label} FAILED: {e}"),
|
|
||||||
},
|
|
||||||
Err(e) => println!("[split] general sign {label} FAILED: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// A profile (kind-0) lookup of a random pubkey — the general-client read path.
|
|
||||||
let stranger = Keys::generate().public_key();
|
|
||||||
let f = Filter::new().kind(Kind::Metadata).author(stranger).limit(1);
|
|
||||||
let _ = general
|
|
||||||
.fetch_events_from([RELAY], f, Duration::from_secs(8))
|
|
||||||
.await;
|
|
||||||
println!("[split] general profile lookup done (clearnet)");
|
|
||||||
general.disconnect().await;
|
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
||||||
println!("[split] PHASE 1 complete — scratch exit should show ZERO streams so far.");
|
|
||||||
|
|
||||||
// ================= PHASE 2: MONEY (scoped exit) =================
|
|
||||||
println!("\n[split] ===== PHASE 2: money/exit (subscribe + catch-up + 1059) =====");
|
|
||||||
let money = Client::builder()
|
|
||||||
.signer(keys.clone())
|
|
||||||
.websocket_transport(grim::nym::NymWebSocketTransport)
|
|
||||||
.build();
|
|
||||||
connect(&money, RELAY).await;
|
|
||||||
let connected = money
|
|
||||||
.relays()
|
|
||||||
.await
|
|
||||||
.values()
|
|
||||||
.any(|r| r.status() == nostr_sdk::RelayStatus::Connected);
|
|
||||||
println!("[split] money client connected over scratch exit = {connected}");
|
|
||||||
|
|
||||||
// Gift-wrap inbox subscription + 3-day catch-up (the receive money path).
|
|
||||||
let since = Timestamp::from_secs(Timestamp::now().as_u64().saturating_sub(3 * 86_400));
|
|
||||||
let giftwrap = Filter::new().kind(Kind::GiftWrap).pubkey(me).since(since);
|
|
||||||
let _ = money
|
|
||||||
.subscribe_with_id_to(
|
|
||||||
[RELAY],
|
|
||||||
SubscriptionId::new("split-giftwrap"),
|
|
||||||
giftwrap.clone(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let _ = money
|
|
||||||
.fetch_events_from([RELAY], giftwrap, Duration::from_secs(20))
|
|
||||||
.await;
|
|
||||||
println!("[split] money subscribe + catch-up done (exit)");
|
|
||||||
|
|
||||||
// The kind-1059 PUBLISH — a NIP-59 gift-wrap, exactly dispatch_dm's v2 path.
|
|
||||||
// Synthetic ~30 KB slatepack so the 1059 is a clear, large stream on the exit.
|
|
||||||
let receiver = Keys::generate().public_key();
|
|
||||||
let slatepack = format!("BEGINSLATEPACK.{}.ENDSLATEPACK", "A".repeat(30_000));
|
|
||||||
let content = format!("goblin:pay\n{slatepack}");
|
|
||||||
let sent = money
|
|
||||||
.send_private_msg_to([RELAY], receiver, content, Vec::<Tag>::new())
|
|
||||||
.await;
|
|
||||||
let wrap_id = match sent {
|
|
||||||
Ok(o) => {
|
|
||||||
println!(
|
|
||||||
"[split] money 1059 publish -> gift-wrap id {}",
|
|
||||||
o.val.to_hex()
|
|
||||||
);
|
|
||||||
Some(o.val)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("[split] money 1059 publish FAILED: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Confirm read-back over the SAME exit (dispatch_dm's silent-loss guard).
|
|
||||||
let mut confirmed_via_exit = false;
|
|
||||||
if let Some(id) = wrap_id {
|
|
||||||
let cf = Filter::new().id(id).limit(1);
|
|
||||||
if let Ok(evs) = money
|
|
||||||
.fetch_events_from([RELAY], cf, Duration::from_secs(15))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
confirmed_via_exit = !evs.is_empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("[split] 1059 confirmed via exit read-back = {confirmed_via_exit}");
|
|
||||||
money.disconnect().await; // forces the scratch exit to log `stream closed (X in, Y out)`
|
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
||||||
|
|
||||||
// ================= PHASE 3: CLEARNET ORACLE =================
|
|
||||||
// Independently confirm the 1059 actually LANDED on relay.floonet.dev.
|
|
||||||
println!("\n[split] ===== PHASE 3: clearnet oracle (did the 1059 land?) =====");
|
|
||||||
let mut landed = false;
|
|
||||||
if let Some(id) = wrap_id {
|
|
||||||
let oracle = Client::builder().signer(keys.clone()).build();
|
|
||||||
connect(&oracle, RELAY).await;
|
|
||||||
let of = Filter::new().id(id).limit(1);
|
|
||||||
if let Ok(evs) = oracle
|
|
||||||
.fetch_events_from([RELAY], of, Duration::from_secs(15))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
landed = !evs.is_empty();
|
|
||||||
}
|
|
||||||
oracle.disconnect().await;
|
|
||||||
}
|
|
||||||
println!("[split] 1059 present on relay.floonet.dev via CLEARNET oracle = {landed}");
|
|
||||||
|
|
||||||
println!("\n[split] ===== DONE. Read the scratch exit log for the byte breakdown. =====");
|
|
||||||
println!("[split] expectation: 0 streams during phase 1; 1 money stream in phase 2 whose");
|
|
||||||
println!("[split] `X B in` is dominated by the ~30 KB 1059 publish (identity+lookup absent).");
|
|
||||||
}
|
|
||||||
+16
-64
@@ -101,22 +101,8 @@ pub struct NostrService {
|
|||||||
/// Directory holding identity.json.
|
/// Directory holding identity.json.
|
||||||
nostr_dir: PathBuf,
|
nostr_dir: PathBuf,
|
||||||
|
|
||||||
/// MONEY-PATH SDK client — carries ONLY kind-1059 gift-wraps (the slatepack
|
/// SDK client, present while the service loop runs.
|
||||||
/// payment traffic: publishing a payment/control wrap, plus the gift-wrap
|
|
||||||
/// inbox subscribe + catch-up + send-confirm read-back). Its transport is the
|
|
||||||
/// Nym [`NymWebSocketTransport`], so every byte rides the mixnet — the scoped
|
|
||||||
/// exit for `relay.floonet.dev`, the public tunnel for any other relay. NEVER
|
|
||||||
/// clearnet: who-pays-whom and "I am listening for payments to this pubkey"
|
|
||||||
/// must stay on the mixnet. Present while the service loop runs.
|
|
||||||
client: RwLock<Option<Client>>,
|
client: RwLock<Option<Client>>,
|
||||||
/// GENERAL SDK client — everything that is NOT a slatepack: our replaceable
|
|
||||||
/// identity events (kinds 0/3/10002/10050), the discovery-indexer fan-out, and
|
|
||||||
/// recipient profile / kind-10050 DM-relay lookups. Uses nostr-sdk's stock
|
|
||||||
/// transport = CLEARNET-direct, never the mixnet, so the metered scoped exit is
|
|
||||||
/// no longer starved by the wallet's whole relay session (identity + discovery
|
|
||||||
/// + catch-up firehose). These are all PUBLIC events/queries; keeping them off
|
|
||||||
/// the exit is the money-path narrowing. Present while the service loop runs.
|
|
||||||
general_client: RwLock<Option<Client>>,
|
|
||||||
/// Handle to the service's tokio runtime. One-shot fetches (e.g. profile
|
/// Handle to the service's tokio runtime. One-shot fetches (e.g. profile
|
||||||
/// lookups) from worker threads MUST run here, not on a throwaway runtime:
|
/// lookups) from worker threads MUST run here, not on a throwaway runtime:
|
||||||
/// the relay connections (incl. the custom Nym mixnet transport) are driven
|
/// the relay connections (incl. the custom Nym mixnet transport) are driven
|
||||||
@@ -173,7 +159,6 @@ impl NostrService {
|
|||||||
store: Arc::new(store),
|
store: Arc::new(store),
|
||||||
nostr_dir,
|
nostr_dir,
|
||||||
client: RwLock::new(None),
|
client: RwLock::new(None),
|
||||||
general_client: RwLock::new(None),
|
|
||||||
rt_handle: RwLock::new(None),
|
rt_handle: RwLock::new(None),
|
||||||
started: AtomicBool::new(false),
|
started: AtomicBool::new(false),
|
||||||
shutdown: AtomicBool::new(false),
|
shutdown: AtomicBool::new(false),
|
||||||
@@ -233,8 +218,7 @@ impl NostrService {
|
|||||||
/// relays (NIP-65/gossip), which we won't otherwise be connected to. Blocking;
|
/// relays (NIP-65/gossip), which we won't otherwise be connected to. Blocking;
|
||||||
/// call from a worker thread.
|
/// call from a worker thread.
|
||||||
pub fn fetch_profile_blocking(&self, hex: &str, hints: &[String]) -> Option<NostrProfile> {
|
pub fn fetch_profile_blocking(&self, hex: &str, hints: &[String]) -> Option<NostrProfile> {
|
||||||
// A profile (kind-0) lookup is general traffic — clearnet general client.
|
let client = self.client.read().clone()?;
|
||||||
let client = self.general_client.read().clone()?;
|
|
||||||
let pk = PublicKey::from_hex(hex).ok()?;
|
let pk = PublicKey::from_hex(hex).ok()?;
|
||||||
let hints: Vec<String> = hints.to_vec();
|
let hints: Vec<String> = hints.to_vec();
|
||||||
// Run on the SERVICE runtime — the relay connections (and the custom Nym
|
// Run on the SERVICE runtime — the relay connections (and the custom Nym
|
||||||
@@ -285,8 +269,7 @@ impl NostrService {
|
|||||||
/// field absent, or relays unreachable) = treat as accepting. Async — safe to
|
/// field absent, or relays unreachable) = treat as accepting. Async — safe to
|
||||||
/// call from the service runtime. Fail-open: only `Some(false)` blocks.
|
/// call from the service runtime. Fail-open: only `Some(false)` blocks.
|
||||||
pub async fn accepts_requests(&self, hex: &str) -> Option<bool> {
|
pub async fn accepts_requests(&self, hex: &str) -> Option<bool> {
|
||||||
// Reading a peer's kind-0 preference is general traffic — clearnet client.
|
let client = self.client.read().clone()?;
|
||||||
let client = self.general_client.read().clone()?;
|
|
||||||
let pk = PublicKey::from_hex(hex).ok()?;
|
let pk = PublicKey::from_hex(hex).ok()?;
|
||||||
let filter = Filter::new().kind(Kind::Metadata).author(pk).limit(1);
|
let filter = Filter::new().kind(Kind::Metadata).author(pk).limit(1);
|
||||||
// First-event-wins, scoped to our own connected relays (cap 8s): return on
|
// First-event-wins, scoped to our own connected relays (cap 8s): return on
|
||||||
@@ -311,8 +294,7 @@ impl NostrService {
|
|||||||
/// Republish our kind-0 profile + kind-10050 DM relays (e.g. after toggling
|
/// Republish our kind-0 profile + kind-10050 DM relays (e.g. after toggling
|
||||||
/// the incoming-requests preference) so the change propagates immediately.
|
/// the incoming-requests preference) so the change propagates immediately.
|
||||||
pub async fn republish_identity(self: &Arc<Self>) {
|
pub async fn republish_identity(self: &Arc<Self>) {
|
||||||
// Identity events (kinds 0/10002/10050) are published clearnet, off the exit.
|
let client = { self.client.read().clone() };
|
||||||
let client = { self.general_client.read().clone() };
|
|
||||||
if let Some(client) = client {
|
if let Some(client) = client {
|
||||||
publish_identity(self, &client).await;
|
publish_identity(self, &client).await;
|
||||||
}
|
}
|
||||||
@@ -567,7 +549,7 @@ impl NostrService {
|
|||||||
let content = protocol::build_payment_content(slatepack);
|
let content = protocol::build_payment_content(slatepack);
|
||||||
let tags = protocol::build_rumor_tags(note);
|
let tags = protocol::build_rumor_tags(note);
|
||||||
|
|
||||||
let (urls, v3) = self.send_targets(&receiver, relay_hints).await;
|
let (urls, v3) = self.send_targets(&client, &receiver, relay_hints).await;
|
||||||
|
|
||||||
// NIP-17 delivers to the RECIPIENT's relays, which may differ from ours;
|
// NIP-17 delivers to the RECIPIENT's relays, which may differ from ours;
|
||||||
// dial any we don't already hold so the gift wrap actually reaches their
|
// dial any we don't already hold so the gift wrap actually reaches their
|
||||||
@@ -596,7 +578,7 @@ impl NostrService {
|
|||||||
let content = protocol::build_control_content();
|
let content = protocol::build_control_content();
|
||||||
let tags = protocol::build_control_tags(slate_id);
|
let tags = protocol::build_control_tags(slate_id);
|
||||||
|
|
||||||
let (urls, v3) = self.send_targets(&receiver, relay_hints).await;
|
let (urls, v3) = self.send_targets(&client, &receiver, relay_hints).await;
|
||||||
|
|
||||||
connect_relays(&client, &urls).await;
|
connect_relays(&client, &urls).await;
|
||||||
|
|
||||||
@@ -671,10 +653,11 @@ impl NostrService {
|
|||||||
/// advertises `nip44_v3`; no tag (or no 10050 at all) = v2 only.
|
/// advertises `nip44_v3`; no tag (or no 10050 at all) = v2 only.
|
||||||
async fn send_targets(
|
async fn send_targets(
|
||||||
&self,
|
&self,
|
||||||
|
client: &Client,
|
||||||
receiver: &PublicKey,
|
receiver: &PublicKey,
|
||||||
relay_hints: &[String],
|
relay_hints: &[String],
|
||||||
) -> (Vec<String>, bool) {
|
) -> (Vec<String>, bool) {
|
||||||
let (urls, v3) = self.fetch_dm_relays(receiver).await;
|
let (urls, v3) = self.fetch_dm_relays(client, receiver).await;
|
||||||
if !urls.is_empty() {
|
if !urls.is_empty() {
|
||||||
return (urls, v3);
|
return (urls, v3);
|
||||||
}
|
}
|
||||||
@@ -697,7 +680,7 @@ impl NostrService {
|
|||||||
/// our own relays AND the pool's discovery indexers — the recipient's
|
/// our own relays AND the pool's discovery indexers — the recipient's
|
||||||
/// 10050 lives on their relays and the indexers, not necessarily on
|
/// 10050 lives on their relays and the indexers, not necessarily on
|
||||||
/// anything we share. Both facts are cached on the contact together.
|
/// anything we share. Both facts are cached on the contact together.
|
||||||
async fn fetch_dm_relays(&self, pk: &PublicKey) -> (Vec<String>, bool) {
|
async fn fetch_dm_relays(&self, client: &Client, pk: &PublicKey) -> (Vec<String>, bool) {
|
||||||
// Use cached relays (and the capability learned with them) first.
|
// Use cached relays (and the capability learned with them) first.
|
||||||
if let Some(contact) = self.store.contact(&pk.to_hex())
|
if let Some(contact) = self.store.contact(&pk.to_hex())
|
||||||
&& !contact.relays.is_empty()
|
&& !contact.relays.is_empty()
|
||||||
@@ -707,24 +690,13 @@ impl NostrService {
|
|||||||
contact.nip44_v3,
|
contact.nip44_v3,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Resolving a recipient's kind-10050 inbox is part of resolving the PRIVATE
|
|
||||||
// payment target, so it rides the MONEY/mixnet client (scoped exit for
|
|
||||||
// relay.floonet.dev, tunnel for the discovery indexers) — never clearnet. It
|
|
||||||
// is a tiny, cheap lookup (one replaceable event, a handful of relay tags):
|
|
||||||
// unlike the fat kind-0 profile (which stays on the clearnet general client),
|
|
||||||
// leaking the recipient's inbox set to a clear relay would erode the very
|
|
||||||
// payment privacy the mixnet buys. If the money client isn't up yet, fall
|
|
||||||
// back to relay hints + our own set, exactly as an empty lookup would.
|
|
||||||
let Some(client) = self.client.read().clone() else {
|
|
||||||
return (vec![], false);
|
|
||||||
};
|
|
||||||
let mut from = self.relays();
|
let mut from = self.relays();
|
||||||
for url in crate::nostr::pool::usable_discovery_relays().await {
|
for url in crate::nostr::pool::usable_discovery_relays().await {
|
||||||
if !from.contains(&url) {
|
if !from.contains(&url) {
|
||||||
from.push(url);
|
from.push(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect_relays(&client, &from).await;
|
connect_relays(client, &from).await;
|
||||||
let filter = Filter::new().kind(Kind::InboxRelays).author(*pk).limit(1);
|
let filter = Filter::new().kind(Kind::InboxRelays).author(*pk).limit(1);
|
||||||
let mut out = vec![];
|
let mut out = vec![];
|
||||||
let mut v3 = false;
|
let mut v3 = false;
|
||||||
@@ -896,18 +868,10 @@ async fn run_service(svc: Arc<NostrService>, wallet: Wallet) {
|
|||||||
// Mirror the configured name authority so resolution + display follow it.
|
// Mirror the configured name authority so resolution + display follow it.
|
||||||
crate::nostr::nip05::set_home_domain(&svc.config.read().home_domain());
|
crate::nostr::nip05::set_home_domain(&svc.config.read().home_domain());
|
||||||
|
|
||||||
// MONEY-PATH client: kind-1059 slatepack gift-wraps only, over the Nym mixnet
|
|
||||||
// (scoped exit for relay.floonet.dev, tunnel elsewhere). See `NostrService::client`.
|
|
||||||
let client = Client::builder()
|
let client = Client::builder()
|
||||||
.signer(svc.keys.clone())
|
.signer(svc.keys.clone())
|
||||||
.websocket_transport(NymWebSocketTransport)
|
.websocket_transport(NymWebSocketTransport)
|
||||||
.build();
|
.build();
|
||||||
// GENERAL client: identity (0/3/10002/10050), discovery fan-out and profile /
|
|
||||||
// DM-relay lookups — CLEARNET-direct (stock nostr-sdk transport), never the
|
|
||||||
// mixnet, so the metered scoped exit is no longer starved by the whole relay
|
|
||||||
// session. Only PUBLIC events/queries ride this; payment linkage stays on
|
|
||||||
// `client` above. See `NostrService::general_client`.
|
|
||||||
let general = Client::builder().signer(svc.keys.clone()).build();
|
|
||||||
// Wait for the in-process Nym mixnet tunnel before any network work
|
// Wait for the in-process Nym mixnet tunnel before any network work
|
||||||
// (relay dials, pool refresh, NIP-11 probes). `warm_up()` starts it at
|
// (relay dials, pool refresh, NIP-11 probes). `warm_up()` starts it at
|
||||||
// launch, but a fast wallet-open can beat the cold mixnet bootstrap — and
|
// launch, but a fast wallet-open can beat the cold mixnet bootstrap — and
|
||||||
@@ -990,13 +954,6 @@ async fn run_service(svc: Arc<NostrService>, wallet: Wallet) {
|
|||||||
if let Err(e) = client.add_relay(relay.clone()).await {
|
if let Err(e) = client.add_relay(relay.clone()).await {
|
||||||
warn!("nostr: add relay {relay} failed: {e}");
|
warn!("nostr: add relay {relay} failed: {e}");
|
||||||
}
|
}
|
||||||
// The general (clearnet) client publishes our identity to the same
|
|
||||||
// advertised set — add them here too so kinds 0/10002/10050 go direct,
|
|
||||||
// off the metered exit. Discovery indexers are added later in the
|
|
||||||
// publish_identity fan-out (also on the general client).
|
|
||||||
if let Err(e) = general.add_relay(relay.clone()).await {
|
|
||||||
warn!("nostr: add general relay {relay} failed: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// The tunnel generation these relays are being dialed on. If the exit is
|
// The tunnel generation these relays are being dialed on. If the exit is
|
||||||
// later reselected (generation bumped by nymproc), the status loop drops
|
// later reselected (generation bumped by nymproc), the status loop drops
|
||||||
@@ -1004,11 +961,9 @@ async fn run_service(svc: Arc<NostrService>, wallet: Wallet) {
|
|||||||
let mut dial_gen = crate::nym::tunnel_generation();
|
let mut dial_gen = crate::nym::tunnel_generation();
|
||||||
let connect_started = std::time::Instant::now();
|
let connect_started = std::time::Instant::now();
|
||||||
client.connect().await;
|
client.connect().await;
|
||||||
// Bring up the clearnet general client too (direct, no tunnel wait needed).
|
|
||||||
general.connect().await;
|
|
||||||
{
|
{
|
||||||
*svc.client.write() = Some(client.clone());
|
let mut w_client = svc.client.write();
|
||||||
*svc.general_client.write() = Some(general.clone());
|
*w_client = Some(client.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log when the first relay reaches Connected over the mixnet, measured from
|
// Log when the first relay reaches Connected over the mixnet, measured from
|
||||||
@@ -1061,10 +1016,8 @@ async fn run_service(svc: Arc<NostrService>, wallet: Wallet) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish identity events (kind 10050 DM relays; kind 0 only when named) on the
|
// Publish identity events (kind 10050 DM relays; kind 0 only when named).
|
||||||
// general (clearnet) client — these are public replaceable events, kept off the
|
publish_identity(&svc, &client).await;
|
||||||
// metered money-path exit.
|
|
||||||
publish_identity(&svc, &general).await;
|
|
||||||
|
|
||||||
// Catch-up + live subscription for our gift wraps — targeted at our OWN
|
// Catch-up + live subscription for our gift wraps — targeted at our OWN
|
||||||
// advertised set only. A pool-wide subscription would be inherited by
|
// advertised set only. A pool-wide subscription would be inherited by
|
||||||
@@ -1230,11 +1183,10 @@ async fn run_service(svc: Arc<NostrService>, wallet: Wallet) {
|
|||||||
// idle tunnel isn't condemned for "no relay" once we stop dialing.
|
// idle tunnel isn't condemned for "no relay" once we stop dialing.
|
||||||
crate::nym::set_relay_consumer(false);
|
crate::nym::set_relay_consumer(false);
|
||||||
{
|
{
|
||||||
*svc.client.write() = None;
|
let mut w_client = svc.client.write();
|
||||||
*svc.general_client.write() = None;
|
*w_client = None;
|
||||||
}
|
}
|
||||||
client.disconnect().await;
|
client.disconnect().await;
|
||||||
general.disconnect().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add + dial every relay in `urls` so a targeted send reaches relays we don't
|
/// Add + dial every relay in `urls` so a targeted send reaches relays we don't
|
||||||
|
|||||||
Reference in New Issue
Block a user