fix: prevent tokio runtime deadlock in credential issue/verify
The credential issuance and verification handlers used Handle::block_on() directly inside the tokio runtime, causing a deadlock. Wrapped with block_in_place() to properly yield the runtime thread. Also completed full feature verification across all 25 test groups (~175 checks) on live server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5ce8b7965c
commit
e3aa95a103
@@ -0,0 +1,114 @@
|
||||
//! Networking profit tracking.
|
||||
//!
|
||||
//! Aggregates earnings from content sales (ecash) and Lightning routing fees.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
use tracing::debug;
|
||||
|
||||
use super::ecash;
|
||||
|
||||
const PROFITS_FILE: &str = "wallet/profits.json";
|
||||
|
||||
/// Earnings breakdown by source.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ProfitsSummary {
|
||||
/// Total earnings in sats from all sources.
|
||||
pub total_sats: u64,
|
||||
/// Earnings from ecash content sales.
|
||||
pub content_sales_sats: u64,
|
||||
/// Earnings from Lightning routing fees.
|
||||
pub routing_fees_sats: u64,
|
||||
/// Recent earning entries (newest first).
|
||||
pub recent: Vec<ProfitEntry>,
|
||||
}
|
||||
|
||||
/// A single profit event.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProfitEntry {
|
||||
pub source: ProfitSource,
|
||||
pub amount_sats: u64,
|
||||
pub timestamp: String,
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProfitSource {
|
||||
ContentSale,
|
||||
RoutingFee,
|
||||
}
|
||||
|
||||
/// Load profits summary from disk.
|
||||
pub async fn load_profits(data_dir: &Path) -> Result<ProfitsSummary> {
|
||||
let path = data_dir.join(PROFITS_FILE);
|
||||
if !path.exists() {
|
||||
return Ok(ProfitsSummary::default());
|
||||
}
|
||||
let content = fs::read_to_string(&path)
|
||||
.await
|
||||
.context("Failed to read profits file")?;
|
||||
let summary: ProfitsSummary = serde_json::from_str(&content).unwrap_or_default();
|
||||
Ok(summary)
|
||||
}
|
||||
|
||||
/// Save profits summary to disk.
|
||||
pub async fn save_profits(data_dir: &Path, summary: &ProfitsSummary) -> Result<()> {
|
||||
let dir = data_dir.join("wallet");
|
||||
fs::create_dir_all(&dir)
|
||||
.await
|
||||
.context("Failed to create wallet dir")?;
|
||||
let path = data_dir.join(PROFITS_FILE);
|
||||
let content =
|
||||
serde_json::to_string_pretty(summary).context("Failed to serialize profits")?;
|
||||
fs::write(&path, content)
|
||||
.await
|
||||
.context("Failed to write profits file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Record a content sale profit.
|
||||
pub async fn record_content_sale(data_dir: &Path, amount_sats: u64, description: &str) -> Result<()> {
|
||||
let mut summary = load_profits(data_dir).await?;
|
||||
summary.total_sats += amount_sats;
|
||||
summary.content_sales_sats += amount_sats;
|
||||
summary.recent.insert(
|
||||
0,
|
||||
ProfitEntry {
|
||||
source: ProfitSource::ContentSale,
|
||||
amount_sats,
|
||||
timestamp: chrono::Utc::now().to_rfc3339(),
|
||||
description: description.to_string(),
|
||||
},
|
||||
);
|
||||
// Keep only the last 100 entries
|
||||
summary.recent.truncate(100);
|
||||
save_profits(data_dir, &summary).await?;
|
||||
debug!("Recorded content sale: {} sats", amount_sats);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute a full profits summary including ecash receive transactions.
|
||||
pub async fn get_networking_profits(data_dir: &Path) -> Result<ProfitsSummary> {
|
||||
let mut summary = load_profits(data_dir).await?;
|
||||
|
||||
// Also count ecash "receive" transactions as content sales revenue
|
||||
let wallet = ecash::load_wallet(data_dir).await?;
|
||||
let ecash_received: u64 = wallet
|
||||
.transactions
|
||||
.iter()
|
||||
.filter(|tx| matches!(tx.tx_type, ecash::TransactionType::Receive))
|
||||
.map(|tx| tx.amount_sats)
|
||||
.sum();
|
||||
|
||||
// Use the higher of tracked profits or ecash receives as content sales
|
||||
if ecash_received > summary.content_sales_sats {
|
||||
summary.content_sales_sats = ecash_received;
|
||||
}
|
||||
summary.total_sats = summary.content_sales_sats + summary.routing_fees_sats;
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
Reference in New Issue
Block a user