1use crate::{Error, engine::Engine, model::RecordPackage};
5use bitcoin::Txid;
6use std::{
7 collections::BTreeMap,
8 path::Path,
9 sync::atomic::{AtomicBool, Ordering},
10 time::{Duration, Instant},
11};
12use urma_core::multipart::VerifiedRecord;
13use urma_runtime::multipart::{
14 Candidate, FetchError, MultipartSource, RecordRequest, RecoveredObject, RecoveryLimits,
15};
16
17pub trait RecordSource {
20 fn fetch(&self, txid: Txid) -> Result<Option<RecordPackage>, Error>;
21}
22
23#[derive(Clone, Copy)]
25pub struct FetchLimits {
26 pub sources: usize,
27 pub timeout: Duration,
28}
29impl Default for FetchLimits {
30 fn default() -> Self {
31 Self {
32 sources: 4,
33 timeout: Duration::from_secs(30),
34 }
35 }
36}
37
38fn verify_cached(
39 engine: &Engine,
40 expected: Txid,
41 package: &RecordPackage,
42) -> Result<VerifiedRecord, Error> {
43 let chain = engine.chain()?;
44 let reveal = urma_chain::decode::transaction(&package.reveal.transaction, chain)
45 .map_err(|e| Error::Invalid(e.to_string()))?;
46 let commit = urma_chain::decode::transaction(&package.commit, chain)
47 .map_err(|e| Error::Invalid(e.to_string()))?;
48 VerifiedRecord::verify(expected, &reveal, &commit).map_err(|e| Error::Invalid(e.to_string()))
49}
50
51pub fn fetch_record(
55 engine: &Engine,
56 txid: Txid,
57 sources: &[&dyn RecordSource],
58 limits: FetchLimits,
59) -> Result<Option<VerifiedRecord>, Error> {
60 let began = Instant::now();
61 let deadline = began
62 .checked_add(limits.timeout)
63 .ok_or_else(|| Error::Invalid("record deadline overflow".into()))?;
64 crate::engine::check_deadline(deadline)?;
65 if let Some(record) = engine.record(txid)? {
66 let verified = verify_cached(engine, txid, &record.package)?;
67 crate::engine::check_deadline(deadline)?;
68 return Ok(Some(verified));
69 }
70 let mut last = None;
71 for source in sources.iter().take(limits.sources) {
72 if began.elapsed() >= limits.timeout {
73 return Err(Error::Busy("record acquisition deadline".into()));
74 }
75 let package = match source.fetch(txid) {
76 Ok(Some(package)) => package,
77 Ok(None) => continue,
78 Err(error) => {
79 last = Some(error);
80 continue;
81 }
82 };
83 if began.elapsed() >= limits.timeout {
84 return Err(Error::Busy("record acquisition deadline".into()));
85 }
86 match verify_cached(engine, txid, &package) {
89 Err(error) => {
90 last = Some(error);
91 continue;
92 }
93 Ok(verified) => match engine.admit_record_before(package, Some(deadline)) {
94 Ok(_) => return Ok(Some(verified)),
95 Err(error) => {
96 last = Some(error);
99 }
100 },
101 }
102 }
103 if began.elapsed() >= limits.timeout {
104 return Err(Error::Busy("record acquisition deadline".into()));
105 }
106 let fallback = engine.retrieve_located_record_before(txid, deadline);
107 crate::engine::check_deadline(deadline)?;
108 match fallback {
109 Ok(Some(record)) => {
110 if began.elapsed() >= limits.timeout {
111 return Err(Error::Busy("record acquisition deadline".into()));
112 }
113 let verified = verify_cached(engine, txid, &record.package)?;
114 crate::engine::check_deadline(deadline)?;
115 Ok(Some(verified))
116 }
117 Err(error) => Err(error),
118 Ok(None) => match last {
119 Some(error) => Err(error),
120 None => Ok(None),
121 },
122 }
123}
124
125pub fn recover(
130 engine: &Engine,
131 root: Txid,
132 sources: &[&dyn RecordSource],
133 limits: RecoveryLimits,
134 scratch: &Path,
135) -> Result<RecoveredObject, Error> {
136 recover_inner(
137 engine,
138 root,
139 sources,
140 limits,
141 scratch,
142 false,
143 &AtomicBool::new(false),
144 )
145}
146
147pub fn recover_and_pin(
151 engine: &Engine,
152 root: Txid,
153 sources: &[&dyn RecordSource],
154 limits: RecoveryLimits,
155 scratch: &Path,
156) -> Result<RecoveredObject, Error> {
157 recover_inner(
158 engine,
159 root,
160 sources,
161 limits,
162 scratch,
163 true,
164 &AtomicBool::new(false),
165 )
166}
167
168pub(crate) fn recover_inner(
169 engine: &Engine,
170 root: Txid,
171 sources: &[&dyn RecordSource],
172 limits: RecoveryLimits,
173 scratch: &Path,
174 pin: bool,
175 cancelled: &AtomicBool,
176) -> Result<RecoveredObject, Error> {
177 check_cancelled(cancelled)?;
178 let generation = engine.status()?.generation;
179 let began = Instant::now();
180 let deadline = began
181 .checked_add(limits.timeout)
182 .ok_or_else(|| Error::Invalid("object deadline overflow".into()))?;
183 let fetch_limits = FetchLimits {
184 sources: limits.attempts.max(1) as usize,
185 timeout: limits.timeout,
186 };
187 let root = fetch_record(engine, root, sources, fetch_limits)?
188 .ok_or_else(|| Error::Invalid("root is not available from the supplied sources".into()))?;
189 check_cancelled(cancelled)?;
190 let mut remaining = limits;
191 remaining.timeout = limits.timeout.saturating_sub(began.elapsed());
192 let mut adapter = Adapter {
193 engine,
194 sources,
195 fetch_limits,
196 began,
197 members: BTreeMap::from([(root.txid(), crate::archive::ObjectMember::from(&root))]),
198 cancelled,
199 };
200 let object = urma_runtime::multipart::reconstruct(&root, &mut adapter, remaining, scratch)
201 .map_err(|e| Error::Source(Box::new(e)))?;
202 check_cancelled(cancelled)?;
203 engine.complete_object(
206 &object,
207 generation,
208 &adapter.members.into_values().collect::<Vec<_>>(),
209 pin,
210 deadline,
211 cancelled,
212 )?;
213 Ok(object)
214}
215
216struct Adapter<'a> {
217 engine: &'a Engine,
218 sources: &'a [&'a dyn RecordSource],
219 fetch_limits: FetchLimits,
220 began: Instant,
221 members: BTreeMap<Txid, crate::archive::ObjectMember>,
222 cancelled: &'a AtomicBool,
223}
224impl MultipartSource for Adapter<'_> {
225 fn fetch(
226 &mut self,
227 request: &RecordRequest,
228 _rejected: &[String],
229 ) -> Result<Candidate, FetchError> {
230 if self.cancelled.load(Ordering::Acquire) {
231 return Err(FetchError::Source(urma_runtime::error::Error::Io(
232 std::io::Error::other("object retention cancelled"),
233 )));
234 }
235 match fetch_record(
238 self.engine,
239 request.reference.txid,
240 self.sources,
241 FetchLimits {
242 timeout: self
243 .fetch_limits
244 .timeout
245 .saturating_sub(self.began.elapsed()),
246 ..self.fetch_limits
247 },
248 ) {
249 Ok(Some(record)) => {
250 self.members
251 .insert(record.txid(), crate::archive::ObjectMember::from(&record));
252 Ok(Candidate {
253 origin: "node-selected-view".into(),
254 record,
255 })
256 }
257 Ok(None) => Err(FetchError::Unavailable),
258 Err(error) => Err(FetchError::Source(urma_runtime::error::Error::Io(
259 std::io::Error::other(error.to_string()),
260 ))),
261 }
262 }
263}
264
265fn check_cancelled(cancelled: &AtomicBool) -> Result<(), Error> {
266 if cancelled.load(Ordering::Acquire) {
267 Err(Error::Busy("object retention cancelled".into()))
268 } else {
269 Ok(())
270 }
271}