feat(wallet,content,seed): Fedimint dual-ecash, paid content streaming, seed ceremony
- Fedimint ecash alongside Cashu: fedimint-clientd (fmcd) HTTP bridge, fedimint_client, fedimint RPC, wallet wiring - Paid peer content: content invoices + streaming content server + content RPCs - Seed-phrase ceremony/reveal RPCs and CLI ceremony tool - LND wallet, mesh status/messaging, app-stack (netbird HTTPS), and decoupled-update wiring; Fedimint Client core app in catalog Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c10f2ac22e
commit
bd567cd165
@@ -17,6 +17,10 @@ pub struct IncomingMessage {
|
||||
/// Sender's node name (for display in group chat).
|
||||
#[serde(default)]
|
||||
pub from_name: Option<String>,
|
||||
/// Sender-assigned unique id for the message. Used to dedup reliably even
|
||||
/// when a slow-Tor retry/redelivery arrives outside the time window (#31).
|
||||
#[serde(default)]
|
||||
pub msg_id: Option<String>,
|
||||
pub message: String,
|
||||
pub timestamp: String,
|
||||
/// "sent" or "received"
|
||||
@@ -141,18 +145,34 @@ fn persist() {
|
||||
}
|
||||
|
||||
/// Store a received message (called from HTTP handler).
|
||||
pub fn store_received_sync(from_pubkey: &str, message: &str, from_name: Option<&str>) {
|
||||
pub fn store_received_sync(
|
||||
from_pubkey: &str,
|
||||
message: &str,
|
||||
from_name: Option<&str>,
|
||||
msg_id: Option<&str>,
|
||||
) {
|
||||
let ts = chrono::Utc::now().to_rfc3339();
|
||||
let mut guard = store().lock().unwrap_or_else(|e| e.into_inner());
|
||||
|
||||
// Deduplication: skip if same pubkey + message within last 30 seconds
|
||||
let dominated = guard.messages.iter().rev().take(20).any(|m| {
|
||||
m.from_pubkey == from_pubkey
|
||||
&& m.message == message
|
||||
&& m.direction == "received"
|
||||
&& within_seconds(&m.timestamp, &ts, 30)
|
||||
});
|
||||
if dominated {
|
||||
// Deduplication. When the sender supplied a unique id, dedup on
|
||||
// (from_pubkey, msg_id) across all retained history — this is robust even
|
||||
// when a slow-Tor redelivery arrives well outside any time window (#31).
|
||||
// Older senders send no id; fall back to the legacy same-pubkey+message
|
||||
// within-30s heuristic.
|
||||
let duplicate = if let Some(id) = msg_id {
|
||||
guard
|
||||
.messages
|
||||
.iter()
|
||||
.any(|m| m.from_pubkey == from_pubkey && m.msg_id.as_deref() == Some(id))
|
||||
} else {
|
||||
guard.messages.iter().rev().take(20).any(|m| {
|
||||
m.from_pubkey == from_pubkey
|
||||
&& m.message == message
|
||||
&& m.direction == "received"
|
||||
&& within_seconds(&m.timestamp, &ts, 30)
|
||||
})
|
||||
};
|
||||
if duplicate {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,6 +180,7 @@ pub fn store_received_sync(from_pubkey: &str, message: &str, from_name: Option<&
|
||||
from_pubkey: from_pubkey.to_string(),
|
||||
from_onion: None,
|
||||
from_name: from_name.map(|s| s.to_string()),
|
||||
msg_id: msg_id.map(|s| s.to_string()),
|
||||
message: message.to_string(),
|
||||
timestamp: ts,
|
||||
direction: "received".to_string(),
|
||||
@@ -169,8 +190,13 @@ pub fn store_received_sync(from_pubkey: &str, message: &str, from_name: Option<&
|
||||
persist();
|
||||
}
|
||||
|
||||
pub async fn store_received(from_pubkey: &str, message: &str, from_name: Option<&str>) {
|
||||
store_received_sync(from_pubkey, message, from_name);
|
||||
pub async fn store_received(
|
||||
from_pubkey: &str,
|
||||
message: &str,
|
||||
from_name: Option<&str>,
|
||||
msg_id: Option<&str>,
|
||||
) {
|
||||
store_received_sync(from_pubkey, message, from_name, msg_id);
|
||||
}
|
||||
|
||||
/// Store a sent message (for display in Archipelago channel).
|
||||
@@ -180,6 +206,7 @@ pub fn store_sent(message: &str) {
|
||||
from_pubkey: "me".to_string(),
|
||||
from_onion: None,
|
||||
from_name: None,
|
||||
msg_id: None,
|
||||
message: message.to_string(),
|
||||
timestamp: chrono::Utc::now().to_rfc3339(),
|
||||
direction: "sent".to_string(),
|
||||
@@ -335,6 +362,9 @@ pub async fn send_to_peer(
|
||||
"message": payload_message,
|
||||
"timestamp": chrono::Utc::now().to_rfc3339(),
|
||||
"encrypted": encrypted,
|
||||
// Unique per-message id so receivers can dedup reliably even across
|
||||
// slow-Tor retries/redeliveries (#31). Old receivers ignore it.
|
||||
"msg_id": uuid::Uuid::new_v4().to_string(),
|
||||
});
|
||||
if let Some(name) = from_name {
|
||||
body["from_name"] = serde_json::Value::String(name.to_string());
|
||||
|
||||
Reference in New Issue
Block a user