heddle_thread_api/replication/
native.rs1use std::{collections::BTreeSet, path::PathBuf, sync::Arc};
3
4use heddle_object_model::object::{
5 ContentHash,
6 thread_replication::{Admission, ThreadFacet},
7};
8use objects::store::ObjectStore;
9use repo::thread_replication::ThreadReplica;
10
11use super::store::{ReceivedOperation, ReplicaStore};
12
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15 #[error(transparent)]
16 Store(#[from] repo::thread_replication::Error),
17 #[error("local replica worker: {0}")]
18 Worker(#[from] tokio::task::JoinError),
19}
20pub struct LocalReplica<S> {
21 replica: ThreadReplica,
22 objects: Arc<S>,
23 authority_home: Option<PathBuf>,
24}
25impl<S> Clone for LocalReplica<S> {
26 fn clone(&self) -> Self {
27 Self {
28 replica: self.replica.clone(),
29 objects: self.objects.clone(),
30 authority_home: self.authority_home.clone(),
31 }
32 }
33}
34impl<S: ObjectStore + Send + Sync + 'static> LocalReplica<S> {
35 pub fn new(replica: ThreadReplica, objects: Arc<S>) -> Self {
36 Self {
37 replica,
38 objects,
39 authority_home: None,
40 }
41 }
42 pub fn with_device_authority(mut self, home: PathBuf) -> Self {
45 self.authority_home = Some(home);
46 self
47 }
48 async fn execute<T: Send + 'static>(
49 &self,
50 operation: impl FnOnce(&ThreadReplica, &S) -> repo::thread_replication::Result<T>
51 + Send
52 + 'static,
53 ) -> Result<T, Error> {
54 let local = self.clone();
55 Ok(
56 tokio::task::spawn_blocking(move || operation(&local.replica, &local.objects))
57 .await??,
58 )
59 }
60}
61impl<S: ObjectStore + Send + Sync + 'static> ReplicaStore for LocalReplica<S> {
62 type Error = Error;
63 fn thread_id(&self) -> ContentHash {
64 self.replica.thread_id()
65 }
66 async fn generation(&self) -> Result<i64, Error> {
67 self.execute(|replica, _| replica.generation()).await
68 }
69 async fn sharing(&self, destination: [u8; 32]) -> Result<BTreeSet<ThreadFacet>, Error> {
70 self.execute(move |replica, _| replica.sharing(&destination).map(|(facets, _)| facets))
71 .await
72 }
73 async fn frontier_page(
74 &self,
75 facet: ThreadFacet,
76 after: Option<ContentHash>,
77 limit: usize,
78 ) -> Result<Vec<ContentHash>, Error> {
79 self.execute(move |replica, _| replica.frontier_page(facet, after, limit))
80 .await
81 }
82 async fn operation(
83 &self,
84 id: ContentHash,
85 ) -> Result<Option<(ReceivedOperation, Admission)>, Error> {
86 self.execute(move |replica, _| {
87 Ok(replica
88 .operation_with_authority_admission(&id)?
89 .map(|stored| {
90 (
91 ReceivedOperation {
92 original: stored.original,
93 authority_admission: stored.authority_admission,
94 },
95 stored.status,
96 )
97 }))
98 })
99 .await
100 }
101 async fn receive(&self, received: ReceivedOperation) -> Result<Admission, Error> {
102 let authority_home = self.authority_home.clone();
103 self.execute(move |replica, objects| {
104 let operation = received.original;
105 if let Some(receipt) = received.authority_admission {
106 return replica.receive_with_authority_admission(&operation, &receipt, objects, |_| Ok(()));
107 }
108 replica.receive(&operation, objects, |native| {
109 use heddle_object_model::object::thread_replication::ThreadOperationBody;
110 if !matches!(native.body, ThreadOperationBody::Metadata(_)) && native.source_author()?.is_none() {
111 return Ok(());
112 }
113 if replica.original_authority_admitted(&operation)? {
117 return Ok(());
118 }
119 let genesis = replica.genesis()?;
120 if matches!(native.source_author()?, Some(heddle_object_model::object::thread_replication::SourceAuthor::LocalKey)) {
121 return replica.verify_local_source_owner(native);
122 }
123 let home = authority_home.as_ref().ok_or_else(|| {
124 repo::thread_replication::Error::Invalid(
125 "original operation requires independently enrolled account authority"
126 .into(),
127 )
128 })?;
129 let now = chrono::Utc::now().timestamp();
130 let authority = repo::device_authority::load(home, now).map_err(authority_error)?;
131 if let ThreadOperationBody::Metadata(bytes) = &native.body {
132 heddle_object_model::object::thread_replication::metadata::ThreadControl::decode(bytes)?.validate_parents(&genesis, &[])?;
133 }
134 let spool = genesis.spool.parse().map_err(authority_error)?;
135 let registered =
136 repo::device_catalog::load(home, spool).map_err(authority_error)?;
137 if native.source_author()?.is_some() {
138 replica.verify_source_authority(native, &authority, ®istered.capability_path, now)
139 } else {
140 repo::thread_replication::metadata::verify_control_authority(native, &authority, ®istered.capability_path, now)
141 }
142
143 })
144 })
145 .await
146 }
147 async fn remember_peer_heads(
148 &self,
149 peer: [u8; 32],
150 heads: Vec<(ThreadFacet, ContentHash)>,
151 ) -> Result<(), Error> {
152 self.execute(move |replica, _| replica.remember_peer_heads(peer, &heads))
153 .await
154 }
155 async fn record_peer_receipt(
156 &self,
157 peer: [u8; 32],
158 id: ContentHash,
159 admission: Admission,
160 ) -> Result<(), Error> {
161 self.execute(move |replica, _| replica.record_peer_receipt(peer, id, &admission))
162 .await
163 }
164 async fn settled_peer_heads(
165 &self,
166 peer: [u8; 32],
167 facets: BTreeSet<ThreadFacet>,
168 limit: usize,
169 ) -> Result<Vec<(ContentHash, Admission)>, Error> {
170 self.execute(move |replica, _| replica.settled_peer_heads(peer, &facets, limit))
171 .await
172 }
173 async fn needed_from_peer(
174 &self,
175 peer: [u8; 32],
176 facets: BTreeSet<ThreadFacet>,
177 limit: usize,
178 ) -> Result<Vec<ContentHash>, Error> {
179 self.execute(move |replica, _| replica.needed_from_peer(peer, &facets, limit))
180 .await
181 }
182}
183
184fn authority_error(error: impl std::fmt::Display) -> repo::thread_replication::Error {
185 repo::thread_replication::Error::Invalid(error.to_string())
186}