heddle_thread_api/publication/
staging.rs1use std::io::Read;
5
6use super::PublicationOriginals;
7use crate::{
8 contract::*,
9 fetch::{Error, ValidatedSourceArtifacts},
10};
11
12pub fn validate_source_artifacts(
17 directory: tempfile::TempDir,
18 opening: &PublishContentOpen,
19 originals: PublicationOriginals,
20) -> Result<ValidatedSourceArtifacts, Error> {
21 validate_source_artifacts_inner(directory, opening, originals, None)
22}
23pub fn validate_source_artifacts_with_import_carriers(
26 directory: tempfile::TempDir,
27 opening: &PublishContentOpen,
28 originals: PublicationOriginals,
29 carriers: crypto::import_authority::VerifiedImportCarriers,
30) -> Result<ValidatedSourceArtifacts, Error> {
31 if opening.import_authority.as_ref() != Some(carriers.bundle()) {
32 return Err(Error::HostedTrustRequired);
33 }
34 validate_source_artifacts_inner(directory, opening, originals, Some(carriers))
35}
36fn validate_source_artifacts_inner(
37 directory: tempfile::TempDir,
38 opening: &PublishContentOpen,
39 originals: PublicationOriginals,
40 carriers: Option<crypto::import_authority::VerifiedImportCarriers>,
41) -> Result<ValidatedSourceArtifacts, Error> {
42 originals.validate_bounds().map_err(preparation)?;
43 crate::hybrid::publish_open(opening).map_err(Error::Invalid)?;
44 let thread = opening
45 .thread
46 .as_ref()
47 .ok_or(Error::Invalid("publication Thread absent"))?;
48 let revision = opening
49 .revision
50 .as_ref()
51 .ok_or(Error::Invalid("publication revision absent"))?;
52 if opening.packs.len() != 2
53 || opening.packs[0].kind != pack_extent::Kind::NativePack as i32
54 || opening.packs[1].kind != pack_extent::Kind::NativeIndex as i32
55 {
56 return Err(Error::Invalid("ordered native pack and index required"));
57 }
58 let mut total = 0_u64;
59 for (extent, name) in opening.packs.iter().zip(["source.pack", "source.idx"]) {
60 let address = extent
61 .pack
62 .as_ref()
63 .ok_or(Error::Invalid("artifact address absent"))?;
64 total = total
65 .checked_add(extent.length)
66 .ok_or(Error::Invalid("artifact length overflow"))?;
67 if address.algorithm != "blake3"
68 || address.digest.len() != 32
69 || extent.offset != 0
70 || extent.length == 0
71 || extent.extent_digest.as_ref() != Some(address)
72 || total > 256 * 1024 * 1024
73 {
74 return Err(Error::Invalid("invalid or oversized publication artifact"));
75 }
76 let mut file = std::fs::File::open(directory.path().join(name))?;
77 if file.metadata()?.len() != extent.length {
78 return Err(Error::Invalid("uploaded artifact length differs"));
79 }
80 let mut hasher = blake3::Hasher::new();
81 let mut buffer = [0_u8; 64 * 1024];
82 loop {
83 let read = file.read(&mut buffer)?;
84 if read == 0 {
85 break;
86 }
87 hasher.update(&buffer[..read]);
88 }
89 if hasher.finalize().as_bytes().as_slice() != address.digest {
90 return Err(Error::Invalid("uploaded artifact digest differs"));
91 }
92 }
93 let selected = thread
94 .id
95 .as_ref()
96 .ok_or(Error::Invalid("Thread identity absent"))?;
97 let mut genesis = None;
98 let mut dependencies = Vec::new();
99 for wrapper in originals.geneses {
100 let record = wrapper
101 .genesis
102 .as_ref()
103 .ok_or(Error::Invalid("original genesis absent"))?;
104 let candidate = heddle_object_model::object::thread_replication::ThreadGenesis::decode(
105 &record.canonical_record,
106 )
107 .map_err(preparation)?;
108 if candidate.id().map_err(preparation)?.as_bytes().as_slice() == selected.value {
109 if genesis.replace(wrapper).is_some() {
110 return Err(Error::Invalid("duplicate selected genesis"));
111 }
112 } else {
113 dependencies.push(wrapper);
114 }
115 }
116 let mut operations = Vec::new();
117 let mut receipts = Vec::new();
118 for batch in originals.operations {
119 if batch.import_authority != opening.import_authority
120 || batch.native_authority != opening.native_authority
121 {
122 return Err(Error::Invalid("publication proof differs from opening"));
123 }
124 for received in crate::authority_admission::match_batch(&batch)? {
125 operations.push(received.original);
126 receipts.extend(received.authority_admission);
127 }
128 }
129 let mut validated = crate::fetch::validate_artifacts(
130 directory,
131 thread,
132 revision,
133 &genesis.ok_or(Error::Invalid("original selected genesis absent"))?,
134 operations,
135 dependencies,
136 receipts,
137 carriers,
138 )?;
139 validated.import_authority = opening.import_authority.clone();
140 validated.native_authority = opening.native_authority.clone();
141 Ok(validated)
142}
143fn preparation(error: impl std::fmt::Display) -> Error {
144 Error::Preparation(error.to_string())
145}
146
147pub struct ProposedSourceArtifacts {
151 artifacts: ValidatedSourceArtifacts,
152 acceptances: super::PublicationAcceptancePlan,
153}
154
155impl ProposedSourceArtifacts {
156 pub fn artifacts(&self) -> &ValidatedSourceArtifacts {
157 &self.artifacts
158 }
159 pub fn acceptances(&self) -> &super::PublicationAcceptancePlan {
160 &self.acceptances
161 }
162 pub fn into_parts(self) -> (ValidatedSourceArtifacts, super::PublicationAcceptancePlan) {
163 (self.artifacts, self.acceptances)
164 }
165}
166
167pub fn validate_proposed_source_artifacts(
168 directory: tempfile::TempDir,
169 opening: &PublishContentClientFrame,
170 originals: PublicationOriginals,
171 spool_genesis: heddle_object_model::object::ContentHash,
172) -> Result<ProposedSourceArtifacts, Error> {
173 let (originals, acceptances) =
174 super::proposed_publication(opening, originals, spool_genesis).map_err(preparation)?;
175 let Some(publish_content_client_frame::Body::Open(open)) = &opening.body else {
176 return Err(Error::Invalid("publication Open required"));
177 };
178 let artifacts = validate_source_artifacts(directory, open, originals)?;
179 Ok(ProposedSourceArtifacts {
180 artifacts,
181 acceptances,
182 })
183}