1use std::{
2 fmt::{Display, Formatter},
3 time::{Duration, SystemTime},
4};
5
6use hopr_crypto_types::prelude::*;
7use hopr_primitive_types::prelude::*;
8
9#[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 #[default]
18 Closed,
19 Open,
21 #[strum(serialize = "PendingToClose")]
24 PendingToClose(SystemTime),
25}
26
27impl ChannelStatus {
28 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
43impl 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
54impl PartialEq for ChannelStatus {
56 fn eq(&self, other: &Self) -> bool {
57 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#[repr(u8)]
74#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display, strum::EnumString)]
75#[strum(serialize_all = "lowercase")]
76pub enum ChannelDirection {
77 Incoming = 0,
79 Outgoing = 1,
81}
82
83pub type ChannelId = Hash;
85
86#[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 pub fn new(source: Address, destination: Address) -> Self {
100 Self(source, destination)
101 }
102
103 pub fn source(&self) -> &Address {
105 &self.0
106 }
107
108 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#[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 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 pub fn get_id(&self) -> &ChannelId {
164 &self.id
165 }
166
167 pub fn closure_time_passed(&self, current_time: SystemTime) -> bool {
172 self.status.closure_time_elapsed(¤t_time)
173 }
174
175 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 pub fn closure_time_at(&self) -> Option<SystemTime> {
192 match self.status {
193 ChannelStatus::PendingToClose(ct) => Some(ct),
194 _ => None,
195 }
196 }
197
198 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 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 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
240pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
242 Hash::create(&[source.as_ref(), destination.as_ref()])
243}
244
245#[derive(Clone, Copy, Debug, strum::Display)]
247pub enum ChannelChange {
248 #[strum(to_string = "status change: {left} -> {right}")]
250 Status { left: ChannelStatus, right: ChannelStatus },
251
252 #[strum(to_string = "balance change: {left} -> {right}")]
254 Balance { left: HoprBalance, right: HoprBalance },
255
256 #[strum(to_string = "epoch change: {left} -> {right}")]
258 Epoch { left: u32, right: u32 },
259
260 #[strum(to_string = "ticket index change: {left} -> {right}")]
262 TicketIndex { left: u64, right: u64 },
263}
264
265impl ChannelChange {
266 pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
273 assert_eq!(left.id, right.id, "must have equal ids"); 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#[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 pub fn channel_id(&self) -> &ChannelId {
327 &self.0
328 }
329}
330
331#[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}