nvpn 4.1.5

CLI and daemon for Nostr VPN private mesh networks
Documentation
use super::*;

pub(crate) fn reconcile_automatic_paid_exit_selection(
    automatic: &mut PaidExitAutomaticBuyer,
    app: &mut AppConfig,
    config_path: &Path,
    now_unix: u64,
) -> Result<bool> {
    automatic.cancel_if_disabled(app);
    if !PaidExitAutomaticBuyer::enabled(app) {
        return Ok(false);
    }

    let store_path = paid_route_store_file_path(config_path);
    let mut store = load_paid_route_store(&store_path)?;
    let selection = match automatic.selection(&store, now_unix) {
        Ok(selection) => selection,
        Err(_) => {
            if let Some(candidate) = automatic.candidate.as_mut() {
                candidate.failed = true;
            }
            return Ok(false);
        }
    };
    if let Some(candidate) = automatic.candidate.as_mut() {
        if candidate.selection != selection {
            candidate.failed = true;
        }
        return Ok(false);
    }

    if let Some((seller_npub, seller_pubkey, session_id, funded)) =
        recover_automatic_paid_exit_session(&store, &selection, now_unix)
    {
        let _client_store_guard = if funded {
            let Some(guard) = try_lock_paid_exit_cashu_client_store(config_path) else {
                return Ok(false);
            };
            Some(guard)
        } else {
            None
        };
        let route_changed =
            app.public_paid_exit_node_pubkey_hex().as_deref() != Some(seller_pubkey.as_str());
        app.select_public_paid_exit_node(&seller_npub)?;
        if !PaidExitAutomaticBuyer::enabled(app) {
            return Err(anyhow!(
                "automatic paid exit recovery changed internet mode"
            ));
        }
        if route_changed {
            app.save(config_path)?;
        }
        if funded {
            queue_recovered_automatic_channel_open(
                app,
                config_path,
                &mut store,
                &session_id,
                now_unix,
            )?;
        }
        automatic.start_candidate(selection, seller_pubkey, session_id, funded, now_unix);
        return Ok(route_changed);
    }

    let buyer_npub = app
        .nostr_keys()?
        .public_key()
        .to_bech32()
        .context("failed to encode automatic paid exit buyer npub")?;
    let session = store.open_buyer_session(OpenPaidRouteBuyerSessionRequest {
        offer_selector: selection.offer_key.clone(),
        buyer_npub,
        mint_url: Some(selection.mint_url.clone()),
        channel_capacity_sat: Some(selection.channel_capacity_sat),
        initial_paid_msat: 0,
        now_unix,
    })?;
    let seller_pubkey = normalize_nostr_pubkey(&session.seller_npub)
        .context("invalid automatically selected paid exit seller")?;
    if !PaidExitAutomaticBuyer::enabled(app) {
        return Ok(false);
    }
    app.select_public_paid_exit_node(&session.seller_npub)?;
    if !PaidExitAutomaticBuyer::enabled(app) {
        return Err(anyhow!(
            "automatic paid exit selection changed internet mode"
        ));
    }
    if session.changed {
        write_paid_route_store(&store_path, &store)?;
    }
    app.save(config_path)?;
    automatic.start_candidate(
        selection,
        seller_pubkey,
        session.session_id,
        false,
        now_unix,
    );
    Ok(true)
}

fn recover_automatic_paid_exit_session(
    store: &PaidRouteStore,
    selection: &nostr_vpn_core::paid_route_store::PaidRouteAutomaticOfferSelection,
    now_unix: u64,
) -> Option<(String, String, String, bool)> {
    let offer = &store.offers.get(&selection.offer_key)?.offer;
    let seller_pubkey = normalize_nostr_pubkey(&offer.seller_npub).ok()?;
    store
        .sessions
        .values()
        .filter_map(|session| {
            let channel = store.channels.get(&session.session.payment.channel_id)?;
            let lease = store.leases.get(&session.session.lease_id)?;
            (channel.role == PaidRouteChannelRole::Buyer
                && channel.offer_id == offer.offer_id
                && channel.counterparty_npub == offer.seller_npub
                && channel.expires_at_unix > now_unix
                && lease.lease.expires_at_unix > now_unix
                && matches!(
                    lease.status,
                    PaidRouteLifecycleStatus::Opening
                        | PaidRouteLifecycleStatus::Probing
                        | PaidRouteLifecycleStatus::Active
                        | PaidRouteLifecycleStatus::Paused
                )
                && matches!(
                    channel.status,
                    PaidRouteLifecycleStatus::Opening
                        | PaidRouteLifecycleStatus::Probing
                        | PaidRouteLifecycleStatus::Active
                        | PaidRouteLifecycleStatus::Paused
                ))
            .then_some((
                session.updated_at_unix,
                session.session.session_id.clone(),
                session.session.payment.cashu_spilman_payment.is_some(),
            ))
        })
        .max_by_key(|candidate| candidate.0)
        .map(|(_, session_id, funded)| {
            (offer.seller_npub.clone(), seller_pubkey, session_id, funded)
        })
}