stellar_base/
time_bounds.rs

1//! Represent when a transaction is valid.
2use std::io::{Read, Write};
3
4use crate::error::{Error, Result};
5use crate::xdr;
6use chrono::{DateTime, Duration, TimeZone, Utc};
7
8/// The time window in which a transaction is considered valid.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct TimeBounds {
11    lower: Option<DateTime<Utc>>,
12    upper: Option<DateTime<Utc>>,
13}
14
15impl TimeBounds {
16    /// Returns time bounds with the upper bounds set to `duration` in the future.
17    pub fn valid_for(duration: Duration) -> TimeBounds {
18        let lower = Utc::now();
19        let upper = lower + duration;
20        TimeBounds {
21            lower: None,
22            upper: Some(upper),
23        }
24    }
25
26    /// Returns time bounds such that the transaction is always valid.
27    pub fn always_valid() -> TimeBounds {
28        TimeBounds {
29            lower: None,
30            upper: None,
31        }
32    }
33
34    /// Makes a new time bounds with the lower bound changed.
35    pub fn with_lower(&self, lower: DateTime<Utc>) -> Result<TimeBounds> {
36        ensure_valid_timestamp(&lower)?;
37        match self.upper {
38            Some(upper) if upper < lower => Err(Error::InvalidTimeBounds),
39            Some(upper) => Ok(TimeBounds {
40                lower: Some(lower),
41                upper: Some(upper),
42            }),
43            None => Ok(TimeBounds {
44                lower: Some(lower),
45                upper: None,
46            }),
47        }
48    }
49
50    /// Makes a new time bounds with the upper bound changed.
51    pub fn with_upper(&self, upper: DateTime<Utc>) -> Result<TimeBounds> {
52        ensure_valid_timestamp(&upper)?;
53        match self.lower {
54            Some(lower) if upper < lower => Err(Error::InvalidTimeBounds),
55            Some(lower) => Ok(TimeBounds {
56                lower: Some(lower),
57                upper: Some(upper),
58            }),
59            None => Ok(TimeBounds {
60                lower: None,
61                upper: Some(upper),
62            }),
63        }
64    }
65
66    /// Retrieves the time bounds lower bound.
67    pub fn lower(&self) -> &Option<DateTime<Utc>> {
68        &self.lower
69    }
70
71    /// Retrieves a mutable reference to the time bounds lower bound.
72    pub fn lower_mut(&mut self) -> &mut Option<DateTime<Utc>> {
73        &mut self.lower
74    }
75
76    /// Retrieves the time bounds lower bound.
77    pub fn upper(&self) -> &Option<DateTime<Utc>> {
78        &self.upper
79    }
80
81    /// Retrieves a mutable reference to the time bounds lower bound.
82    pub fn upper_mut(&mut self) -> &mut Option<DateTime<Utc>> {
83        &mut self.upper
84    }
85
86    /// Returns the xdr object.
87    pub fn to_xdr(&self) -> Result<xdr::TimeBounds> {
88        let min_time: u64 = match self.lower {
89            None => 0,
90            Some(t) => t.timestamp() as u64,
91        };
92        let min_time = xdr::TimePoint(min_time);
93        let max_time: u64 = match self.upper {
94            None => 0,
95            Some(t) => t.timestamp() as u64,
96        };
97        let max_time = xdr::TimePoint(max_time);
98        Ok(xdr::TimeBounds { min_time, max_time })
99    }
100
101    /// Creates from the xdr object.
102    pub fn from_xdr(x: &xdr::TimeBounds) -> Result<TimeBounds> {
103        let min_time_epoch = x.min_time.0 as i64;
104        let max_time_epoch = x.max_time.0 as i64;
105
106        let mut res = TimeBounds::always_valid();
107
108        if min_time_epoch != 0 {
109            res = res.with_lower(
110                Utc.timestamp_opt(min_time_epoch, 0)
111                    .single()
112                    .ok_or(Error::InvalidTimeBounds)?,
113            )?;
114        }
115        if max_time_epoch != 0 {
116            res = res.with_upper(
117                Utc.timestamp_opt(max_time_epoch, 0)
118                    .single()
119                    .ok_or(Error::InvalidTimeBounds)?,
120            )?;
121        }
122
123        Ok(res)
124    }
125}
126
127impl xdr::WriteXdr for TimeBounds {
128    fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
129        let xdr = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
130        xdr.write_xdr(w)
131    }
132}
133
134impl xdr::ReadXdr for TimeBounds {
135    fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
136        let xdr_result = xdr::TimeBounds::read_xdr(r)?;
137        Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
138    }
139}
140
141fn ensure_valid_timestamp(dt: &DateTime<Utc>) -> Result<()> {
142    let ts = dt.timestamp();
143    if ts >= 0 {
144        Ok(())
145    } else {
146        Err(Error::InvalidTimeBounds)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::TimeBounds;
153    use crate::xdr::{XDRDeserialize, XDRSerialize};
154    use chrono::{DateTime, Datelike, Duration, Utc};
155
156    #[test]
157    fn test_valid_for() {
158        let five_min = Duration::minutes(5);
159        let tb = TimeBounds::valid_for(five_min);
160        assert_eq!(None, *tb.lower());
161        assert_ne!(None, *tb.upper());
162    }
163
164    #[test]
165    fn test_always_valid() {
166        let tb = TimeBounds::always_valid();
167        assert_eq!(None, *tb.lower());
168        assert_eq!(None, *tb.upper());
169    }
170
171    #[test]
172    fn test_with_upper_success() {
173        let tb = TimeBounds::always_valid().with_upper(Utc::now()).unwrap();
174        assert_eq!(None, *tb.lower());
175        assert_ne!(None, *tb.upper());
176    }
177
178    #[test]
179    fn test_with_lower_success() {
180        let tb = TimeBounds::always_valid().with_lower(Utc::now()).unwrap();
181        assert_ne!(None, *tb.lower());
182        assert_eq!(None, *tb.upper());
183    }
184
185    #[test]
186    fn test_with_both_success() {
187        let now = Utc::now();
188        let before_now = now - Duration::minutes(1);
189        let tb = TimeBounds::always_valid()
190            .with_lower(before_now)
191            .unwrap()
192            .with_upper(now)
193            .unwrap();
194        assert_ne!(None, *tb.lower());
195        assert_ne!(None, *tb.upper());
196    }
197
198    #[test]
199    fn test_with_upper_before_the_seventies() {
200        let res = TimeBounds::always_valid().with_upper(Utc::now().with_year(1960).unwrap());
201        assert!(res.is_err());
202    }
203
204    #[test]
205    fn test_with_lower_before_the_seventies() {
206        let res = TimeBounds::always_valid().with_lower(Utc::now().with_year(1960).unwrap());
207        assert!(res.is_err());
208    }
209
210    #[test]
211    fn test_with_upper_before_lower() {
212        let now = Utc::now();
213        let before_now = now - Duration::minutes(1);
214        let res = TimeBounds::always_valid()
215            .with_lower(now)
216            .unwrap()
217            .with_upper(before_now);
218        assert!(res.is_err());
219    }
220
221    #[test]
222    fn test_with_lower_after_upper() {
223        let now = Utc::now();
224        let before_now = now - Duration::minutes(1);
225        let res = TimeBounds::always_valid()
226            .with_upper(before_now)
227            .unwrap()
228            .with_lower(now);
229        assert!(res.is_err());
230    }
231
232    #[test]
233    fn test_serialize_always_valid() {
234        let tb = TimeBounds::always_valid();
235        let xdr = tb.xdr_base64().unwrap();
236        assert_eq!("AAAAAAAAAAAAAAAAAAAAAA==", xdr);
237    }
238
239    #[test]
240    fn test_serialize_with_bounds() {
241        let now = DateTime::<Utc>::from_timestamp(1594305941, 0).unwrap();
242        let before_now = now - Duration::minutes(1);
243        let tb = TimeBounds::always_valid()
244            .with_lower(before_now)
245            .unwrap()
246            .with_upper(now)
247            .unwrap();
248        let xdr = tb.xdr_base64().unwrap();
249        assert_eq!("AAAAAF8HLVkAAAAAXwctlQ==", xdr);
250    }
251
252    #[test]
253    fn test_deserialize_always_valid() {
254        let expected = TimeBounds::always_valid();
255        let tb = TimeBounds::from_xdr_base64("AAAAAAAAAAAAAAAAAAAAAA==").unwrap();
256        assert_eq!(expected, tb);
257    }
258
259    #[test]
260    fn test_deserialize_with_bounds() {
261        let now = DateTime::<Utc>::from_timestamp(1594305941, 0).unwrap();
262        let before_now = now - Duration::minutes(1);
263        let expected = TimeBounds::always_valid()
264            .with_lower(before_now)
265            .unwrap()
266            .with_upper(now)
267            .unwrap();
268        let tb = TimeBounds::from_xdr_base64("AAAAAF8HLVkAAAAAXwctlQ==").unwrap();
269        assert_eq!(expected, tb);
270    }
271}