ckb_std/
since.rs

1use core::cmp::Ordering;
2
3/// Transaction input's since field
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Since(u64);
6
7impl Since {
8    const LOCK_TYPE_FLAG: u64 = 1 << 63;
9    const METRIC_TYPE_FLAG_MASK: u64 = 0x6000_0000_0000_0000;
10    const FLAGS_MASK: u64 = 0xff00_0000_0000_0000;
11    const VALUE_MASK: u64 = 0x00ff_ffff_ffff_ffff;
12    const REMAIN_FLAGS_BITS: u64 = 0x1f00_0000_0000_0000;
13    const LOCK_BY_BLOCK_NUMBER_MASK: u64 = 0x0000_0000_0000_0000;
14    const LOCK_BY_EPOCH_MASK: u64 = 0x2000_0000_0000_0000;
15    const LOCK_BY_TIMESTAMP_MASK: u64 = 0x4000_0000_0000_0000;
16
17    pub fn from_block_number(number: u64, absolute: bool) -> Option<Self> {
18        if number & Self::FLAGS_MASK != 0 {
19            return None;
20        }
21        Some(Self::new(
22            number
23                | Self::LOCK_BY_BLOCK_NUMBER_MASK
24                | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }),
25        ))
26    }
27
28    pub fn from_timestamp(timestamp: u64, absolute: bool) -> Option<Self> {
29        if timestamp & Self::FLAGS_MASK != 0 {
30            return None;
31        }
32        Some(Self::new(
33            timestamp
34                | Self::LOCK_BY_TIMESTAMP_MASK
35                | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }),
36        ))
37    }
38
39    pub fn from_epoch(epoch: EpochNumberWithFraction, absolute: bool) -> Self {
40        debug_assert!(epoch.full_value() & Self::FLAGS_MASK == 0);
41        Self::new(
42            epoch.full_value()
43                | Self::LOCK_BY_EPOCH_MASK
44                | (if absolute { 0 } else { Self::LOCK_TYPE_FLAG }),
45        )
46    }
47
48    pub fn new(v: u64) -> Self {
49        Since(v)
50    }
51
52    pub fn as_u64(self) -> u64 {
53        self.0
54    }
55
56    pub fn is_absolute(self) -> bool {
57        self.0 & Self::LOCK_TYPE_FLAG == 0
58    }
59
60    #[inline]
61    pub fn is_relative(self) -> bool {
62        !self.is_absolute()
63    }
64
65    pub fn flags_is_valid(self) -> bool {
66        (self.0 & Self::REMAIN_FLAGS_BITS == 0)
67            && ((self.0 & Self::METRIC_TYPE_FLAG_MASK) != Self::METRIC_TYPE_FLAG_MASK)
68    }
69
70    pub fn flags(self) -> u64 {
71        self.0 & Self::FLAGS_MASK
72    }
73
74    pub fn extract_lock_value(self) -> Option<LockValue> {
75        let value = self.0 & Self::VALUE_MASK;
76        match self.0 & Self::METRIC_TYPE_FLAG_MASK {
77            //0b0000_0000
78            Self::LOCK_BY_BLOCK_NUMBER_MASK => Some(LockValue::BlockNumber(value)),
79            //0b0010_0000
80            Self::LOCK_BY_EPOCH_MASK => Some(LockValue::EpochNumberWithFraction(
81                EpochNumberWithFraction::from_full_value(value),
82            )),
83            //0b0100_0000
84            Self::LOCK_BY_TIMESTAMP_MASK => Some(LockValue::Timestamp(value * 1000)),
85            _ => None,
86        }
87    }
88
89    /// Given the base commitment block, this method converts a relative since
90    /// value to an absolute since value for later comparison.
91    #[cfg(feature = "ckb-types")]
92    pub fn to_absolute_value(self, base_block: ckb_types::packed::Header) -> Option<Self> {
93        debug_assert!(self.is_relative());
94
95        let to_le_u64 = |v: &ckb_types::packed::Uint64| {
96            let mut tmp = [0u8; 8];
97            tmp.copy_from_slice(&v.raw_data());
98            u64::from_le_bytes(tmp)
99        };
100
101        match self.extract_lock_value() {
102            Some(LockValue::BlockNumber(number)) => to_le_u64(&base_block.raw().number())
103                .checked_add(number)
104                .and_then(|block_number| Self::from_block_number(block_number, true)),
105            Some(LockValue::Timestamp(timestamp)) => to_le_u64(&base_block.raw().timestamp())
106                .checked_add(timestamp)
107                .and_then(|timestamp| Self::from_timestamp(timestamp, true)),
108            Some(LockValue::EpochNumberWithFraction(epoch)) => {
109                let base_epoch = EpochNumberWithFraction::from_full_value(
110                    to_le_u64(&base_block.raw().epoch()) & Self::VALUE_MASK,
111                );
112                Some(Self::from_epoch((epoch + base_epoch)?, true))
113            }
114            _ => None,
115        }
116    }
117}
118
119impl PartialOrd for Since {
120    fn partial_cmp(&self, other: &Since) -> Option<Ordering> {
121        // Given 2 since values alone, there is no way to compare an absolute value
122        // to a relative value. However, a higher level method can convert a relative
123        // value to an absolute value.
124        if self.is_absolute() != other.is_absolute() {
125            return None;
126        }
127
128        match (self.extract_lock_value(), other.extract_lock_value()) {
129            (Some(LockValue::BlockNumber(a)), Some(LockValue::BlockNumber(b))) => a.partial_cmp(&b),
130            (
131                Some(LockValue::EpochNumberWithFraction(a)),
132                Some(LockValue::EpochNumberWithFraction(b)),
133            ) => a.partial_cmp(&b),
134            (Some(LockValue::Timestamp(a)), Some(LockValue::Timestamp(b))) => a.partial_cmp(&b),
135            _ => None,
136        }
137    }
138}
139
140pub enum LockValue {
141    BlockNumber(u64),
142    EpochNumberWithFraction(EpochNumberWithFraction),
143    Timestamp(u64),
144}
145
146impl LockValue {
147    pub fn block_number(&self) -> Option<u64> {
148        if let Self::BlockNumber(v) = self {
149            Some(*v)
150        } else {
151            None
152        }
153    }
154
155    pub fn epoch(&self) -> Option<EpochNumberWithFraction> {
156        if let Self::EpochNumberWithFraction(v) = self {
157            Some(*v)
158        } else {
159            None
160        }
161    }
162
163    pub fn timestamp(&self) -> Option<u64> {
164        if let Self::Timestamp(v) = self {
165            Some(*v)
166        } else {
167            None
168        }
169    }
170}
171
172#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
173pub struct EpochNumberWithFraction(u64);
174
175impl EpochNumberWithFraction {
176    pub const NUMBER_OFFSET: usize = 0;
177    pub const NUMBER_BITS: usize = 24;
178    pub const NUMBER_MAXIMUM_VALUE: u64 = (1u64 << Self::NUMBER_BITS);
179    pub const NUMBER_MASK: u64 = (Self::NUMBER_MAXIMUM_VALUE - 1);
180    pub const INDEX_OFFSET: usize = Self::NUMBER_BITS;
181    pub const INDEX_BITS: usize = 16;
182    pub const INDEX_MAXIMUM_VALUE: u64 = (1u64 << Self::INDEX_BITS);
183    pub const INDEX_MASK: u64 = (Self::INDEX_MAXIMUM_VALUE - 1);
184    pub const LENGTH_OFFSET: usize = Self::NUMBER_BITS + Self::INDEX_BITS;
185    pub const LENGTH_BITS: usize = 16;
186    pub const LENGTH_MAXIMUM_VALUE: u64 = (1u64 << Self::LENGTH_BITS);
187    pub const LENGTH_MASK: u64 = (Self::LENGTH_MAXIMUM_VALUE - 1);
188
189    pub fn create(number: u64, index: u64, length: u64) -> Option<EpochNumberWithFraction> {
190        if number < Self::NUMBER_MAXIMUM_VALUE
191            && index < Self::INDEX_MAXIMUM_VALUE
192            && length < Self::LENGTH_MAXIMUM_VALUE
193            && length > 0
194            && index < length
195        {
196            Some(Self::new_unchecked(number, index, length))
197        } else {
198            None
199        }
200    }
201
202    pub fn new(number: u64, index: u64, length: u64) -> EpochNumberWithFraction {
203        debug_assert!(number < Self::NUMBER_MAXIMUM_VALUE);
204        debug_assert!(index < Self::INDEX_MAXIMUM_VALUE);
205        debug_assert!(length < Self::LENGTH_MAXIMUM_VALUE);
206        debug_assert!(length > 0);
207        debug_assert!(index < length);
208        Self::new_unchecked(number, index, length)
209    }
210
211    pub const fn new_unchecked(number: u64, index: u64, length: u64) -> Self {
212        EpochNumberWithFraction(
213            (length << Self::LENGTH_OFFSET)
214                | (index << Self::INDEX_OFFSET)
215                | (number << Self::NUMBER_OFFSET),
216        )
217    }
218
219    pub fn number(self) -> u64 {
220        (self.0 >> Self::NUMBER_OFFSET) & Self::NUMBER_MASK
221    }
222
223    pub fn index(self) -> u64 {
224        (self.0 >> Self::INDEX_OFFSET) & Self::INDEX_MASK
225    }
226
227    pub fn length(self) -> u64 {
228        (self.0 >> Self::LENGTH_OFFSET) & Self::LENGTH_MASK
229    }
230
231    pub fn full_value(self) -> u64 {
232        self.0
233    }
234
235    // One caveat here, is that if the user specifies a zero epoch length either
236    // delibrately, or by accident, calling to_rational() after that might
237    // result in a division by zero panic. To prevent that, this method would
238    // automatically rewrite the value to epoch index 0 with epoch length to
239    // prevent panics
240    pub fn from_full_value(value: u64) -> Self {
241        let epoch = Self(value);
242        if epoch.length() == 0 {
243            Self::new(epoch.number(), 0, 1)
244        } else {
245            epoch
246        }
247    }
248}
249
250impl PartialOrd for EpochNumberWithFraction {
251    fn partial_cmp(&self, other: &EpochNumberWithFraction) -> Option<Ordering> {
252        if self.number() < other.number() {
253            Some(Ordering::Less)
254        } else if self.number() > other.number() {
255            Some(Ordering::Greater)
256        } else {
257            let block_a = (self.index() as u128) * (other.length() as u128);
258            let block_b = (other.index() as u128) * (self.length() as u128);
259            block_a.partial_cmp(&block_b)
260        }
261    }
262}
263
264impl core::ops::Add for EpochNumberWithFraction {
265    type Output = Option<EpochNumberWithFraction>;
266
267    fn add(self, rhs: EpochNumberWithFraction) -> Self::Output {
268        let mut number = self.number().checked_add(rhs.number())?;
269
270        let mut numerator = ((self.index() as u128) * (rhs.length() as u128))
271            .checked_add((rhs.index() as u128) * (self.length() as u128))?;
272        let mut denominator = (self.length() as u128) * (rhs.length() as u128);
273        let divisor = gcd::binary_u128(numerator, denominator);
274        debug_assert!(numerator % divisor == 0);
275        debug_assert!(denominator % divisor == 0);
276        numerator /= divisor;
277        denominator /= divisor;
278
279        let full_epoches = u64::try_from(numerator / denominator).ok()?;
280        number = number.checked_add(full_epoches)?;
281        numerator %= denominator;
282
283        let numerator = u64::try_from(numerator).ok()?;
284        let denominator = u64::try_from(denominator).ok()?;
285
286        EpochNumberWithFraction::create(number, numerator, denominator)
287    }
288}