Skip to main content

hopr_internal_types/
channels.rs

1use std::{
2    fmt::{Display, Formatter},
3    time::{Duration, SystemTime},
4};
5
6use hopr_crypto_types::prelude::*;
7use hopr_primitive_types::prelude::*;
8
9/// Describes status of a channel
10#[derive(Copy, Clone, Debug, smart_default::SmartDefault, strum::Display, strum::EnumDiscriminants)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[strum_discriminants(vis(pub))]
13#[strum_discriminants(derive(strum::FromRepr, strum::EnumCount), repr(i8))]
14#[strum(serialize_all = "PascalCase")]
15pub enum ChannelStatus {
16    /// The channel is closed.
17    #[default]
18    Closed,
19    /// The channel is opened.
20    Open,
21    /// The channel is pending to be closed.
22    /// The timestamp marks the *earliest* possible time when the channel can transition into the `Closed` state.
23    #[strum(serialize = "PendingToClose")]
24    PendingToClose(SystemTime),
25}
26
27impl ChannelStatus {
28    /// Checks if is [`ChannelStatus::PendingToClose`] and the closure time has elapsed.
29    ///
30    /// Otherwise, also:
31    ///
32    /// - Returns `false` if it is [`ChannelStatus::Open`].
33    /// - Returns `true` if it is [`ChannelStatus::Closed`].
34    pub fn closure_time_elapsed(&self, current_time: &SystemTime) -> bool {
35        match self {
36            ChannelStatus::Closed => true,
37            ChannelStatus::Open => false,
38            ChannelStatus::PendingToClose(closure_time) => closure_time <= current_time,
39        }
40    }
41}
42
43// Cannot use #[repr(u8)] due to PendingToClose
44impl From<ChannelStatus> for i8 {
45    fn from(value: ChannelStatus) -> Self {
46        match value {
47            ChannelStatus::Closed => 0,
48            ChannelStatus::Open => 1,
49            ChannelStatus::PendingToClose(_) => 2,
50        }
51    }
52}
53
54// Manual implementation of PartialEq, because we need only precision up to seconds in PendingToClose
55impl PartialEq for ChannelStatus {
56    fn eq(&self, other: &Self) -> bool {
57        // Use pattern matching to avoid recursion
58        match (self, other) {
59            (Self::Open, Self::Open) => true,
60            (Self::Closed, Self::Closed) => true,
61            (Self::PendingToClose(ct_1), Self::PendingToClose(ct_2)) => {
62                let diff = hopr_primitive_types::traits::SaturatingSub::saturating_sub(ct_1, *ct_2);
63                diff.as_secs() == 0
64            }
65            _ => false,
66        }
67    }
68}
69impl Eq for ChannelStatus {}
70
71/// Describes a direction of node's own channel.
72/// The direction of a channel that is not own is undefined.
73#[repr(u8)]
74#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display, strum::EnumString)]
75#[strum(serialize_all = "lowercase")]
76pub enum ChannelDirection {
77    /// The other party is the initiator of the channel.
78    Incoming = 0,
79    /// Our own node is the initiator of the channel.
80    Outgoing = 1,
81}
82
83/// Alias for the [`Hash`](struct@Hash) representing a channel ID.
84pub type ChannelId = Hash;
85
86/// An immutable pair of addresses representing parties of a channel.
87#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub struct ChannelParties(Address, Address);
90
91impl Display for ChannelParties {
92    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93        write!(f, "{} -> {}", self.0, self.1)
94    }
95}
96
97impl ChannelParties {
98    /// New instance from source and destination addresses.
99    pub fn new(source: Address, destination: Address) -> Self {
100        Self(source, destination)
101    }
102
103    /// Channel source.
104    pub fn source(&self) -> &Address {
105        &self.0
106    }
107
108    /// Channel destination.
109    pub fn destination(&self) -> &Address {
110        &self.1
111    }
112}
113
114impl<'a> From<&'a ChannelParties> for ChannelId {
115    fn from(value: &'a ChannelParties) -> Self {
116        generate_channel_id(&value.0, &value.1)
117    }
118}
119
120impl<'a> From<&'a ChannelEntry> for ChannelParties {
121    fn from(value: &'a ChannelEntry) -> Self {
122        Self(value.source, value.destination)
123    }
124}
125
126/// Overall description of a channel
127#[derive(Copy, Clone, Debug, PartialEq)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129pub struct ChannelEntry {
130    pub source: Address,
131    pub destination: Address,
132    pub balance: HoprBalance,
133    pub ticket_index: u64,
134    pub status: ChannelStatus,
135    pub channel_epoch: u32,
136    id: ChannelId,
137}
138
139impl ChannelEntry {
140    /// Maximum possible balance of a channel: 10^25 wxHOPR
141    pub const MAX_CHANNEL_BALANCE: u128 = 10_u128.pow(25);
142
143    pub fn new(
144        source: Address,
145        destination: Address,
146        balance: HoprBalance,
147        ticket_index: u64,
148        status: ChannelStatus,
149        channel_epoch: u32,
150    ) -> Self {
151        ChannelEntry {
152            source,
153            destination,
154            balance,
155            ticket_index,
156            status,
157            channel_epoch,
158            id: generate_channel_id(&source, &destination),
159        }
160    }
161
162    /// Generates the channel ID using the source and destination address
163    pub fn get_id(&self) -> &ChannelId {
164        &self.id
165    }
166
167    /// Checks if the closure time of this channel has passed.
168    ///
169    /// Also returns `false` if the channel closure has not been initiated (it is in `Open` state).
170    /// Returns also `true`, if the channel is in `Closed` state.
171    pub fn closure_time_passed(&self, current_time: SystemTime) -> bool {
172        self.status.closure_time_elapsed(&current_time)
173    }
174
175    /// Calculates the remaining channel closure grace period.
176    ///
177    /// Returns `None` if the channel closure has not been initiated yet (channel is in `Open` state).
178    pub fn remaining_closure_time(&self, current_time: SystemTime) -> Option<Duration> {
179        match self.status {
180            ChannelStatus::Open => None,
181            ChannelStatus::PendingToClose(closure_time) => Some(
182                hopr_primitive_types::traits::SaturatingSub::saturating_sub(&closure_time, current_time),
183            ),
184            ChannelStatus::Closed => Some(Duration::ZERO),
185        }
186    }
187
188    /// Returns the earliest time the channel can transition from `PendingToClose` into `Closed`.
189    ///
190    /// If the channel is not in the ` PendingToClose ` state, it returns `None`.
191    pub fn closure_time_at(&self) -> Option<SystemTime> {
192        match self.status {
193            ChannelStatus::PendingToClose(ct) => Some(ct),
194            _ => None,
195        }
196    }
197
198    /// Determines the channel direction given the self-address.
199    ///
200    /// Returns `None` if neither source nor destination are equal to `me`.
201    pub fn direction(&self, me: &Address) -> Option<ChannelDirection> {
202        if self.source.eq(me) {
203            Some(ChannelDirection::Outgoing)
204        } else if self.destination.eq(me) {
205            Some(ChannelDirection::Incoming)
206        } else {
207            None
208        }
209    }
210
211    /// Determines the channel's direction and counterparty relative to `me`.
212    ///
213    /// Returns `None` if neither source nor destination are equal to `me`.
214    pub fn orientation(&self, me: &Address) -> Option<(ChannelDirection, Address)> {
215        if self.source.eq(me) {
216            Some((ChannelDirection::Outgoing, self.destination))
217        } else if self.destination.eq(me) {
218            Some((ChannelDirection::Incoming, self.source))
219        } else {
220            None
221        }
222    }
223
224    /// Makes a diff of this channel (left) and the `other` channel (right).
225    ///
226    /// The channels must have the same ID.
227    ///
228    /// See [`ChannelChange`]
229    pub fn diff(&self, other: &Self) -> Vec<ChannelChange> {
230        ChannelChange::diff_channels(self, other)
231    }
232}
233
234impl Display for ChannelEntry {
235    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236        write!(f, "{} channel {}", self.status, self.get_id(),)
237    }
238}
239
240/// Generates channel ID hash from `source` and `destination` addresses.
241pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
242    Hash::create(&[source.as_ref(), destination.as_ref()])
243}
244
245/// Lists possible changes on a channel entry update
246#[derive(Clone, Copy, Debug, strum::Display)]
247pub enum ChannelChange {
248    /// Channel status has changed
249    #[strum(to_string = "status change: {left} -> {right}")]
250    Status { left: ChannelStatus, right: ChannelStatus },
251
252    /// Channel balance has changed
253    #[strum(to_string = "balance change: {left} -> {right}")]
254    Balance { left: HoprBalance, right: HoprBalance },
255
256    /// Channel epoch has changed
257    #[strum(to_string = "epoch change: {left} -> {right}")]
258    Epoch { left: u32, right: u32 },
259
260    /// Ticket index has changed
261    #[strum(to_string = "ticket index change: {left} -> {right}")]
262    TicketIndex { left: u64, right: u64 },
263}
264
265impl ChannelChange {
266    /// Compares the two given channels and returns a vector of [`ChannelChange`]s.
267    ///
268    /// Both channels must have the same ID (source, destination and direction) to be comparable using this function.
269    /// The function panics if `left` and `right` do not have equal ids.
270    ///
271    /// If an empty vector is returned, it implies that both channels are equal.
272    pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
273        assert_eq!(left.id, right.id, "must have equal ids"); // misuse
274
275        // Short-circuit if both channels are equal, avoiding unnecessary allocations
276        if left == right {
277            return Vec::with_capacity(0);
278        }
279
280        let mut ret = Vec::with_capacity(4);
281        if left.status != right.status {
282            ret.push(ChannelChange::Status {
283                left: left.status,
284                right: right.status,
285            });
286        }
287
288        if left.balance != right.balance {
289            ret.push(ChannelChange::Balance {
290                left: left.balance,
291                right: right.balance,
292            });
293        }
294
295        if left.channel_epoch != right.channel_epoch {
296            ret.push(ChannelChange::Epoch {
297                left: left.channel_epoch,
298                right: right.channel_epoch,
299            });
300        }
301
302        if left.ticket_index != right.ticket_index {
303            ret.push(ChannelChange::TicketIndex {
304                left: left.ticket_index,
305                right: right.ticket_index,
306            })
307        }
308
309        ret
310    }
311}
312
313/// A wrapper around [`ChannelId`] representing a Channel that is corrupted.
314#[derive(Copy, Clone, Debug, PartialEq)]
315#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
316pub struct CorruptedChannelEntry(ChannelId);
317
318impl From<ChannelId> for CorruptedChannelEntry {
319    fn from(value: ChannelId) -> Self {
320        CorruptedChannelEntry(value)
321    }
322}
323
324impl CorruptedChannelEntry {
325    /// Returns the channel ID of the corrupted channel.
326    pub fn channel_id(&self) -> &ChannelId {
327        &self.0
328    }
329}
330
331/// A pair of source and destination addresses representing a channel.
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
333pub struct SrcDstPair(Address, Address);
334
335impl From<ChannelEntry> for SrcDstPair {
336    fn from(channel: ChannelEntry) -> Self {
337        SrcDstPair(channel.source, channel.destination)
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use std::{
344        ops::Add,
345        str::FromStr,
346        time::{Duration, SystemTime},
347    };
348
349    use hex_literal::hex;
350
351    use super::*;
352
353    lazy_static::lazy_static! {
354        static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775")).expect("lazy static keypair should be constructible");
355        static ref BOB: ChainKeypair = ChainKeypair::from_secret(&hex!("48680484c6fc31bc881a0083e6e32b6dc789f9eaba0f8b981429fd346c697f8c")).expect("lazy static keypair should be constructible");
356
357        static ref ADDRESS_1: Address = "3829b806aea42200c623c4d6b9311670577480ed".parse().expect("lazy static address should be constructible");
358        static ref ADDRESS_2: Address = "1a34729c69e95d6e11c3a9b9be3ea0c62c6dc5b1".parse().expect("lazy static address should be constructible");
359    }
360
361    #[test]
362    pub fn test_generate_id() -> anyhow::Result<()> {
363        let from = Address::from_str("0xa460f2e47c641b64535f5f4beeb9ac6f36f9d27c")?;
364        let to = Address::from_str("0xb8b75fef7efdf4530cf1688c933d94e4e519ccd1")?;
365        let id = generate_channel_id(&from, &to).to_string();
366        assert_eq!("0x1a410210ce7265f3070bf0e8885705dce452efcfbd90a5467525d136fcefc64a", id);
367
368        Ok(())
369    }
370
371    #[test]
372    fn channel_status_names() {
373        assert_eq!("Open", ChannelStatus::Open.to_string());
374        assert_eq!("Closed", ChannelStatus::Closed.to_string());
375        assert_eq!(
376            "PendingToClose",
377            ChannelStatus::PendingToClose(SystemTime::now()).to_string()
378        );
379    }
380
381    #[test]
382    fn channel_status_repr_compat() {
383        assert_eq!(ChannelStatusDiscriminants::Open as i8, i8::from(ChannelStatus::Open));
384        assert_eq!(
385            ChannelStatusDiscriminants::Closed as i8,
386            i8::from(ChannelStatus::Closed)
387        );
388        assert_eq!(
389            ChannelStatusDiscriminants::PendingToClose as i8,
390            i8::from(ChannelStatus::PendingToClose(SystemTime::now()))
391        );
392    }
393
394    #[test]
395    pub fn channel_entry_closure_time() {
396        let mut ce = ChannelEntry::new(*ADDRESS_1, *ADDRESS_2, 10.into(), 23, ChannelStatus::Open, 3);
397
398        assert!(
399            !ce.closure_time_passed(SystemTime::now()),
400            "opened channel cannot pass closure time"
401        );
402        assert!(
403            ce.remaining_closure_time(SystemTime::now()).is_none(),
404            "opened channel cannot have remaining closure time"
405        );
406
407        let current_time = SystemTime::now();
408        ce.status = ChannelStatus::PendingToClose(current_time.add(Duration::from_secs(60)));
409
410        assert!(
411            !ce.closure_time_passed(current_time),
412            "must not have passed closure time"
413        );
414        assert_eq!(
415            60,
416            ce.remaining_closure_time(current_time)
417                .expect("must have closure time")
418                .as_secs()
419        );
420
421        let current_time = current_time.add(Duration::from_secs(120));
422
423        assert!(ce.closure_time_passed(current_time), "must have passed closure time");
424        assert_eq!(
425            Duration::ZERO,
426            ce.remaining_closure_time(current_time).expect("must have closure time")
427        );
428    }
429}