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 = ct_1.max(ct_2).saturating_sub(*ct_1.min(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(closure_time.saturating_sub(current_time)),
182            ChannelStatus::Closed => Some(Duration::ZERO),
183        }
184    }
185
186    /// Returns the earliest time the channel can transition from `PendingToClose` into `Closed`.
187    ///
188    /// If the channel is not in the ` PendingToClose ` state, it returns `None`.
189    pub fn closure_time_at(&self) -> Option<SystemTime> {
190        match self.status {
191            ChannelStatus::PendingToClose(ct) => Some(ct),
192            _ => None,
193        }
194    }
195
196    /// Determines the channel direction given the self-address.
197    ///
198    /// Returns `None` if neither source nor destination are equal to `me`.
199    pub fn direction(&self, me: &Address) -> Option<ChannelDirection> {
200        if self.source.eq(me) {
201            Some(ChannelDirection::Outgoing)
202        } else if self.destination.eq(me) {
203            Some(ChannelDirection::Incoming)
204        } else {
205            None
206        }
207    }
208
209    /// Determines the channel's direction and counterparty relative to `me`.
210    ///
211    /// Returns `None` if neither source nor destination are equal to `me`.
212    pub fn orientation(&self, me: &Address) -> Option<(ChannelDirection, Address)> {
213        if self.source.eq(me) {
214            Some((ChannelDirection::Outgoing, self.destination))
215        } else if self.destination.eq(me) {
216            Some((ChannelDirection::Incoming, self.source))
217        } else {
218            None
219        }
220    }
221
222    /// Makes a diff of this channel (left) and the `other` channel (right).
223    ///
224    /// The channels must have the same ID.
225    ///
226    /// See [`ChannelChange`]
227    pub fn diff(&self, other: &Self) -> Vec<ChannelChange> {
228        ChannelChange::diff_channels(self, other)
229    }
230}
231
232impl Display for ChannelEntry {
233    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234        write!(f, "{} channel {}", self.status, self.get_id(),)
235    }
236}
237
238/// Generates channel ID hash from `source` and `destination` addresses.
239pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
240    Hash::create(&[source.as_ref(), destination.as_ref()])
241}
242
243/// Lists possible changes on a channel entry update
244#[derive(Clone, Copy, Debug, strum::Display)]
245pub enum ChannelChange {
246    /// Channel status has changed
247    #[strum(to_string = "status change: {left} -> {right}")]
248    Status { left: ChannelStatus, right: ChannelStatus },
249
250    /// Channel balance has changed
251    #[strum(to_string = "balance change: {left} -> {right}")]
252    Balance { left: HoprBalance, right: HoprBalance },
253
254    /// Channel epoch has changed
255    #[strum(to_string = "epoch change: {left} -> {right}")]
256    Epoch { left: u32, right: u32 },
257
258    /// Ticket index has changed
259    #[strum(to_string = "ticket index change: {left} -> {right}")]
260    TicketIndex { left: u64, right: u64 },
261}
262
263impl ChannelChange {
264    /// Compares the two given channels and returns a vector of [`ChannelChange`]s.
265    ///
266    /// Both channels must have the same ID (source, destination and direction) to be comparable using this function.
267    /// The function panics if `left` and `right` do not have equal ids.
268    ///
269    /// If an empty vector is returned, it implies that both channels are equal.
270    pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
271        assert_eq!(left.id, right.id, "must have equal ids"); // misuse
272
273        // Short-circuit if both channels are equal, avoiding unnecessary allocations
274        if left == right {
275            return Vec::with_capacity(0);
276        }
277
278        let mut ret = Vec::with_capacity(4);
279        if left.status != right.status {
280            ret.push(ChannelChange::Status {
281                left: left.status,
282                right: right.status,
283            });
284        }
285
286        if left.balance != right.balance {
287            ret.push(ChannelChange::Balance {
288                left: left.balance,
289                right: right.balance,
290            });
291        }
292
293        if left.channel_epoch != right.channel_epoch {
294            ret.push(ChannelChange::Epoch {
295                left: left.channel_epoch,
296                right: right.channel_epoch,
297            });
298        }
299
300        if left.ticket_index != right.ticket_index {
301            ret.push(ChannelChange::TicketIndex {
302                left: left.ticket_index,
303                right: right.ticket_index,
304            })
305        }
306
307        ret
308    }
309}
310
311/// A wrapper around [`ChannelId`] representing a Channel that is corrupted.
312#[derive(Copy, Clone, Debug, PartialEq)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
314pub struct CorruptedChannelEntry(ChannelId);
315
316impl From<ChannelId> for CorruptedChannelEntry {
317    fn from(value: ChannelId) -> Self {
318        CorruptedChannelEntry(value)
319    }
320}
321
322impl CorruptedChannelEntry {
323    /// Returns the channel ID of the corrupted channel.
324    pub fn channel_id(&self) -> &ChannelId {
325        &self.0
326    }
327}
328
329/// A pair of source and destination addresses representing a channel.
330#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
331pub struct SrcDstPair(Address, Address);
332
333impl From<ChannelEntry> for SrcDstPair {
334    fn from(channel: ChannelEntry) -> Self {
335        SrcDstPair(channel.source, channel.destination)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use std::{
342        ops::Add,
343        str::FromStr,
344        time::{Duration, SystemTime},
345    };
346
347    use hex_literal::hex;
348
349    use super::*;
350
351    lazy_static::lazy_static! {
352        static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775")).expect("lazy static keypair should be constructible");
353        static ref BOB: ChainKeypair = ChainKeypair::from_secret(&hex!("48680484c6fc31bc881a0083e6e32b6dc789f9eaba0f8b981429fd346c697f8c")).expect("lazy static keypair should be constructible");
354
355        static ref ADDRESS_1: Address = "3829b806aea42200c623c4d6b9311670577480ed".parse().expect("lazy static address should be constructible");
356        static ref ADDRESS_2: Address = "1a34729c69e95d6e11c3a9b9be3ea0c62c6dc5b1".parse().expect("lazy static address should be constructible");
357    }
358
359    #[test]
360    pub fn test_generate_id() -> anyhow::Result<()> {
361        let from = Address::from_str("0xa460f2e47c641b64535f5f4beeb9ac6f36f9d27c")?;
362        let to = Address::from_str("0xb8b75fef7efdf4530cf1688c933d94e4e519ccd1")?;
363        let id = generate_channel_id(&from, &to).to_string();
364        assert_eq!("0x1a410210ce7265f3070bf0e8885705dce452efcfbd90a5467525d136fcefc64a", id);
365
366        Ok(())
367    }
368
369    #[test]
370    fn channel_status_names() {
371        assert_eq!("Open", ChannelStatus::Open.to_string());
372        assert_eq!("Closed", ChannelStatus::Closed.to_string());
373        assert_eq!(
374            "PendingToClose",
375            ChannelStatus::PendingToClose(SystemTime::now()).to_string()
376        );
377    }
378
379    #[test]
380    fn channel_status_repr_compat() {
381        assert_eq!(ChannelStatusDiscriminants::Open as i8, i8::from(ChannelStatus::Open));
382        assert_eq!(
383            ChannelStatusDiscriminants::Closed as i8,
384            i8::from(ChannelStatus::Closed)
385        );
386        assert_eq!(
387            ChannelStatusDiscriminants::PendingToClose as i8,
388            i8::from(ChannelStatus::PendingToClose(SystemTime::now()))
389        );
390    }
391
392    #[test]
393    pub fn channel_entry_closure_time() {
394        let mut ce = ChannelEntry::new(*ADDRESS_1, *ADDRESS_2, 10.into(), 23, ChannelStatus::Open, 3);
395
396        assert!(
397            !ce.closure_time_passed(SystemTime::now()),
398            "opened channel cannot pass closure time"
399        );
400        assert!(
401            ce.remaining_closure_time(SystemTime::now()).is_none(),
402            "opened channel cannot have remaining closure time"
403        );
404
405        let current_time = SystemTime::now();
406        ce.status = ChannelStatus::PendingToClose(current_time.add(Duration::from_secs(60)));
407
408        assert!(
409            !ce.closure_time_passed(current_time),
410            "must not have passed closure time"
411        );
412        assert_eq!(
413            60,
414            ce.remaining_closure_time(current_time)
415                .expect("must have closure time")
416                .as_secs()
417        );
418
419        let current_time = current_time.add(Duration::from_secs(120));
420
421        assert!(ce.closure_time_passed(current_time), "must have passed closure time");
422        assert_eq!(
423            Duration::ZERO,
424            ce.remaining_closure_time(current_time).expect("must have closure time")
425        );
426    }
427}