community: spaces, an owner-signed roster with epochs, a bounded window of signed lines, bodies sealed to a rotating key when private

This commit is contained in:
KeelrLeek committed 2026-09-02 15:50:18 -05:00
1 parent 4378c4c0cc
commit 8b511b20e7
14 files changed
+2913 -13

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
//! The SoulStream Space contract. Multi-writer: the owner offers
//! checkpoints, members offer messages; `persona_state::space::merge_space`
//! is the bouncer (owner signature, roster, dedup, per-author fairness,
//! the window). Deterministic by construction, so every delivery order
//! converges; a state that does not merge to itself is refused.
//!
//! Build: `cargo build --release --target wasm32-unknown-unknown` in this
//! directory; copy `target/wasm32-unknown-unknown/release/
//! soulstream_space_contract.wasm` to `src-tauri/assets/space_contract.wasm`
//! and re-pin the code hash in `src-tauri/src/spaces.rs`.
use freenet_stdlib::prelude::*;
use persona_state::space as t;
pub struct Contract;
fn parts(parameters: &Parameters<'static>) -> Result<([u8; 32], [u8; 16]), ContractError> {
t::params_parts(parameters.as_ref())
.ok_or_else(|| ContractError::Other("params are not family+owner+space".into()))
}
fn collect<'a>(state: &'a State<'static>, data: &'a [UpdateData<'static>]) -> (Vec<&'a [u8]>, bool) {
let mut candidates: Vec<&[u8]> = Vec::new();
if !state.as_ref().is_empty() {
candidates.push(state.as_ref());
}
let mut any_parse = false;
let mut push = |b: &'a [u8]| {
if !b.is_empty() {
any_parse |= bincode::deserialize::<t::SpaceState>(b).is_ok();
candidates.push(b);
}
};
for ud in data {
match ud {
UpdateData::State(s) => push(s.as_ref()),
UpdateData::Delta(d) => push(d.as_ref()),
UpdateData::StateAndDelta { state, delta } => {
push(state.as_ref());
push(delta.as_ref());
}
_ => {}
}
}
(candidates, any_parse)
}
#[contract]
impl ContractInterface for Contract {
fn validate_state(
parameters: Parameters<'static>,
state: State<'static>,
_related: RelatedContracts<'static>,
) -> Result<ValidateResult, ContractError> {
let (owner, space_id) = parts(&parameters)?;
let bytes = state.as_ref();
if bytes.is_empty() {
return Ok(ValidateResult::Valid);
}
if bytes.len() > t::MAX_STATE_BYTES {
return Err(ContractError::Other(format!(
"space state {} bytes over cap {}",
bytes.len(),
t::MAX_STATE_BYTES
)));
}
let merged = t::merge_space(&owner, &space_id, &[bytes]);
let canon = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?;
if canon != bytes {
return Err(ContractError::Other(
"state is not canonical (unsigned, off-roster, or over the window)".into(),
));
}
Ok(ValidateResult::Valid)
}
fn update_state(
parameters: Parameters<'static>,
state: State<'static>,
data: Vec<UpdateData<'static>>,
) -> Result<UpdateModification<'static>, ContractError> {
let (owner, space_id) = parts(&parameters)?;
let (candidates, any_parse) = collect(&state, &data);
if !any_parse && !data.is_empty() {
return Err(ContractError::InvalidUpdate);
}
let merged = t::merge_space(&owner, &space_id, &candidates);
let bytes = bincode::serialize(&merged).map_err(|e| ContractError::Deser(e.to_string()))?;
Ok(UpdateModification::valid(State::from(bytes)))
}
fn summarize_state(
parameters: Parameters<'static>,
state: State<'static>,
) -> Result<StateSummary<'static>, ContractError> {
let _ = parts(&parameters)?;
if state.as_ref().is_empty() {
return Ok(StateSummary::from(Vec::new()));
}
Ok(StateSummary::from(blake3::hash(state.as_ref()).as_bytes().to_vec()))
}
fn get_state_delta(
parameters: Parameters<'static>,
state: State<'static>,
summary: StateSummary<'static>,
) -> Result<StateDelta<'static>, ContractError> {
let _ = parts(&parameters)?;
if state.as_ref().is_empty() {
return Ok(StateDelta::from(Vec::new()));
}
if summary.as_ref() == blake3::hash(state.as_ref()).as_bytes() {
Ok(StateDelta::from(Vec::new()))
} else {
Ok(StateDelta::from(state.as_ref().to_vec()))
}
}
}