coven_protocol/remote_object/
graph.rs1use super::identity::*;
2use super::*;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct CandidateObjectMaterial {
6 pub object: ExactObjectRef,
7 pub canonical_semantic_bytes: Vec<u8>,
8 pub stored_bytes: Vec<u8>,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct CandidateObjectGraph {
16 family: CandidateFamilyId,
17 objects: Vec<CandidateExclusiveObjectDomain>,
18}
19
20impl CandidateObjectGraph {
21 pub fn from_commit(
22 commit: &crate::store_commit::StoreBatchCommit,
23 ) -> Result<Self, RemoteObjectRecordError> {
24 let manifest = commit.verified_candidate_objects()?;
25 let mut objects = Vec::new();
26 for candidate in &manifest.objects {
27 match candidate {
28 crate::store_commit::CandidateExclusiveObjectRef::StorePackage(reference) => {
29 objects.push(CandidateExclusiveObjectDomain::StorePackage {
30 reference: reference.clone(),
31 });
32 }
33 crate::store_commit::CandidateExclusiveObjectRef::CirclePackage(reference) => {
34 objects.push(CandidateExclusiveObjectDomain::CirclePackage {
35 reference: reference.clone(),
36 });
37 }
38 crate::store_commit::CandidateExclusiveObjectRef::CircleAccess {
39 circle_id,
40 access,
41 } => {
42 objects.push(CandidateExclusiveObjectDomain::CircleAccessLeaf {
43 family: manifest.family,
44 circle_id: *circle_id,
45 reference: access.leaf.clone(),
46 });
47 objects.push(CandidateExclusiveObjectDomain::CircleAccessEnvelope {
48 family: manifest.family,
49 circle_id: *circle_id,
50 reference: access.envelope.clone(),
51 });
52 if let Some(bootstrap) = &access.bootstrap {
53 objects.push(CandidateExclusiveObjectDomain::CircleBootstrapImage {
54 family: manifest.family,
55 circle_id: *circle_id,
56 owner_pubkey: access.leaf.owner_pubkey.clone(),
57 epoch_id: access.leaf.epoch_id,
58 recipient_slot: access.leaf.recipient_slot.clone(),
59 reference: bootstrap.clone(),
60 });
61 }
62 }
63 crate::store_commit::CandidateExclusiveObjectRef::CircleEpochCloseIntent {
64 circle_id,
65 reference,
66 } => {
67 objects.push(CandidateExclusiveObjectDomain::CircleEpochCloseIntent {
68 family: manifest.family,
69 circle_id: *circle_id,
70 reference: reference.clone(),
71 });
72 }
73 crate::store_commit::CandidateExclusiveObjectRef::CircleEpochCloseOutcome {
74 circle_id,
75 reference,
76 } => {
77 objects.push(CandidateExclusiveObjectDomain::CircleEpochCloseOutcome {
78 family: manifest.family,
79 circle_id: *circle_id,
80 reference: reference.clone(),
81 });
82 }
83 crate::store_commit::CandidateExclusiveObjectRef::CircleEpochCloseCancellation {
84 circle_id,
85 reference,
86 } => {
87 objects.push(CandidateExclusiveObjectDomain::CircleEpochCloseCancellation {
88 family: manifest.family,
89 circle_id: *circle_id,
90 reference: reference.clone(),
91 });
92 }
93 }
94 }
95 Ok(Self {
96 family: manifest.family,
97 objects,
98 })
99 }
100
101 pub fn exact_objects(&self) -> impl Iterator<Item = &ExactObjectRef> {
102 self.objects
103 .iter()
104 .map(CandidateExclusiveObjectDomain::object)
105 }
106
107 pub fn close(
108 self,
109 commit: &crate::store_commit::StoreBatchCommit,
110 owner: &StoreBatchCommitRef,
111 materials: Vec<CandidateObjectMaterial>,
112 ) -> Result<Vec<ClosedRemoteObject>, RemoteObjectRecordError> {
113 owner.verify_commit(commit)?;
114 if self.family != commit.candidate_family() {
115 return Err(RemoteObjectRecordError::DomainMismatch);
116 }
117 let mut exact = std::collections::BTreeMap::new();
118 for material in materials {
119 if exact.insert(material.object.clone(), material).is_some() {
120 return Err(RemoteObjectRecordError::DuplicateCandidateObject);
121 }
122 }
123 validate_access_pairs(&self.objects, &exact)?;
124 let mut records = Vec::with_capacity(self.objects.len());
125 for domain in self.objects {
126 let object = domain.object().clone();
127 let material = exact
128 .remove(&object)
129 .ok_or(RemoteObjectRecordError::CandidateObjectMissing)?;
130 let (semantic_hash, payloads) = match &domain {
131 CandidateExclusiveObjectDomain::CircleBootstrapImage { reference, .. } => {
132 (reference.image_hash, RemoteObjectPayloads::SpooledExternal)
133 }
134 _ => (
135 ObjectHash::digest(&material.canonical_semantic_bytes),
136 RemoteObjectPayloads::SpooledInline,
137 ),
138 };
139 let record = RemoteObjectRecord::CandidateExclusive(CandidateObjectRecord {
140 identity: CandidateExclusiveTarget {
141 family: domain.family(),
142 domain,
143 semantic_hash,
144 object,
145 },
146 payloads,
147 state: CandidateObjectState::Prepared {
148 ownership: PendingCandidateOwnership {
149 pending: BTreeSet::from([owner.clone()]),
150 nonactivated: Vec::new(),
151 },
152 },
153 });
154 record.validate_payload(&material.canonical_semantic_bytes)?;
155 records.push(ClosedRemoteObject::with_spooled_payloads(
156 record,
157 &material.canonical_semantic_bytes,
158 &material.stored_bytes,
159 )?);
160 }
161 if !exact.is_empty() {
162 return Err(RemoteObjectRecordError::CandidateObjectInvented);
163 }
164 Ok(records)
165 }
166}