heddle_object_model/object/thread_replication/
ownership_claim.rs1use std::collections::BTreeSet;
4
5use serde::{Deserialize, Serialize};
6
7use super::{GenesisOwner, SourceAuthor, ThreadGenesis, bounded, invalid};
8use crate::{error::Result, object::ContentHash};
9
10pub const FORMAT: &str = "heddle-thread-ownership-claim-v1";
11pub const METHOD: &str = "/heddle.api.v1alpha2.ThreadService/ClaimThreadOwnership";
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct ThreadOwnershipClaim {
16 pub version: u16,
17 pub thread: ContentHash,
18 pub prior_local_key: [u8; 32],
19 pub accepting_publisher: [u8; 32],
20 pub acceptance: SourceAuthor,
21 pub source_frontier: BTreeSet<ContentHash>,
23}
24impl ThreadOwnershipClaim {
25 pub fn encode(&self) -> Result<Vec<u8>> {
26 self.acceptance.validate()?;
27 if self.version != 1
28 || self.prior_local_key == [0; 32]
29 || self.accepting_publisher == [0; 32]
30 || self.source_frontier.len() > 128
31 || !matches!(self.acceptance, SourceAuthor::Account { .. })
32 {
33 return Err(invalid("invalid explicit Thread ownership claim"));
34 }
35 let bytes = rmp_serde::to_vec_named(self)?;
36 bounded(&bytes)?;
37 Ok(bytes)
38 }
39 pub fn decode(bytes: &[u8]) -> Result<Self> {
40 bounded(bytes)?;
41 let value: Self = rmp_serde::from_slice(bytes)?;
42 if value.encode()? != bytes {
43 return Err(invalid("noncanonical Thread ownership claim"));
44 }
45 Ok(value)
46 }
47 pub fn id(&self) -> Result<ContentHash> {
48 Ok(ContentHash::compute_typed(FORMAT, &self.encode()?))
49 }
50 pub fn validate_genesis(&self, genesis: &ThreadGenesis) -> Result<()> {
51 self.encode()?;
52 let SourceAuthor::Account { spool, .. } = &self.acceptance else {
53 return Err(invalid("Thread claim requires account acceptance"));
54 };
55 if self.thread != genesis.id()?
56 || genesis.spool != spool.to_string()
57 || genesis.owner != GenesisOwner::LocalKey(self.prior_local_key)
58 {
59 return Err(invalid(
60 "Thread claim differs from immutable local ownership",
61 ));
62 }
63 Ok(())
64 }
65 pub fn account(&self) -> Result<uuid::Uuid> {
66 let SourceAuthor::Account { actor, .. } = &self.acceptance else {
67 return Err(invalid("Thread claim requires account acceptance"));
68 };
69 Ok(actor.principal_id)
70 }
71}