Skip to main content

uts_core/codec/v1/
timestamp.rs

1//! ** The implementation here is subject to change as this is a read-only version. **
2
3use crate::{
4    alloc::{Allocator, Global, vec, vec::Vec},
5    codec::v1::{
6        Attestation, FinalizationError, MayHaveInput, PendingAttestation,
7        attestation::RawAttestation, opcode::OpCode,
8    },
9    utils::Hexed,
10};
11use allocator_api2::SliceExt;
12use core::fmt::Debug;
13use std::sync::OnceLock;
14
15pub(crate) mod builder;
16mod decode;
17mod encode;
18mod fmt;
19
20/// Proof that that one or more attestations commit to a message.
21///
22/// This should not be confused with [`DetachedTimestamp`](crate::codec::v1::DetachedTimestamp),
23/// single [`Timestamp`]s **DO NOT** include the digest of the message they commit to.
24///
25/// Sample Timestamp:
26/// ```text
27/// execute APPEND 7d9472db4ae254e8
28/// execute SHA256
29/// execute APPEND 65191d41c625e4505a442928ec4211b3
30/// execute SHA256
31/// execute APPEND 000639ee5837a935dce596c85f1ce323d5219afe84ee0832ee6614924f4c6598
32/// execute SHA256
33/// execute PREPEND 6944db61
34/// execute APPEND 0ef41e45bb5534b3
35/// result attested by Pending: update URI https://alice.btc.calendar.opentimestamps.org
36/// ```
37#[derive(Clone, Debug)]
38pub enum Timestamp<A: Allocator = Global> {
39    Step(Step<A>),
40    Attestation(RawAttestation<A>),
41}
42
43/// An execution Step.
44#[derive(Clone)]
45pub struct Step<A: Allocator = Global> {
46    op: OpCode,
47    data: Vec<u8, A>,
48    input: OnceLock<Vec<u8, A>>,
49    next: Vec<Timestamp<A>, A>,
50}
51
52impl<A: Allocator> PartialEq for Timestamp<A> {
53    fn eq(&self, other: &Self) -> bool {
54        match (self, other) {
55            (Timestamp::Step(s1), Timestamp::Step(s2)) => s1 == s2,
56            (Timestamp::Attestation(a1), Timestamp::Attestation(a2)) => a1 == a2,
57            _ => false,
58        }
59    }
60}
61impl<A: Allocator> Eq for Timestamp<A> {}
62
63impl<A: Allocator> PartialEq for Step<A> {
64    fn eq(&self, other: &Self) -> bool {
65        self.op == other.op && self.data == other.data && self.next == other.next
66    }
67}
68impl<A: Allocator> Eq for Step<A> {}
69
70impl<A: Allocator + Debug> Debug for Step<A> {
71    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72        let mut f = f.debug_struct("Step");
73        f.field("op", &self.op);
74        if self.op.has_immediate() {
75            f.field("data", &Hexed(&self.data));
76        }
77        f.field("next", &self.next).finish()
78    }
79}
80
81impl Timestamp {
82    /// Creates a new timestamp builder.
83    pub fn builder() -> builder::TimestampBuilder<Global> {
84        builder::TimestampBuilder::new_in(Global)
85    }
86
87    /// Merges multiple timestamps into a single FORK timestamp.
88    ///
89    /// # Panics
90    ///
91    /// This will panic if there are conflicting inputs when finalizing unfinalized timestamps.
92    pub fn merge(timestamps: Vec<Timestamp, Global>) -> Timestamp {
93        Self::merge_in(timestamps, Global)
94    }
95
96    /// Try to merge multiple timestamps into a single FORK timestamp.
97    ///
98    /// Returns an error if there are conflicting inputs when finalizing unfinalized timestamps.
99    pub fn try_merge(timestamps: Vec<Timestamp, Global>) -> Result<Timestamp, FinalizationError> {
100        Self::try_merge_in(timestamps, Global)
101    }
102}
103
104impl<A: Allocator> Timestamp<A> {
105    /// Returns the opcode of this timestamp node.
106    pub fn op(&self) -> OpCode {
107        match self {
108            Timestamp::Step(step) => {
109                debug_assert_ne!(
110                    step.op,
111                    OpCode::ATTESTATION,
112                    "sanity check failed: Step with ATTESTATION opcode"
113                );
114                step.op
115            }
116            Timestamp::Attestation(_) => OpCode::ATTESTATION,
117        }
118    }
119
120    /// Returns this timestamp as a step, if it is one.
121    #[inline]
122    pub fn as_step(&self) -> Option<&Step<A>> {
123        match self {
124            Timestamp::Step(step) => Some(step),
125            Timestamp::Attestation(_) => None,
126        }
127    }
128
129    /// Returns this timestamp as an attestation, if it is one.
130    #[inline]
131    pub fn as_attestation(&self) -> Option<&RawAttestation<A>> {
132        match self {
133            Timestamp::Attestation(attestation) => Some(attestation),
134            Timestamp::Step(_) => None,
135        }
136    }
137
138    /// Returns the allocator used by this timestamp node.
139    #[inline]
140    pub fn allocator(&self) -> &A {
141        match self {
142            Self::Attestation(attestation) => attestation.allocator(),
143            Self::Step(step) => step.allocator(),
144        }
145    }
146
147    /// Returns true if this timestamp is finalized.
148    #[inline]
149    pub fn is_finalized(&self) -> bool {
150        self.input().is_some()
151    }
152
153    /// Iterates over all attestations in this timestamp.
154    #[inline]
155    pub fn attestations(&self) -> AttestationIter<'_, A> {
156        AttestationIter { stack: vec![self] }
157    }
158
159    /// Iterates over all pending attestation steps in this timestamp.
160    ///
161    /// # Note
162    ///
163    /// This iterator will yield `Timestamp` instead of `RawAttestation`.
164    #[inline]
165    pub fn pending_attestations_mut(&mut self) -> PendingAttestationIterMut<'_, A> {
166        PendingAttestationIterMut { stack: vec![self] }
167    }
168}
169
170impl<A: Allocator + Clone> Timestamp<A> {
171    /// Creates a new timestamp builder with the given allocator.
172    pub fn builder_in(alloc: A) -> builder::TimestampBuilder<A> {
173        builder::TimestampBuilder::new_in(alloc)
174    }
175
176    /// Finalizes the timestamp with the given input data.
177    ///
178    /// # Panics
179    ///
180    /// Panics if the timestamp is already finalized with different input data.
181    #[inline]
182    pub fn finalize(&self, input: &[u8]) {
183        self.try_finalize(input)
184            .expect("conflicting inputs when finalizing timestamp")
185    }
186
187    /// Try finalizes the timestamp with the given input data.
188    ///
189    /// Returns an error if the timestamp is already finalized with different input data.
190    pub fn try_finalize(&self, input: &[u8]) -> Result<(), FinalizationError> {
191        let init_fn = || SliceExt::to_vec_in(input, self.allocator().clone());
192        match self {
193            Self::Attestation(attestation) => {
194                if let Some(already) = attestation.value.get() {
195                    return if input != already {
196                        Err(FinalizationError)
197                    } else {
198                        Ok(())
199                    };
200                }
201                let _ = attestation.value.get_or_init(init_fn);
202            }
203            Self::Step(step) => {
204                if let Some(already) = step.input.get() {
205                    return if input != already {
206                        Err(FinalizationError)
207                    } else {
208                        Ok(())
209                    };
210                }
211                let input = step.input.get_or_init(init_fn);
212
213                match step.op {
214                    OpCode::FORK => {
215                        debug_assert!(step.next.len() >= 2, "FORK must have at least two children");
216                        for child in &step.next {
217                            child.try_finalize(input)?;
218                        }
219                    }
220                    OpCode::ATTESTATION => unreachable!("should not happen"),
221                    op => {
222                        let output = op.execute_in(input, &step.data, step.allocator().clone());
223                        debug_assert!(step.next.len() == 1, "non-FORK must have exactly one child");
224                        step.next[0].try_finalize(&output)?;
225                    }
226                }
227            }
228        }
229        Ok(())
230    }
231
232    /// Merges multiple timestamps into a single FORK timestamp.
233    ///
234    /// # Panics
235    ///
236    /// This will panic if there are conflicting inputs when finalizing unfinalized timestamps.
237    pub fn merge_in(timestamps: Vec<Timestamp<A>, A>, alloc: A) -> Timestamp<A> {
238        Self::try_merge_in(timestamps, alloc).expect("conflicting inputs when merging timestamps")
239    }
240
241    /// Merges multiple timestamps into a single FORK timestamp.
242    ///
243    /// This will attempt to finalize unfinalized timestamps if any of the input timestamps are finalized.
244    ///
245    /// Returns an error if there are conflicting inputs when finalizing unfinalized timestamps.
246    pub fn try_merge_in(
247        timestamps: Vec<Timestamp<A>, A>,
248        alloc: A,
249    ) -> Result<Timestamp<A>, FinalizationError> {
250        // if any timestamp is finalized, ensure they are with the same input,
251        // finalize unfinalized timestamps with that input
252        let finalized_input = timestamps.iter().find_map(|ts| ts.input());
253        if let Some(input) = finalized_input {
254            for ts in timestamps.iter().filter(|ts| !ts.is_finalized()) {
255                ts.try_finalize(input)?;
256            }
257        }
258
259        Ok(Timestamp::Step(Step {
260            op: OpCode::FORK,
261            data: Vec::new_in(alloc.clone()),
262            input: OnceLock::new(),
263            next: timestamps,
264        }))
265    }
266}
267
268impl<A: Allocator> MayHaveInput for Timestamp<A> {
269    #[inline]
270    fn input(&self) -> Option<&[u8]> {
271        match self {
272            Timestamp::Step(step) => step.input(),
273            Timestamp::Attestation(attestation) => attestation.input(),
274        }
275    }
276}
277
278impl<A: Allocator> Step<A> {
279    /// Returns the opcode of this step.
280    pub fn op(&self) -> OpCode {
281        self.op
282    }
283
284    /// Returns the immediate data of this step.
285    pub fn data(&self) -> &[u8] {
286        self.data.as_slice()
287    }
288
289    /// Returns the next timestamps of this step.
290    pub fn next(&self) -> &[Timestamp<A>] {
291        self.next.as_slice()
292    }
293
294    /// Returns the next timestamps of this step.
295    pub fn next_mut(&mut self) -> &mut [Timestamp<A>] {
296        self.next.as_mut_slice()
297    }
298
299    /// Returns the allocator used by this step.
300    pub fn allocator(&self) -> &A {
301        self.data.allocator()
302    }
303}
304
305impl<A: Allocator> MayHaveInput for Step<A> {
306    #[inline]
307    fn input(&self) -> Option<&[u8]> {
308        self.input.get().map(|v| v.as_slice())
309    }
310}
311
312#[must_use = "AttestationIter is an iterator, it does nothing unless consumed"]
313pub struct AttestationIter<'a, A: Allocator> {
314    stack: Vec<&'a Timestamp<A>>,
315}
316
317impl<'a, A: Allocator> Iterator for AttestationIter<'a, A> {
318    type Item = &'a RawAttestation<A>;
319
320    fn next(&mut self) -> Option<Self::Item> {
321        while let Some(ts) = self.stack.pop() {
322            match ts {
323                Timestamp::Step(step) => {
324                    for next in step.next().iter().rev() {
325                        self.stack.push(next);
326                    }
327                }
328                Timestamp::Attestation(attestation) => return Some(attestation),
329            }
330        }
331        None
332    }
333}
334
335pub struct PendingAttestationIterMut<'a, A: Allocator> {
336    stack: Vec<&'a mut Timestamp<A>>,
337}
338
339impl<'a, A: Allocator> Iterator for PendingAttestationIterMut<'a, A> {
340    type Item = &'a mut Timestamp<A>;
341
342    fn next(&mut self) -> Option<Self::Item> {
343        while let Some(ts) = self.stack.pop() {
344            match ts {
345                Timestamp::Step(step) => {
346                    for next in step.next_mut().iter_mut().rev() {
347                        self.stack.push(next);
348                    }
349                }
350                Timestamp::Attestation(attestation) => {
351                    if attestation.tag == PendingAttestation::TAG {
352                        return Some(ts);
353                    }
354                }
355            }
356        }
357        None
358    }
359}