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}