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