heddle_thread_api/hybrid/
history.rs1use std::future::Future;
4
5use api::{
6 heddle::api::common::{HostedWitnessHistoryProofV1, SignedHostedWitnessStatementV1},
7 hybrid_codec::{Reject, canonical},
8 witness_trust::{self, VerifiedWitnessSet},
9};
10use prost::Message;
11
12use crate::contract::{GetHostedWitnessHistoryProofRequest, GetHostedWitnessHistoryProofResponse};
13
14#[derive(Debug, thiserror::Error)]
15pub enum Error<E: std::error::Error> {
16 #[error("HYBRID history proof rejected: {0}")]
17 Rejected(#[from] Reject),
18 #[error("history proof lookup failed: {0}")]
19 Lookup(E),
20 #[error("history proof is unavailable")]
21 NotFound,
22}
23
24pub trait HistoryProofLookup: Send + Sync {
27 type Error: std::error::Error + Send + Sync + 'static;
28 fn lookup(
29 &self,
30 request: &GetHostedWitnessHistoryProofRequest,
31 ) -> impl Future<Output = Result<Option<GetHostedWitnessHistoryProofResponse>, Self::Error>> + Send;
32}
33
34pub fn request(
35 signed: &SignedHostedWitnessStatementV1,
36) -> Result<GetHostedWitnessHistoryProofRequest, Reject> {
37 let statement = signed.body.as_ref().ok_or(Reject::Canonical)?;
38 let request = GetHostedWitnessHistoryProofRequest {
39 executor_id: statement.executor_id.clone(),
40 statement_leaf_digest: witness_trust::leaf_digest(
41 statement.purpose,
42 &canonical(statement)?,
43 &signed.signature,
44 )?,
45 };
46 witness_trust::validate_lookup(&request)?;
47 Ok(request)
48}
49
50pub fn replace_receiver_metadata(
54 original: &mut crate::contract::ImportPublicProofBundleV1,
55 refreshed: crate::contract::ImportPublicProofBundleV1,
56) -> Result<(), Reject> {
57 api::import_authority::validate_public_bundle(&refreshed)?;
58 let mut unchanged = refreshed.clone();
59 unchanged.witness_set = original.witness_set.clone();
60 unchanged.history_proofs = original.history_proofs.clone();
61 if &unchanged != original {
62 return Err(Reject::Scope);
63 }
64 *original = refreshed;
65 Ok(())
66}
67
68pub async fn retrieve<L: HistoryProofLookup>(
71 lookup: &L,
72 set: &VerifiedWitnessSet,
73 signed: &SignedHostedWitnessStatementV1,
74 now_ms: i64,
75) -> Result<HostedWitnessHistoryProofV1, Error<L::Error>> {
76 let request = request(signed)?;
77 let response = lookup
78 .lookup(&request)
79 .await
80 .map_err(Error::Lookup)?
81 .ok_or(Error::NotFound)?;
82 if response.encoded_len() > witness_trust::MAX_PROOF_BYTES {
83 return Err(Reject::Bounds.into());
84 }
85 let proof = response.proof.ok_or(Reject::Proof)?;
86 if proof.siblings.len() > witness_trust::MAX_SIBLINGS {
87 return Err(Reject::Bounds.into());
88 }
89 witness_trust::resolve_statement(set, signed, Some(&proof), false, now_ms)?;
90 Ok(proof)
91}
92
93pub async fn complete_bundle<L: HistoryProofLookup>(
97 lookup: &L,
98 set: &VerifiedWitnessSet,
99 bundle: &mut crate::contract::ImportPublicProofBundleV1,
100 now_ms: i64,
101) -> Result<(), Error<L::Error>> {
102 api::import_authority::validate_public_bundle(bundle)?;
103 let proofs = complete_proofs(
104 lookup,
105 set,
106 &bundle.statements,
107 &bundle.history_proofs,
108 now_ms,
109 )
110 .await?;
111 let mut completed = bundle.clone();
112 completed.history_proofs = proofs;
113 api::import_authority::validate_public_bundle(&completed)?;
114 *bundle = completed;
115 Ok(())
116}
117
118pub fn replace_native_receiver_metadata(
119 original: &mut crate::contract::NativePublicProofBundleV1,
120 refreshed: crate::contract::NativePublicProofBundleV1,
121) -> Result<(), Reject> {
122 api::native_witness::validate_public_bundle(&refreshed)?;
123 let mut unchanged = refreshed.clone();
124 unchanged.witness_set = original.witness_set.clone();
125 unchanged.history_proofs = original.history_proofs.clone();
126 if &unchanged != original {
127 return Err(Reject::Scope);
128 }
129 *original = refreshed;
130 Ok(())
131}
132pub async fn complete_native_bundle<L: HistoryProofLookup>(
133 lookup: &L,
134 set: &VerifiedWitnessSet,
135 bundle: &mut crate::contract::NativePublicProofBundleV1,
136 now_ms: i64,
137) -> Result<(), Error<L::Error>> {
138 api::native_witness::validate_public_bundle(bundle)?;
139 let proofs = complete_proofs(
140 lookup,
141 set,
142 &bundle.statements,
143 &bundle.history_proofs,
144 now_ms,
145 )
146 .await?;
147 let mut completed = bundle.clone();
148 completed.history_proofs = proofs;
149 api::native_witness::validate_public_bundle(&completed)?;
150 *bundle = completed;
151 Ok(())
152}
153async fn complete_proofs<L: HistoryProofLookup>(
154 lookup: &L,
155 set: &VerifiedWitnessSet,
156 statements: &[SignedHostedWitnessStatementV1],
157 original_proofs: &[HostedWitnessHistoryProofV1],
158 now_ms: i64,
159) -> Result<Vec<HostedWitnessHistoryProofV1>, Error<L::Error>> {
160 let mut proofs = original_proofs.to_vec();
161 let mut used = std::collections::BTreeSet::new();
162 for signed in statements {
163 let body = signed.body.as_ref().ok_or(Reject::Canonical)?;
164 match witness_trust::resolve_statement(set, signed, None, false, now_ms) {
165 Ok(_) => continue,
166 Err(Reject::Proof) => {}
167 Err(error) => return Err(error.into()),
168 }
169 let mut matched = None;
170 for (index, proof) in proofs.iter().enumerate().filter(|(_, proof)| {
171 proof.executor_id == body.executor_id && proof.purpose == body.purpose
172 }) {
173 if witness_trust::resolve_statement(set, signed, Some(proof), false, now_ms).is_ok()
174 && matched.replace(index).is_some()
175 {
176 return Err(Reject::Proof.into());
177 }
178 }
179 let index = if let Some(index) = matched {
180 index
181 } else {
182 let proof = retrieve(lookup, set, signed, now_ms).await?;
183 proofs.push(proof);
184 if proofs.len() > 1024 {
185 return Err(Reject::Bounds.into());
186 }
187 proofs.len() - 1
188 };
189 used.insert(index);
190 }
191 if used.len() != proofs.len() {
192 return Err(Reject::Proof.into());
193 }
194 Ok(proofs)
195}