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 = 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#[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(closure_time.saturating_sub(current_time)),
182 ChannelStatus::Closed => Some(Duration::ZERO),
183 }
184 }
185
186 pub fn closure_time_at(&self) -> Option<SystemTime> {
190 match self.status {
191 ChannelStatus::PendingToClose(ct) => Some(ct),
192 _ => None,
193 }
194 }
195
196 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 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 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
238pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
240 Hash::create(&[source.as_ref(), destination.as_ref()])
241}
242
243#[derive(Clone, Copy, Debug, strum::Display)]
245pub enum ChannelChange {
246 #[strum(to_string = "status change: {left} -> {right}")]
248 Status { left: ChannelStatus, right: ChannelStatus },
249
250 #[strum(to_string = "balance change: {left} -> {right}")]
252 Balance { left: HoprBalance, right: HoprBalance },
253
254 #[strum(to_string = "epoch change: {left} -> {right}")]
256 Epoch { left: u32, right: u32 },
257
258 #[strum(to_string = "ticket index change: {left} -> {right}")]
260 TicketIndex { left: u64, right: u64 },
261}
262
263impl ChannelChange {
264 pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
271 assert_eq!(left.id, right.id, "must have equal ids"); 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#[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 pub fn channel_id(&self) -> &ChannelId {
325 &self.0
326 }
327}
328
329#[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}