Skip to main content

zingo_status/
confirmation_status.rs

1//! If a note is confirmed, it is:
2//!  Confirmed === on-record on-chain at `BlockHeight`
3
4use std::io::{Read, Write};
5
6use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
7
8use zcash_protocol::consensus::BlockHeight;
9
10/// Transaction confirmation status. As a transaction is created and transmitted to the blockchain, it will move
11/// through each of these states. Received transactions will either be seen in the mempool or scanned from confirmed
12/// blocks. Variant order is logical display order for efficient sorting instead of the order of logical status flow.
13#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14pub enum ConfirmationStatus {
15    /// The transaction has been included in a confirmed block on the blockchain.
16    /// The block height is the height of the confirmed block that contains the transaction.
17    Confirmed(BlockHeight),
18    /// The transaction is known to be - or has been - in the mempool.
19    /// The block height is the chain height when the transaction was seen in the mempool + 1 (target height).
20    Mempool(BlockHeight),
21    /// The transaction has been transmitted to the blockchain but has not been seen in the mempool yet.
22    /// The block height is the chain height when the transaction was transmitted + 1 (target height).
23    Transmitted(BlockHeight),
24    /// The transaction has been created but not yet transmitted to the blockchain.
25    /// The block height is the chain height when the transaction was created + 1 (target height).
26    Calculated(BlockHeight),
27    /// The transaction has been created but failed to be transmitted, was not accepted into the mempool, was rejected
28    /// from the mempool or expired before it was included in a confirmed block on the block chain.
29    /// The block height is the chain height when the transaction was last updated + 1 (target height).
30    Failed(BlockHeight),
31}
32
33impl ConfirmationStatus {
34    /// A wrapper matching the Confirmed case.
35    /// # Examples
36    ///
37    /// ```
38    /// use zingo_status::confirmation_status::ConfirmationStatus;
39    /// use zcash_protocol::consensus::BlockHeight;
40    ///
41    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed());
42    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed());
43    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed());
44    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed());
45    /// ```
46    #[must_use]
47    pub fn is_confirmed(&self) -> bool {
48        matches!(self, Self::Confirmed(_))
49    }
50
51    /// To return true, the status must be confirmed and no earlier than specified height.
52    /// # Examples
53    ///
54    /// ```
55    /// use zingo_status::confirmation_status::ConfirmationStatus;
56    /// use zcash_protocol::consensus::BlockHeight;
57    ///
58    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after_or_at(&9.into()));
59    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after_or_at(&10.into()));
60    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after_or_at(&11.into()));
61    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after_or_at(&9.into()));
62    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after_or_at(&10.into()));
63    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after_or_at(&11.into()));
64    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after_or_at(&9.into()));
65    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after_or_at(&10.into()));
66    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after_or_at(&11.into()));
67    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_after_or_at(&9.into()));
68    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_after_or_at(&10.into()));
69    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_after_or_at(&11.into()));
70    /// ```
71    #[must_use]
72    pub fn is_confirmed_after_or_at(&self, comparison_height: &BlockHeight) -> bool {
73        matches!(self, Self::Confirmed(self_height) if self_height >= comparison_height)
74    }
75
76    /// To return true, the status must be confirmed and no earlier than specified height.
77    /// # Examples
78    ///
79    /// ```
80    /// use zingo_status::confirmation_status::ConfirmationStatus;
81    /// use zcash_protocol::consensus::BlockHeight;
82    ///
83    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after(&9.into()));
84    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after(&10.into()));
85    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_after(&11.into()));
86    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after(&9.into()));
87    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after(&10.into()));
88    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_after(&11.into()));
89    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after(&9.into()));
90    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after(&10.into()));
91    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_after(&11.into()));
92    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_after(&9.into()));
93    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_after(&10.into()));
94    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_after(&11.into()));
95    /// ```
96    #[must_use]
97    pub fn is_confirmed_after(&self, comparison_height: &BlockHeight) -> bool {
98        matches!(self, Self::Confirmed(self_height) if self_height > comparison_height)
99    }
100
101    /// To return true, the status must be confirmed and no later than specified height.
102    /// # Examples
103    ///
104    /// ```
105    /// use zingo_status::confirmation_status::ConfirmationStatus;
106    /// use zcash_protocol::consensus::BlockHeight;
107    ///
108    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before_or_at(&9.into()));
109    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before_or_at(&10.into()));
110    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before_or_at(&11.into()));
111    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before_or_at(&9.into()));
112    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before_or_at(&10.into()));
113    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before_or_at(&11.into()));
114    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before_or_at(&9.into()));
115    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before_or_at(&10.into()));
116    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before_or_at(&11.into()));
117    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_before_or_at(&9.into()));
118    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_before_or_at(&10.into()));
119    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_before_or_at(&11.into()));
120    /// ```
121    // TODO: blockheight impls copy so remove ref
122    #[must_use]
123    pub fn is_confirmed_before_or_at(&self, comparison_height: &BlockHeight) -> bool {
124        matches!(self, Self::Confirmed(self_height) if self_height <= comparison_height)
125    }
126
127    /// To return true, the status must be confirmed earlier than specified height.
128    /// # Examples
129    ///
130    /// ```
131    /// use zingo_status::confirmation_status::ConfirmationStatus;
132    /// use zcash_protocol::consensus::BlockHeight;
133    ///
134    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before(&9.into()));
135    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before(&10.into()));
136    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_confirmed_before(&11.into()));
137    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before(&9.into()));
138    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before(&10.into()));
139    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_confirmed_before(&11.into()));
140    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before(&9.into()));
141    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before(&10.into()));
142    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_confirmed_before(&11.into()));
143    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_before(&9.into()));
144    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_confirmed_before(&10.into()));
145    /// assert!(ConfirmationStatus::Confirmed(10.into()).is_confirmed_before(&11.into()));
146    /// ```
147    #[must_use]
148    pub fn is_confirmed_before(&self, comparison_height: &BlockHeight) -> bool {
149        matches!(self, Self::Confirmed(self_height) if self_height < comparison_height)
150    }
151
152    /// To return true, the status must not be confirmed and it must have been submitted sufficiently far in the past. This allows deduction of expired transactions.
153    /// # Examples
154    ///
155    /// ```
156    /// use zingo_status::confirmation_status::ConfirmationStatus;
157    /// use zcash_protocol::consensus::BlockHeight;
158    ///
159    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_pending_before(&9.into()));
160    /// assert!(!ConfirmationStatus::Calculated(10.into()).is_pending_before(&10.into()));
161    /// assert!(ConfirmationStatus::Calculated(10.into()).is_pending_before(&11.into()));
162    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_pending_before(&9.into()));
163    /// assert!(!ConfirmationStatus::Transmitted(10.into()).is_pending_before(&10.into()));
164    /// assert!(ConfirmationStatus::Transmitted(10.into()).is_pending_before(&11.into()));
165    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_pending_before(&9.into()));
166    /// assert!(!ConfirmationStatus::Mempool(10.into()).is_pending_before(&10.into()));
167    /// assert!(ConfirmationStatus::Mempool(10.into()).is_pending_before(&11.into()));
168    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_pending_before(&9.into()));
169    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_pending_before(&10.into()));
170    /// assert!(!ConfirmationStatus::Confirmed(10.into()).is_pending_before(&11.into()));
171    /// ```
172    #[must_use]
173    pub fn is_pending_before(&self, comparison_height: &BlockHeight) -> bool {
174        match self {
175            Self::Calculated(self_height)
176            | Self::Transmitted(self_height)
177            | Self::Mempool(self_height) => self_height < comparison_height,
178            _ => false,
179        }
180    }
181
182    /// Check if transaction has `Calculated`, `Transmitted` or `Mempool` status.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use zingo_status::confirmation_status::ConfirmationStatus;
188    /// use zcash_protocol::consensus::BlockHeight;
189    ///
190    /// assert!(ConfirmationStatus::Calculated(1.into()).is_pending());
191    /// assert!(ConfirmationStatus::Transmitted(1.into()).is_pending());
192    /// assert!(ConfirmationStatus::Mempool(1.into()).is_pending());
193    /// assert!(!ConfirmationStatus::Confirmed(1.into()).is_pending());
194    /// assert!(!ConfirmationStatus::Failed(1.into()).is_pending());
195    /// ```
196    #[must_use]
197    pub fn is_pending(&self) -> bool {
198        matches!(
199            self,
200            Self::Calculated(_) | Self::Transmitted(_) | Self::Mempool(_)
201        )
202    }
203
204    /// Check if transaction has `Failed` status.
205    /// # Examples
206    ///
207    /// ```
208    /// use zingo_status::confirmation_status::ConfirmationStatus;
209    /// use zcash_protocol::consensus::BlockHeight;
210    ///
211    /// assert!(!ConfirmationStatus::Calculated(1.into()).is_failed());
212    /// assert!(!ConfirmationStatus::Transmitted(1.into()).is_failed());
213    /// assert!(!ConfirmationStatus::Mempool(1.into()).is_failed());
214    /// assert!(!ConfirmationStatus::Confirmed(1.into()).is_failed());
215    /// assert!(ConfirmationStatus::Failed(1.into()).is_failed());
216    /// ```
217    #[must_use]
218    pub fn is_failed(&self) -> bool {
219        matches!(self, Self::Failed(_))
220    }
221
222    /// Returns none if transaction is not confirmed, otherwise returns the height it was confirmed at.
223    /// # Examples
224    ///
225    /// ```
226    /// use zingo_status::confirmation_status::ConfirmationStatus;
227    /// use zcash_protocol::consensus::BlockHeight;
228    ///
229    /// let status = ConfirmationStatus::Confirmed(16.into());
230    /// assert_eq!(status.get_confirmed_height(), Some(16.into()));
231    ///
232    /// let status = ConfirmationStatus::Mempool(15.into());
233    /// assert_eq!(status.get_confirmed_height(), None);
234    /// ```
235    #[must_use]
236    pub fn get_confirmed_height(&self) -> Option<BlockHeight> {
237        match self {
238            Self::Confirmed(self_height) => Some(*self_height),
239            _ => None,
240        }
241    }
242
243    /// # Examples
244    ///
245    /// ```
246    /// use zingo_status::confirmation_status::ConfirmationStatus;
247    /// use zcash_protocol::consensus::BlockHeight;
248    ///
249    /// let status = ConfirmationStatus::Confirmed(15.into());
250    /// assert_eq!(status.get_height(), 15.into());
251    /// ```
252    #[must_use]
253    pub fn get_height(&self) -> BlockHeight {
254        match self {
255            Self::Confirmed(self_height) => *self_height,
256            Self::Mempool(self_height) => *self_height,
257            Self::Transmitted(self_height) => *self_height,
258            Self::Calculated(self_height) => *self_height,
259            Self::Failed(self_height) => *self_height,
260        }
261    }
262
263    fn serialized_version() -> u8 {
264        1
265    }
266
267    /// Deserialize into `reader`
268    pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
269        let version = reader.read_u8()?;
270        let status = reader.read_u8()?;
271        let block_height = BlockHeight::from_u32(reader.read_u32::<LittleEndian>()?);
272
273        match version {
274            0 => match status {
275                0 => Ok(Self::Calculated(block_height)),
276                1 => Ok(Self::Transmitted(block_height)),
277                2 => Ok(Self::Mempool(block_height)),
278                3 => Ok(Self::Confirmed(block_height)),
279                _ => Err(std::io::Error::new(
280                    std::io::ErrorKind::InvalidData,
281                    "failed to read status",
282                )),
283            },
284            1.. => match status {
285                0 => Ok(Self::Confirmed(block_height)),
286                1 => Ok(Self::Mempool(block_height)),
287                2 => Ok(Self::Transmitted(block_height)),
288                3 => Ok(Self::Calculated(block_height)),
289                4 => Ok(Self::Failed(block_height)),
290                _ => Err(std::io::Error::new(
291                    std::io::ErrorKind::InvalidData,
292                    "failed to read status",
293                )),
294            },
295        }
296    }
297
298    /// Serialize into `writer`
299    pub fn write<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
300        writer.write_u8(Self::serialized_version())?;
301        writer.write_u8(match self {
302            Self::Confirmed(_) => 0,
303            Self::Mempool(_) => 1,
304            Self::Transmitted(_) => 2,
305            Self::Calculated(_) => 3,
306            Self::Failed(_) => 4,
307        })?;
308        writer.write_u32::<LittleEndian>(self.get_height().into())
309    }
310}
311
312/// a public interface, writ in stone
313impl std::fmt::Display for ConfirmationStatus {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        match self {
316            Self::Confirmed(_h) => {
317                write!(f, "confirmed")
318            }
319            Self::Mempool(_h) => {
320                write!(f, "mempool")
321            }
322            Self::Transmitted(_h) => {
323                write!(f, "transmitted")
324            }
325            Self::Calculated(_h) => {
326                write!(f, "calculated")
327            }
328            Self::Failed(_h) => {
329                write!(f, "failed")
330            }
331        }
332    }
333}
334#[test]
335fn stringify_display() {
336    let status = ConfirmationStatus::Transmitted(BlockHeight::from_u32(16_000));
337    let string = format!("{status}");
338    assert_eq!(string, "transmitted");
339}
340
341/// a more complete stringification
342impl std::fmt::Debug for ConfirmationStatus {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        match self {
345            Self::Confirmed(h) => {
346                let hi = u32::from(*h);
347                write!(f, "Confirmed at {hi}")
348            }
349            Self::Mempool(h) => {
350                let hi = u32::from(*h);
351                write!(f, "Mempool for {hi}")
352            }
353            Self::Transmitted(h) => {
354                let hi = u32::from(*h);
355                write!(f, "Transmitted for {hi}")
356            }
357            Self::Calculated(h) => {
358                let hi = u32::from(*h);
359                write!(f, "Calculated for {hi}")
360            }
361            Self::Failed(h) => {
362                let hi = u32::from(*h);
363                write!(f, "Failed. Last updated at {hi}")
364            }
365        }
366    }
367}
368#[test]
369fn stringify_debug() {
370    let status = ConfirmationStatus::Transmitted(BlockHeight::from_u32(16_000));
371    let string = format!("{status:?}");
372    assert_eq!(string, "Transmitted for 16000");
373}
374
375impl From<ConfirmationStatus> for String {
376    fn from(value: ConfirmationStatus) -> Self {
377        format!("{value}")
378    }
379}