repo/thread_replication/
prefix.rs1use std::collections::BTreeSet;
4
5use api::{
6 heddle::api::v1alpha2 as wire,
7 hybrid_codec::{self, Reject},
8 import_authority as import,
9};
10use prost::Message;
11
12use super::authority::PublicProof;
13
14impl PublicProof {
15 pub fn foreign_dependencies(&self) -> &[wire::ForeignDependencyV1] {
16 match self {
17 Self::Import(b) => &b.foreign_dependencies,
18 Self::Native(b) => &b.foreign_dependencies,
19 }
20 }
21
22 pub fn foreign_original(
23 &self,
24 reference: &wire::ForeignDependencyV1,
25 ) -> Result<&wire::SignedRecord, Reject> {
26 let (authority, landing) = match self {
27 Self::Import(b) => (&b.authority_witnesses, &b.landing_witnesses),
28 Self::Native(b) => (&b.authority_witnesses, &b.landing_witnesses),
29 };
30 authority
31 .iter()
32 .flat_map(|p| &p.dependencies)
33 .chain(
34 landing
35 .iter()
36 .flat_map(|p| p.source_operation.iter().chain(&p.review_evidence)),
37 )
38 .find(|r| {
39 import::signed_native_digest(r).is_ok_and(|d| d == reference.signed_native_digest)
40 })
41 .ok_or(Reject::Scope)
42 }
43 pub fn prefix_original(
44 &self,
45 reference: &wire::ForeignDependencyV1,
46 ) -> Result<&wire::SignedRecord, Reject> {
47 let (genesis, authority, landing) = match self {
48 Self::Import(b) => (
49 b.genesis_witnesses
50 .iter()
51 .filter_map(|p| p.original_genesis.as_ref())
52 .collect::<Vec<_>>(),
53 &b.authority_witnesses,
54 &b.landing_witnesses,
55 ),
56 Self::Native(b) => (
57 b.genesis_witnesses
58 .iter()
59 .filter_map(|p| p.original_genesis.as_ref())
60 .collect::<Vec<_>>(),
61 &b.authority_witnesses,
62 &b.landing_witnesses,
63 ),
64 };
65 genesis
66 .iter()
67 .copied()
68 .chain(
69 authority
70 .iter()
71 .flat_map(|p| p.original.iter().chain(&p.dependencies)),
72 )
73 .chain(landing.iter().flat_map(|p| {
74 p.execution
75 .iter()
76 .chain(p.source_operation.iter())
77 .chain(&p.review_evidence)
78 }))
79 .find(|r| {
80 import::signed_native_digest(r).is_ok_and(|d| d == reference.signed_native_digest)
81 })
82 .ok_or(Reject::Scope)
83 }
84
85 fn require_boundary_originals(&self) -> Result<(), Reject> {
88 use objects::object::{
89 original_boundary_acceptance::{
90 OriginalBoundaryAcceptance, OriginalPublicationManifest, PublicationIntent,
91 },
92 thread_replication::{
93 self as native, ownership_claim::ThreadOwnershipClaim,
94 ownership_resolution::ThreadOwnershipResolution,
95 },
96 };
97 let (geneses, authority, landing, boundaries) = match self {
98 Self::Native(b) => (
99 b.genesis_witnesses
100 .iter()
101 .filter_map(|p| p.original_genesis.as_ref())
102 .collect::<Vec<_>>(),
103 &b.authority_witnesses,
104 &b.landing_witnesses,
105 b.genesis_witnesses
106 .iter()
107 .filter_map(|p| p.boundary_acceptance.as_ref())
108 .collect::<Vec<_>>(),
109 ),
110 Self::Import(b) => (
111 b.genesis_witnesses
112 .iter()
113 .filter_map(|p| p.original_genesis.as_ref())
114 .collect::<Vec<_>>(),
115 &b.authority_witnesses,
116 &b.landing_witnesses,
117 b.genesis_witnesses
118 .iter()
119 .filter_map(|p| p.boundary_acceptance.as_ref())
120 .collect::<Vec<_>>(),
121 ),
122 };
123 let originals = geneses
124 .into_iter()
125 .chain(
126 authority
127 .iter()
128 .flat_map(|p| p.original.iter().chain(&p.dependencies)),
129 )
130 .chain(landing.iter().flat_map(|p| {
131 p.execution
132 .iter()
133 .chain(p.source_operation.iter())
134 .chain(&p.review_evidence)
135 }));
136 let mut ids = BTreeSet::new();
137 for original in originals {
138 let id = match original.format.as_str() {
139 native::GENESIS_FORMAT => crypto::import_authority::verify_native_genesis(original)
140 .map_err(|_| Reject::Scope)?
141 .1
142 .id(),
143 native::OPERATION_FORMAT => {
144 crypto::import_authority::verify_native_operation(original)
145 .map_err(|_| Reject::Scope)?
146 .1
147 .id()
148 }
149 native::ownership_claim::FORMAT => {
150 ThreadOwnershipClaim::decode(&original.canonical_record)
151 .map_err(|_| Reject::Scope)?
152 .id()
153 }
154 native::ownership_resolution::FORMAT => {
155 ThreadOwnershipResolution::decode(&original.canonical_record)
156 .map_err(|_| Reject::Scope)?
157 .id()
158 }
159 _ => continue,
160 }
161 .map_err(|_| Reject::Scope)?;
162 ids.insert(id);
163 }
164 for boundary in boundaries
165 .into_iter()
166 .chain(authority.iter().flat_map(|p| &p.boundary_acceptances))
167 {
168 let acceptance = OriginalBoundaryAcceptance::decode(
169 &boundary
170 .signed_acceptance
171 .as_ref()
172 .ok_or(Reject::Scope)?
173 .canonical_record,
174 )
175 .map_err(|_| Reject::Scope)?;
176 let manifest = OriginalPublicationManifest::decode(&boundary.originals_manifest)
177 .map_err(|_| Reject::Scope)?;
178 let intent = PublicationIntent::decode(&boundary.publication_intent)
179 .map_err(|_| Reject::Scope)?;
180 if acceptance
181 .selected(&intent, &manifest)
182 .map_err(|_| Reject::Scope)?
183 .iter()
184 .any(|entry| !ids.contains(&entry.subject.id()))
185 {
186 return Err(Reject::Scope);
187 }
188 }
189 Ok(())
190 }
191
192 pub fn prefix(&self, reference: &wire::ForeignDependencyV1) -> Result<Self, Reject> {
196 if reference.format_version != 1
197 || reference.prefix_admission_order == 0
198 || reference.thread_genesis_digest.len() != 32
199 || reference.signed_native_digest.len() != 32
200 {
201 return Err(Reject::Scope);
202 }
203 let cutoff = reference.prefix_admission_order;
204 let mut projected = self.clone();
205 match &mut projected {
206 Self::Native(b) if reference.origin == 2 => {
207 api::native_witness::validate_public_bundle(b)?;
208 b.statements
209 .retain(|s| s.body.as_ref().is_some_and(|s| s.admission_order <= cutoff));
210 let payloads = payloads(&b.statements)?;
211 retain(&mut b.genesis_witnesses, &payloads)?;
212 retain(&mut b.authority_witnesses, &payloads)?;
213 retain(&mut b.landing_witnesses, &payloads)?;
214 retain_foreign(
215 &mut b.foreign_dependencies,
216 &b.authority_witnesses,
217 &b.landing_witnesses,
218 )?;
219 api::native_witness::validate_public_bundle(b)?;
220 }
221 Self::Import(b) if reference.origin == 1 => {
222 import::validate_public_bundle(b)?;
223 b.statements
224 .retain(|s| s.body.as_ref().is_some_and(|s| s.admission_order <= cutoff));
225 let payloads = payloads(&b.statements)?;
226 retain(&mut b.genesis_witnesses, &payloads)?;
227 retain(&mut b.authority_witnesses, &payloads)?;
228 retain(&mut b.landing_witnesses, &payloads)?;
229 let mut selected = BTreeSet::new();
230 let mut manifests = BTreeSet::new();
231 for operation in &b.operations {
232 for manifest in &b.manifests {
233 let publication = import::publication_payload(operation, manifest)?;
234 if payloads.contains(&hybrid_codec::canonical(&publication)?) {
235 selected.insert(import::signed_operation_digest(operation)?);
236 manifests.insert(import::manifest_digest(manifest)?);
237 }
238 }
239 }
240 b.operations.retain(|o| {
241 import::signed_operation_digest(o).is_ok_and(|d| selected.contains(&d))
242 });
243 b.manifests
244 .retain(|m| import::manifest_digest(m).is_ok_and(|d| manifests.contains(&d)));
245 b.terminal_manifest = b.manifests.iter().max_by_key(|m| m.slots.len()).cloned();
246 retain_foreign(
247 &mut b.foreign_dependencies,
248 &b.authority_witnesses,
249 &b.landing_witnesses,
250 )?;
251 import::validate_public_bundle(b)?;
252 }
253 _ => return Err(Reject::Scope),
254 }
255 projected.require_boundary_originals()?;
256 Ok(projected)
257 }
258}
259fn payloads(
260 statements: &[api::heddle::api::common::SignedHostedWitnessStatementV1],
261) -> Result<BTreeSet<Vec<u8>>, Reject> {
262 statements
263 .iter()
264 .map(|s| {
265 Ok(s.body
266 .as_ref()
267 .ok_or(Reject::Canonical)?
268 .canonical_payload
269 .clone())
270 })
271 .collect()
272}
273fn retain<T: Message + hybrid_codec::Canonical>(
274 records: &mut Vec<T>,
275 payloads: &BTreeSet<Vec<u8>>,
276) -> Result<(), Reject> {
277 let mut keep = Vec::with_capacity(records.len());
278 for record in records.drain(..) {
279 if payloads.contains(&hybrid_codec::canonical(&record)?) {
280 keep.push(record);
281 }
282 }
283 *records = keep;
284 Ok(())
285}
286fn retain_foreign(
287 references: &mut Vec<wire::ForeignDependencyV1>,
288 authority: &[wire::ImportAuthorityWitnessV1],
289 landing: &[wire::HostedLandingWitnessV1],
290) -> Result<(), Reject> {
291 let used = authority
292 .iter()
293 .flat_map(|p| &p.dependencies)
294 .chain(
295 landing
296 .iter()
297 .flat_map(|p| p.source_operation.iter().chain(&p.review_evidence)),
298 )
299 .map(import::signed_native_digest)
300 .collect::<Result<BTreeSet<_>, _>>()?;
301 references.retain(|r| used.contains(&r.signed_native_digest));
302 Ok(())
303}