community: one writer at a time per ledger, and an inbox publish that bumps from what the network really holds

This commit is contained in:
KeelrLeek committed 2026-09-03 11:12:41 -05:00
1 parent 0846b39d45
commit 733e3b57a8
3 files changed
+129 -5

No files matched your search

+53 -5
View File
@@ -30,6 +30,7 @@ use persona_state::inbox as ib;
use serde::{Deserialize, Serialize};
use tauri::{Emitter, Manager};
use crate::ledger;
use crate::persona;
use crate::state::AppState;
@@ -147,17 +148,20 @@ fn dir(app: &tauri::AppHandle) -> Result<PathBuf, String> {
.map_err(|e| e.to_string())?
.join("freenet"))
}
/// Both the file the ledger lives in and the key its write lock is taken on.
const LEDGER_FILE: &str = "inbox-ledger.json";
fn load(app: &tauri::AppHandle) -> Ledger {
dir(app)
.ok()
.and_then(|d| std::fs::read(d.join("inbox-ledger.json")).ok())
.and_then(|d| std::fs::read(d.join(LEDGER_FILE)).ok())
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default()
}
fn save(app: &tauri::AppHandle, l: &Ledger) -> Result<(), String> {
let d = dir(app)?;
std::fs::create_dir_all(&d).map_err(|e| e.to_string())?;
std::fs::write(d.join("inbox-ledger.json"), serde_json::to_vec_pretty(l).map_err(|e| e.to_string())?)
std::fs::write(d.join(LEDGER_FILE), serde_json::to_vec_pretty(l).map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())
}
@@ -204,13 +208,37 @@ pub fn status(app: &tauri::AppHandle) -> InboxStatus {
/// Publish (or re-publish) how to reach this persona. Minting the box key
/// and publishing the descriptor is what makes the inbox exist.
pub async fn publish(app: &tauri::AppHandle) -> Result<InboxStatus, String> {
let _ledger = ledger::hold(LEDGER_FILE).await;
let adapter = app.state::<AppState>().freenet.clone();
let owner = own_key(app).await?;
// Whether we already had a box key, read before minting one.
let had_key = box_secret(app, false)?.is_some();
let secret = box_secret(app, true)?.expect("minted");
let mut l = load(app);
let first = l.desc_seq == 0;
let seq = l.desc_seq + 1;
let key_id = if l.box_key_id == 0 { 1 } else { l.box_key_id };
// A box key with no sequence is the signature of a ledger that lost a
// write: the key is minted inside this function, just before the put. Ask
// the network what it holds rather than trusting the zero, or we would
// re-put an existing contract at a sequence it already has and the
// descriptor would never move again.
let published = if l.desc_seq == 0 && had_key {
published_descriptor(app, &owner).await?
} else {
None
};
if let Some(p) = &published {
log::warn!(
"inbox: ledger said never published but the network holds sequence {} — bumping from there",
p.sequence
);
}
let net_seq = published.as_ref().map(|p| p.sequence).unwrap_or(0);
let seq = l.desc_seq.max(net_seq) + 1;
let first = seq == 1;
let key_id = match (l.box_key_id, published.as_ref().map(|p| p.box_key_id)) {
(0, Some(id)) if id > 0 => id,
(0, _) => 1,
(id, _) => id,
};
let mut d = ib::InboxDescriptor {
schema_version: ib::SCHEMA_VERSION,
owner,
@@ -280,6 +308,25 @@ pub async fn resolve(app: &tauri::AppHandle, key: &[u8; 32]) -> Option<ib::Inbox
desc
}
/// Our own descriptor exactly as the network holds it. Deliberately skips the
/// resolve cache: the whole point is to catch a ledger that lost a write.
async fn published_descriptor(
app: &tauri::AppHandle,
owner: &[u8; 32],
) -> Result<Option<ib::InboxDescriptor>, String> {
let adapter = app.state::<AppState>().freenet.clone();
let ids = FreenetAdapter::contract_ids(DESC_WASM.to_vec(), &ib::make_desc_params(owner));
match adapter.contract_get(&ids.instance, false).await {
Ok(bytes) => Ok(ib::validate_descriptor(owner, &bytes)),
// The contract genuinely is not out there: this really is a first put.
Err(e) if e.to_string().contains("not found") => Ok(None),
// Anything else means we could not ask. Refusing is the module rule —
// guessing "nothing" here re-puts an existing descriptor at a sequence
// the network already holds, and it would never move again.
Err(e) => Err(format!("could not read the published descriptor: {e}")),
}
}
/// True when we can reach `key` through Freenet right now.
pub async fn reachable(app: &tauri::AppHandle, key: &[u8; 32]) -> bool {
resolve(app, key).await.is_some()
@@ -340,6 +387,7 @@ pub struct CheckResult {
/// Read our inbox, open what we can, act on it, prune what we handled.
pub async fn check(app: &tauri::AppHandle) -> Result<CheckResult, String> {
let _ledger = ledger::hold(LEDGER_FILE).await;
let adapter = app.state::<AppState>().freenet.clone();
let owner = own_key(app).await?;
let Some(secret) = box_secret(app, false)? else {